Skip to content

Commit

Permalink
feat: batch activity view (#2290)
Browse files Browse the repository at this point in the history
  • Loading branch information
stepan662 authored May 3, 2024
1 parent 5e4116e commit 272d03f
Show file tree
Hide file tree
Showing 27 changed files with 718 additions and 125 deletions.
22 changes: 22 additions & 0 deletions e2e/cypress/e2e/projects/projectDashboard.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ describe('Project stats', () => {
.should('be.visible');
cy.gcy('activity-compact').contains('Created key').should('be.visible');
cy.gcy('activity-compact').contains('Created project').should('be.visible');

openActivityDetail('Created key');

// activity is accessible through external link
cy.url().then((url) => {
const activityId = new URL(url).searchParams.get('activity');
const projectId = new URL(url).pathname.match(/[0-9]+/)[0];
cy.visit(
HOST + `/projects/${projectId}/activity-detail?activity=${activityId}`
);
cy.waitForDom();
cy.gcy('activity-detail-dialog').contains('Created key');
});
});

it('Global statistics', () => {
Expand Down Expand Up @@ -86,6 +99,15 @@ describe('Project stats', () => {
});
});

const openActivityDetail = (label: string) => {
cy.gcy('activity-compact')
.contains(label)
.closestDcy('activity-compact')
.findDcy('activity-compact-detail-button')
.click({ force: true });
cy.gcy('activity-detail-dialog').contains(label);
};

const checkLabelRow = (
state: string,
percentage: string,
Expand Down
2 changes: 2 additions & 0 deletions e2e/cypress/support/dataCyType.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ declare namespace DataCy {
"account-security-set-password-instructions-sent" |
"active-plan-license-key-input" |
"activity-compact" |
"activity-compact-detail-button" |
"activity-detail" |
"activity-detail-dialog" |
"add-box" |
"administration-access-message" |
"administration-cloud-plan-field-auto-assign-to-selected" |
Expand Down
15 changes: 15 additions & 0 deletions webapp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"react-google-recaptcha-v3": "1.9.5",
"react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0",
"react-intersection-observer": "^9.10.1",
"react-list": "^0.8.17",
"react-markdown": "^8.0.4",
"react-qr-code": "^2.0.7",
Expand Down
1 change: 1 addition & 0 deletions webapp/src/ThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ const getTheme = (mode: PaletteMode) => {
tokens: c.tokens,
placeholders: c.placeholders,
languageChips: c.languageChips,
revisionFilterBanner: c.revisionFilterBanner,
},
mixins: {
toolbar: {
Expand Down
13 changes: 13 additions & 0 deletions webapp/src/colors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ export type ExampleBanner = {
border: string;
};

export type RevisionFilterBanner = {
background: string;
highlightText: string;
};

export type TipsBanner = {
background: string;
};
Expand Down Expand Up @@ -197,6 +202,10 @@ export const colors = {
mainText: '#004437',
linkText: '#009B85',
},
revisionFilterBanner: {
background: '#00AF9A14',
highlightText: '#00AF9A',
} satisfies RevisionFilterBanner,
quickStart: {
highlight: '#F7F8FB',
circleNormal: '#E7EBF5',
Expand Down Expand Up @@ -321,6 +330,10 @@ export const colors = {
mainText: '#BEF4E9',
linkText: '#dddddd',
},
revisionFilterBanner: {
background: '#99E5D629',
highlightText: '#99E5D6',
} satisfies RevisionFilterBanner,
quickStart: {
highlight: '#233043',
circleNormal: '#2c3c52',
Expand Down
26 changes: 8 additions & 18 deletions webapp/src/component/activity/ActivityCompact/ActivityCompact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import { components } from 'tg.service/apiSchema.generated';
import { buildActivity } from '../activityTools';
import { ActivityTitle } from '../ActivityTitle';
import { ActivityFields } from './CompactFields';
import { useState } from 'react';
import { Field } from '../types';
import { ActivityModel, Field } from '../types';
import { ActivityUser } from '../ActivityUser';
import { ActivityDetailDialog } from '../ActivityDetail/ActivityDetailDialog';
import { actionsConfiguration } from '../configuration';

type ProjectActivityModel = components['schemas']['ProjectActivityModel'];
Expand Down Expand Up @@ -75,11 +73,11 @@ const StyledMoreIndicator = styled(Box)`
type Props = {
data: ProjectActivityModel;
diffEnabled: boolean;
onDetailOpen: (data: ActivityModel) => void;
};

export const ActivityCompact = ({ data, diffEnabled }: Props) => {
export const ActivityCompact = ({ data, diffEnabled, onDetailOpen }: Props) => {
const activity = useMemo(() => buildActivity(data, true), [data]);
const [detailOpen, setDetailOpen] = useState(false);

let fieldsNum = 0;

Expand All @@ -99,38 +97,30 @@ export const ActivityCompact = ({ data, diffEnabled }: Props) => {
});

return (
<StyledContainer>
<StyledContainer data-cy="activity-compact">
<StyledUser>
<ActivityUser item={data} onlyTime />
</StyledUser>
<StyledContent data-cy="activity-compact">
<StyledContent>
<ActivityTitle activity={activity} />
<ActivityFields fields={limitedFields} diffEnabled={diffEnabled} />
{fieldsNum > maxFields && (
<StyledMoreIndicator onClick={() => setDetailOpen(true)}>
<StyledMoreIndicator onClick={() => onDetailOpen(data)}>
...
</StyledMoreIndicator>
)}
{/* <pre>{JSON.stringify(data, null, 2)}</pre> */}
</StyledContent>
<StyledAction>
<IconButton
data-cy="activity-compact-detail-button"
className="showOnMouseOver"
size="small"
onClick={() => setDetailOpen(true)}
onClick={() => onDetailOpen(data)}
>
<MoreVert />
</IconButton>
</StyledAction>
{detailOpen && (
<ActivityDetailDialog
data={data}
initialDiffEnabled={diffEnabled}
open={detailOpen}
onClose={() => setDetailOpen(false)}
maxWidth="lg"
/>
)}
</StyledContainer>
);
};
95 changes: 92 additions & 3 deletions webapp/src/component/activity/ActivityDetail/ActivityDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@ import { styled, Box } from '@mui/material';

import { ActivityUser } from '../ActivityUser';
import { ActivityEntities } from './ActivityEntities';
import { Activity, ActivityModel } from '../types';
import {
Activity,
ActivityModel,
Entity,
EntityEnum,
EntityOptions,
} from '../types';
import { useApiInfiniteQuery } from 'tg.service/http/useQueryApi';
import { useProject } from 'tg.hooks/useProject';
import { useEffect, useMemo } from 'react';
import { buildEntity } from '../activityTools';
import { activityEntities } from '../activityEntities';
import { BoxLoading } from 'tg.component/common/BoxLoading';
import { useInView } from 'react-intersection-observer';

const StyledContainer = styled('div')`
display: grid;
Expand Down Expand Up @@ -34,13 +47,89 @@ type Props = {
diffEnabled: boolean;
};

export const ActivityDetail = ({ data, activity, diffEnabled }: Props) => {
export const ActivityDetail = ({ data, diffEnabled, activity }: Props) => {
const { ref, inView } = useInView({ rootMargin: '100px' });
const project = useProject();

const isBatch = !data.modifiedEntities;

const path = { projectId: project.id, revisionId: data.revisionId };
const query = { size: 40 };
const detailLoadable = useApiInfiniteQuery({
url: '/v2/projects/{projectId}/activity/revisions/{revisionId}/modified-entities',
method: 'get',
path,
query,
options: {
enabled: isBatch,
cacheTime: 0,
getNextPageParam: (lastPage) => {
if (
lastPage.page &&
lastPage.page.number! < lastPage.page.totalPages! - 1
) {
return {
path,
query: {
...query,
page: lastPage.page!.number! + 1,
},
};
} else {
return null;
}
},
},
});

useEffect(() => {
if (inView) {
detailLoadable.fetchNextPage();
}
}, [inView]);

const activityWithData = useMemo(() => {
const entities = detailLoadable.data?.pages.flatMap((p) => {
return p._embedded?.modifiedEntities
?.map((e) => {
const options = activityEntities[e.entityClass] as
| EntityOptions
| undefined;
if (options) {
return buildEntity(
e.entityClass as EntityEnum,
e,
options,
Object.keys(options?.fields || {})
);
}
})
.filter(Boolean) as Entity[];
});
return { ...activity, entities: entities ?? activity.entities };
}, [detailLoadable.data]);

return (
<StyledContainer data-cy="activity-detail">
<StyledUserWrapper>
<ActivityUser item={data} />
</StyledUserWrapper>
<ActivityEntities activity={activity} diffEnabled={diffEnabled} />
{detailLoadable.isLoading ? (
<BoxLoading />
) : (
<>
<ActivityEntities
activity={activityWithData}
diffEnabled={diffEnabled}
showAllReferences={isBatch}
/>
{detailLoadable.hasNextPage && (
<Box display="flex" justifyContent="center" mt={3} ref={ref}>
<BoxLoading />
</Box>
)}
</>
)}
</StyledContainer>
);
};
Loading

0 comments on commit 272d03f

Please sign in to comment.