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

feat: activity chart polish (#8665)

![image](https://github.com/user-attachments/assets/a97f5745-1300-473e-80af-54f0cfc985e1)
This commit is contained in:
Jaanus Sellin 2024-11-06 12:00:42 +02:00 committed by GitHub
parent ba72be6169
commit d6e722b7b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 33 additions and 21 deletions

View File

@ -5,22 +5,25 @@ import type { ProjectActivitySchema } from '../../../../openapi';
import { styled, Tooltip } from '@mui/material'; import { styled, Tooltip } from '@mui/material';
const StyledContainer = styled('div')(({ theme }) => ({ const StyledContainer = styled('div')(({ theme }) => ({
gap: theme.spacing(1), display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
gap: theme.spacing(2),
})); }));
const TitleContainer = styled('h4')({
margin: 0,
width: '100%',
});
type Output = { date: string; count: number; level: number }; type Output = { date: string; count: number; level: number };
export function transformData(inputData: ProjectActivitySchema): Output[] { export function transformData(inputData: ProjectActivitySchema): Output[] {
const resultMap: Record<string, number> = {}; const countArray = inputData.map((item) => item.count);
// Step 1: Count the occurrences of each date
inputData.forEach((item) => {
const formattedDate = new Date(item.date).toISOString().split('T')[0];
resultMap[formattedDate] = (resultMap[formattedDate] || 0) + 1;
});
// Step 2: Get all counts, sort them, and find the cut-off values for percentiles // Step 2: Get all counts, sort them, and find the cut-off values for percentiles
const counts = Object.values(resultMap).sort((a, b) => a - b); const counts = Object.values(countArray).sort((a, b) => a - b);
const percentile = (percent: number) => { const percentile = (percent: number) => {
const index = Math.floor((percent / 100) * counts.length); const index = Math.floor((percent / 100) * counts.length);
@ -43,13 +46,13 @@ export function transformData(inputData: ProjectActivitySchema): Output[] {
}; };
// Step 4: Convert the map back to an array and assign levels // Step 4: Convert the map back to an array and assign levels
return Object.entries(resultMap) return inputData
.map(([date, count]) => ({ .map(({ date, count }) => ({
date, date,
count, count,
level: calculateLevel(count), level: calculateLevel(count),
})) }))
.reverse(); // Optional: reverse the order if needed .reverse();
} }
export const ProjectActivity = () => { export const ProjectActivity = () => {
@ -64,10 +67,10 @@ export const ProjectActivity = () => {
const levelledData = transformData(data.activityCountByDate); const levelledData = transformData(data.activityCountByDate);
return ( return (
<StyledContainer> <>
{data.activityCountByDate.length > 0 ? ( {data.activityCountByDate.length > 0 ? (
<> <StyledContainer>
<span>Activity in project</span> <TitleContainer>Activity in project</TitleContainer>
<ActivityCalendar <ActivityCalendar
theme={explicitTheme} theme={explicitTheme}
data={levelledData} data={levelledData}
@ -81,10 +84,10 @@ export const ProjectActivity = () => {
</Tooltip> </Tooltip>
)} )}
/> />
</> </StyledContainer>
) : ( ) : (
<span>No activity</span> <span>No activity</span>
)} )}
</StyledContainer> </>
); );
}; };

View File

@ -409,7 +409,7 @@ class EventStore implements IEventStore {
})); }));
} }
async getProjectEventActivity( async getProjectRecentEventActivity(
project: string, project: string,
): Promise<ProjectActivitySchema> { ): Promise<ProjectActivitySchema> {
const result = await this.db('events') const result = await this.db('events')
@ -418,6 +418,11 @@ class EventStore implements IEventStore {
) )
.count('* AS count') .count('* AS count')
.where('project', project) .where('project', project)
.andWhere(
'created_at',
'>=',
this.db.raw("NOW() - INTERVAL '1 year'"),
)
.groupBy(this.db.raw("TO_CHAR(created_at::date, 'YYYY-MM-DD')")) .groupBy(this.db.raw("TO_CHAR(created_at::date, 'YYYY-MM-DD')"))
.orderBy('date', 'asc'); .orderBy('date', 'asc');

View File

@ -22,7 +22,7 @@ export class ProjectStatusService {
), ),
}, },
activityCountByDate: activityCountByDate:
await this.eventStore.getProjectEventActivity(projectId), await this.eventStore.getProjectRecentEventActivity(projectId),
}; };
} }
} }

View File

@ -47,5 +47,7 @@ export interface IEventStore
queryCount(operations: IQueryOperations[]): Promise<number>; queryCount(operations: IQueryOperations[]): Promise<number>;
setCreatedByUserId(batchSize: number): Promise<number | undefined>; setCreatedByUserId(batchSize: number): Promise<number | undefined>;
getEventCreators(): Promise<Array<{ id: number; name: string }>>; getEventCreators(): Promise<Array<{ id: number; name: string }>>;
getProjectEventActivity(project: string): Promise<ProjectActivitySchema>; getProjectRecentEventActivity(
project: string,
): Promise<ProjectActivitySchema>;
} }

View File

@ -18,7 +18,9 @@ class FakeEventStore implements IEventStore {
this.events = []; this.events = [];
} }
getProjectEventActivity(project: string): Promise<ProjectActivitySchema> { getProjectRecentEventActivity(
project: string,
): Promise<ProjectActivitySchema> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }