Skip to content

Commit

Permalink
new analytics productivity route and components (#4223)
Browse files Browse the repository at this point in the history
* new Productivity route and components

* remove console.log

* Add more tests

* Add alumni and inactive badges

* fix test

* linting

* linting

* fix test

* fix multiple display

* add flag

* add test

* Update tests

* camel to kebab routes

* fix import order

* fix type import

* re-enable productivity flag

* update badge labels

* adjust table sizings

* skip e2e tests
  • Loading branch information
lctrt authored Apr 8, 2024
1 parent d1beb9d commit 95e04da
Show file tree
Hide file tree
Showing 56 changed files with 1,083 additions and 165 deletions.
1 change: 1 addition & 0 deletions .github/workflows/reusable-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:

test-e2e:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: mcr.microsoft.com/playwright:focal
steps:
Expand Down
80 changes: 66 additions & 14 deletions apps/crn-frontend/src/analytics/Routes.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,84 @@
import { isEnabled } from '@asap-hub/flags';
import { SkeletonBodyFrame as Frame } from '@asap-hub/frontend-utils';
import { AnalyticsPage } from '@asap-hub/react-components';
import { FC, lazy, useEffect } from 'react';
import { Route, Switch, useRouteMatch } from 'react-router-dom';
import { analytics } from '@asap-hub/routing';
import { lazy, useEffect } from 'react';
import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom';

const loadAnalytics = () =>
import(/* webpackChunkName: "analytics" */ './Analytics');
const loadProductivity = () =>
import(/* webpackChunkName: "productivity" */ './productivity/Productivity');

const AnalyticsBody = lazy(loadAnalytics);
const loadLeadership = () =>
import(/* webpackChunkName: "leadership" */ './leadership/Leadership');

const About: FC<Record<string, never>> = () => {
const LeadershipBody = lazy(loadLeadership);
const ProductivityBody = lazy(loadProductivity);

const Routes = () => {
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
loadAnalytics();
loadLeadership().then(loadLeadership);
}, []);

const { path } = useRouteMatch();

return (
<Switch>
<Route exact path={path}>
<AnalyticsPage>
<Frame title="Analytics">
<AnalyticsBody />
</Frame>
</AnalyticsPage>
{isEnabled('DISPLAY_ANALYTICS_PRODUCTIVITY') && (
<Route path={path + analytics({}).productivity.template}>
<Switch>
<Route
exact
path={
path +
analytics({}).productivity.template +
analytics({}).productivity({}).metric.template
}
>
<AnalyticsPage>
<Frame title="Resource & Data Sharing">
<ProductivityBody />
</Frame>
</AnalyticsPage>
</Route>
<Redirect
to={analytics({}).productivity({}).metric({ metric: 'user' }).$}
/>
</Switch>
</Route>
)}
<Route path={path + analytics({}).leadership.template}>
<Switch>
<Route
exact
path={
path +
analytics({}).leadership.template +
analytics({}).leadership({}).metric.template
}
>
<AnalyticsPage>
<Frame title="Leadership & Membership">
<LeadershipBody />
</Frame>
</AnalyticsPage>
</Route>
<Redirect
to={
analytics({}).leadership({}).metric({ metric: 'working-group' }).$
}
/>
</Switch>
</Route>
{isEnabled('DISPLAY_ANALYTICS_PRODUCTIVITY') ? (
<Redirect to={analytics({}).productivity({ metric: 'user' }).$} />
) : (
<Redirect
to={analytics({}).leadership({ metric: 'working-group' }).$}
/>
)}
</Switch>
);
};

export default About;
export default Routes;
90 changes: 75 additions & 15 deletions apps/crn-frontend/src/analytics/__tests__/Routes.test.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
import {
render,
screen,
waitFor,
waitForElementToBeRemoved,
} from '@testing-library/react';
import { MemoryRouter, Route } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { Suspense } from 'react';
import { mockConsoleError } from '@asap-hub/dom-test-utils';
import { analytics } from '@asap-hub/routing';
import { disable, enable } from '@asap-hub/flags';

import About from '../Routes';
import { getAnalyticsLeadership } from '../api';
import Analytics from '../Routes';
import { getAnalyticsLeadership } from '../leadership/api';
import { Auth0Provider, WhenReady } from '../../auth/test-utils';

jest.mock('../api');
jest.mock('../leadership/api');
mockConsoleError();
afterEach(() => {
jest.clearAllMocks();
});

const mockGetMemberships = getAnalyticsLeadership as jest.MockedFunction<
typeof getAnalyticsLeadership
>;
const mockGetAnalyticsLeadership =
getAnalyticsLeadership as jest.MockedFunction<typeof getAnalyticsLeadership>;

const renderPage = async () => {
render(
const renderPage = async (path: string) => {
const { container } = render(
<RecoilRoot>
<Suspense fallback="loading">
<Auth0Provider user={{}}>
<WhenReady>
<MemoryRouter initialEntries={['/analytics']}>
<MemoryRouter initialEntries={[{ pathname: path }]}>
<Route path={analytics.template}>
<About />
<Analytics />
</Route>
</MemoryRouter>
</WhenReady>
Expand All @@ -40,13 +41,66 @@ const renderPage = async () => {
</RecoilRoot>,
);
await waitForElementToBeRemoved(() => screen.queryByText(/loading/i));

return container;
};

describe('Analytics page', () => {
it('renders the Analytics Page successfully', async () => {
mockGetMemberships.mockResolvedValueOnce({ items: [], total: 0 });
await renderPage(
analytics({}).productivity({}).metric({ metric: 'user' }).$,
);
expect(
await screen.findByText(/Analytics/i, {
selector: 'h1',
}),
).toBeVisible();
});

it('redirects to user productivity page when flag is true', async () => {
enable('DISPLAY_ANALYTICS_PRODUCTIVITY');

await renderPage(analytics({}).$);

expect(
await screen.findByText(/User Productivity/i, {
selector: 'h3',
}),
).toBeVisible();
});

it('redirects to working group page when productivity flag is false', async () => {
disable('DISPLAY_ANALYTICS_PRODUCTIVITY');
mockGetAnalyticsLeadership.mockResolvedValueOnce({ items: [], total: 0 });
await renderPage(analytics({}).$);

expect(
await screen.findByText(/Working Group Leadership & Membership/i, {
selector: 'h3',
}),
).toBeVisible();
});
});

describe('Productivity', () => {
it('renders the productivity tab', async () => {
await renderPage(
analytics({}).productivity({}).metric({ metric: 'team' }).$,
);
expect(
await screen.findByText(/Analytics/i, {
selector: 'h1',
}),
).toBeVisible();
});
});
describe('Leadership & Membership', () => {
it('renders the Analytics Page successfully', async () => {
mockGetAnalyticsLeadership.mockResolvedValueOnce({ items: [], total: 0 });

await renderPage();
await renderPage(
analytics({}).leadership({}).metric({ metric: 'interest-group' }).$,
);
expect(
await screen.findByText(/Analytics/i, {
selector: 'h1',
Expand All @@ -55,11 +109,17 @@ describe('Analytics page', () => {
});

it('renders error message when the response is not a 2XX', async () => {
mockGetMemberships.mockRejectedValue(new Error('Failed to fetch'));
mockGetAnalyticsLeadership.mockRejectedValueOnce(
new Error('Failed to fetch'),
);
await renderPage(
analytics({}).leadership({}).metric({ metric: 'interest-group' }).$,
);

await renderPage();
await waitFor(() => {
expect(mockGetAnalyticsLeadership).toHaveBeenCalled();
});

expect(mockGetMemberships).toHaveBeenCalled();
expect(screen.getByText(/Something went wrong/i)).toBeVisible();
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { FC, useState } from 'react';
import { AnalyticsPageBody } from '@asap-hub/react-components';
import { FC } from 'react';
import { AnalyticsLeadershipPageBody } from '@asap-hub/react-components';
import { useHistory, useParams } from 'react-router-dom';
import { analytics } from '@asap-hub/routing';

import { useAnalyticsLeadership } from './state';
import { usePagination, usePaginationParams } from '../hooks';
import { usePagination, usePaginationParams } from '../../hooks';

type MetricResponse = {
id: string;
Expand All @@ -19,9 +22,9 @@ type MetricResponse = {

const getDataForMetric = (
data: MetricResponse[],
metric: 'workingGroup' | 'interestGroup',
metric: 'working-group' | 'interest-group',
) => {
if (metric === 'workingGroup') {
if (metric === 'working-group') {
return data.map((row) => ({
id: row.id,
name: row.displayName,
Expand All @@ -41,21 +44,27 @@ const getDataForMetric = (
}));
};

const About: FC<Record<string, never>> = () => {
const [metric, setMetric] = useState<'workingGroup' | 'interestGroup'>(
'workingGroup',
);
const Leadership: FC<Record<string, never>> = () => {
const history = useHistory();
const { metric } = useParams<{
metric: 'working-group' | 'interest-group';
}>();
const setMetric = (newMetric: 'working-group' | 'interest-group') =>
history.push(analytics({}).leadership({}).metric({ metric: newMetric }).$);

const { currentPage, pageSize } = usePaginationParams();

const { items, total } = useAnalyticsLeadership({
currentPage,
pageSize,
searchQuery: '',
filters: new Set(),
});

const { numberOfPages, renderPageHref } = usePagination(total, pageSize);

return (
<AnalyticsPageBody
<AnalyticsLeadershipPageBody
metric={metric}
setMetric={setMetric}
data={getDataForMetric(items, metric)}
Expand All @@ -66,4 +75,4 @@ const About: FC<Record<string, never>> = () => {
);
};

export default About;
export default Leadership;
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import {
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Suspense } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { MemoryRouter, Route } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { analytics } from '@asap-hub/routing';

import Analytics from '../Analytics';
import Leadership from '../Leadership';
import { getAnalyticsLeadership } from '../api';
import { analyticsLeadershipState } from '../state';

Expand Down Expand Up @@ -53,7 +54,9 @@ const data: ListAnalyticsTeamLeadershipResponse = {
],
};

const renderPage = async () => {
const renderPage = async (
path = analytics({}).leadership({}).metric({ metric: 'working-group' }).$,
) => {
const result = render(
<RecoilRoot
initializeState={({ reset }) => {
Expand All @@ -68,8 +71,10 @@ const renderPage = async () => {
<Suspense fallback="loading">
<Auth0Provider user={{}}>
<WhenReady>
<MemoryRouter initialEntries={['/analytics']}>
<Analytics />
<MemoryRouter initialEntries={[path]}>
<Route path="/analytics/leadership/:metric">
<Leadership />
</Route>
</MemoryRouter>
</WhenReady>
</Auth0Provider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { AlgoliaSearchClient, ClientSearchResponse } from '@asap-hub/algolia';
import { GetListOptions } from '@asap-hub/frontend-utils';
import { AnalyticsTeamLeadershipResponse } from '@asap-hub/model';
import { getAnalyticsLeadership } from '../api';
import { createAlgoliaResponse } from '../../__fixtures__/algolia';
import { createAlgoliaResponse } from '../../../__fixtures__/algolia';

jest.mock('../../config');
jest.mock('../../../config');

afterEach(() => {
nock.cleanAll();
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
selectorFamily,
useRecoilState,
} from 'recoil';
import { useAnalyticsAlgolia } from '../hooks/algolia';
import { useAnalyticsAlgolia } from '../../hooks/algolia';
import { getAnalyticsLeadership } from './api';

const analyticsLeadershipIndexState = atomFamily<
Expand Down
Loading

0 comments on commit 95e04da

Please sign in to comment.