1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-04 00:18:01 +01:00

feat: add timestamps to project events (#8389)

This PR adds timestamps to project events and displays them in the
"latest events" box in the project details view.

It also changes the font weight of events to be only normal.


![image](https://github.com/user-attachments/assets/69ee4052-fe96-4fc9-ae45-0818acb0570a)
This commit is contained in:
Thomas Heartman 2024-10-09 09:32:58 +02:00 committed by GitHub
parent 5d61b3903b
commit f23ba70bb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 107 additions and 22 deletions

View File

@ -2,7 +2,9 @@ import type { FC } from 'react';
import { Markdown } from '../common/Markdown/Markdown';
import type { PersonalDashboardProjectDetailsSchema } from '../../openapi';
import { UserAvatar } from '../common/UserAvatar/UserAvatar';
import { styled } from '@mui/material';
import { Typography, styled } from '@mui/material';
import { formatDateYMDHM } from 'utils/formatDate';
import { useLocationSettings } from 'hooks/useLocationSettings';
const Events = styled('ul')(({ theme }) => ({
padding: 0,
@ -16,27 +18,72 @@ const Event = styled('li')(({ theme }) => ({
gap: theme.spacing(2),
alignItems: 'center',
marginBottom: theme.spacing(4),
'*': {
fontWeight: 'normal',
},
}));
const TitleContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
gap: theme.spacing(2),
alignItems: 'center',
}));
const ActionBox = styled('article')(({ theme }) => ({
padding: theme.spacing(0, 2),
display: 'flex',
gap: theme.spacing(3),
flexDirection: 'column',
}));
const Timestamp = styled('time')(({ theme }) => ({
color: theme.palette.text.secondary,
fontSize: theme.typography.fontSize,
marginBottom: theme.spacing(1),
}));
export const LatestProjectEvents: FC<{
latestEvents: PersonalDashboardProjectDetailsSchema['latestEvents'];
}> = ({ latestEvents }) => {
const { locationSettings } = useLocationSettings();
return (
<Events>
{latestEvents.map((event) => {
return (
<Event key={event.id}>
<UserAvatar
src={event.createdByImageUrl}
sx={{ mt: 1 }}
/>
<Markdown>
{event.summary ||
'No preview available for this event'}
</Markdown>
</Event>
);
})}
</Events>
<ActionBox>
<TitleContainer>
<Typography
sx={{
fontWeight: 'bold',
}}
component='h4'
>
Latest Events
</Typography>
</TitleContainer>
<Events>
{latestEvents.map((event) => {
return (
<Event key={event.id}>
<UserAvatar
src={event.createdByImageUrl}
sx={{ mt: 1 }}
/>
<div>
<Timestamp dateTime={event.createdAt}>
{formatDateYMDHM(
event.createdAt,
locationSettings.locale,
)}
</Timestamp>
<Markdown>
{event.summary ||
'No preview available for this event'}
</Markdown>
</div>
</Event>
);
})}
</Events>
</ActionBox>
);
};

View File

@ -22,4 +22,5 @@ export type PersonalDashboardProjectDetailsSchemaLatestEventsItem = {
* @nullable
*/
summary: string | null;
createdAt: string;
};

View File

@ -237,6 +237,8 @@ test('should return personal dashboard project details', async () => {
`/api/admin/personal-dashboard/${project.id}`,
);
const timestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
expect(body).toMatchObject({
owners: [
{
@ -268,24 +270,28 @@ test('should return personal dashboard project details', async () => {
},
latestEvents: [
{
createdAt: expect.stringMatching(timestampPattern),
createdBy: 'new_user@test.com',
summary: expect.stringContaining(
'**new_user@test.com** created **[log_feature_c]',
),
},
{
createdAt: expect.stringMatching(timestampPattern),
createdBy: 'new_user@test.com',
summary: expect.stringContaining(
'**new_user@test.com** created **[log_feature_b]',
),
},
{
createdAt: expect.stringMatching(timestampPattern),
createdBy: 'new_user@test.com',
summary: expect.stringContaining(
'**new_user@test.com** created **[log_feature_a]',
),
},
{
createdAt: expect.stringMatching(timestampPattern),
createdBy: 'unknown',
summary: expect.stringContaining(
'triggered **project-access-added**',

View File

@ -1,4 +1,9 @@
import { type IUnleashConfig, type IUnleashServices, NONE } from '../../types';
import {
type IUnleashConfig,
type IUnleashServices,
NONE,
serializeDates,
} from '../../types';
import type { OpenApiService } from '../../services';
import {
createResponseSchema,
@ -114,9 +119,9 @@ export default class PersonalDashboardController extends Controller {
200,
res,
personalDashboardProjectDetailsSchema.$id,
{
serializeDates({
...projectDetails,
},
}),
);
}
}

View File

@ -23,6 +23,19 @@ import type { PersonalDashboardProjectDetailsSchema } from '../../openapi';
import type { IRoleWithProject } from '../../types/stores/access-store';
import { NotFoundError } from '../../error';
type PersonalDashboardProjectDetailsUnserialized = Omit<
PersonalDashboardProjectDetailsSchema,
'latestEvents'
> & {
latestEvents: {
createdBy: string;
summary: string;
createdByImageUrl: string;
id: number;
createdAt: Date;
}[];
};
export class PersonalDashboardService {
private personalDashboardReadModel: IPersonalDashboardReadModel;
@ -105,7 +118,7 @@ export class PersonalDashboardService {
async getPersonalProjectDetails(
userId: number,
projectId: string,
): Promise<PersonalDashboardProjectDetailsSchema> {
): Promise<PersonalDashboardProjectDetailsUnserialized> {
const onboardingStatus =
await this.onboardingReadModel.getOnboardingStatusForProject(
projectId,
@ -119,6 +132,7 @@ export class PersonalDashboardService {
const formatEvents = (recentEvents: IEvent[]) =>
recentEvents.map((event) => ({
createdAt: event.createdAt,
summary: this.featureEventFormatter.format(event).text,
createdBy: event.createdBy,
id: event.id,

View File

@ -89,7 +89,13 @@ export const personalDashboardProjectDetailsSchema = {
type: 'object',
description: 'An event summary',
additionalProperties: false,
required: ['summary', 'createdBy', 'createdByImageUrl', 'id'],
required: [
'summary',
'createdBy',
'createdByImageUrl',
'id',
'createdAt',
],
properties: {
id: {
type: 'integer',
@ -112,6 +118,12 @@ export const personalDashboardProjectDetailsSchema = {
description: `URL used for the user profile image of the event author`,
example: 'https://example.com/242x200.png',
},
createdAt: {
type: 'string',
format: 'date-time',
description: 'When the event was recorded',
example: '2021-09-01T12:00:00Z',
},
},
},
},