Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add projects select to analytics page gf-348 #470

Merged
merged 18 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a9057c7
feat: add a select component to the analytics page gf-348
AnutkaGn Sep 20, 2024
ffcb2aa
Merge branch 'main' of https://github.com/BinaryStudioAcademy/bsa-202…
AnutkaGn Sep 20, 2024
99f9e4d
feat: add the ability to get activity logs by ID project on the backe…
AnutkaGn Sep 21, 2024
28f1f79
feat: add projects select to analytics page gf-348
AnutkaGn Sep 21, 2024
2c248dc
Merge branch 'main' of https://github.com/BinaryStudioAcademy/bsa-202…
AnutkaGn Sep 21, 2024
fed6b54
fix: remove from remove the project from the array dependencie in the…
AnutkaGn Sep 21, 2024
18080e5
feat: make queries is optional gf-348
AnutkaGn Sep 22, 2024
c0d6074
fix: change type and code style gf-348
AnutkaGn Sep 23, 2024
f591dff
fix: improve query gf-348
AnutkaGn Sep 23, 2024
710a066
Merge branch 'main' of https://github.com/BinaryStudioAcademy/bsa-202…
AnutkaGn Sep 23, 2024
94e6f79
fix: change folder structure gf-348
AnutkaGn Sep 23, 2024
19df425
Merge branch 'main' of https://github.com/BinaryStudioAcademy/bsa-202…
AnutkaGn Sep 23, 2024
4803f13
fix: change projects store gf-348
AnutkaGn Sep 23, 2024
6bfb4f4
Merge branch 'main' of https://github.com/BinaryStudioAcademy/bsa-202…
AnutkaGn Sep 23, 2024
1cea696
Merge branch 'main' of https://github.com/BinaryStudioAcademy/bsa-202…
AnutkaGn Sep 23, 2024
4a8cef3
fix: create a separate request to receive all projects gf-348
AnutkaGn Sep 23, 2024
99b9a0f
Merge branch 'main' of https://github.com/BinaryStudioAcademy/bsa-202…
AnutkaGn Sep 23, 2024
e7e231b
fix: delete route /all gf-348
AnutkaGn Sep 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,12 @@ class ActivityLogController extends BaseController {
query: ActivityLogQueryParameters;
}>,
): Promise<APIHandlerResponse> {
const { endDate, startDate } = options.query;
const { endDate, projectId, startDate } = options.query;

return {
payload: await this.activityLogService.findAll({
endDate,
projectId,
startDate,
}),
status: HTTPCode.OK,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,22 @@ class ActivityLogRepository implements Repository {

public async findAll({
endDate,
projectId,
startDate,
}: ActivityLogQueryParameters): Promise<{ items: ActivityLogEntity[] }> {
const activityLogs = await this.activityLogModel
const query = this.activityLogModel
.query()
.withGraphFetched("[gitEmail.contributor, project, createdByUser]")
.modifyGraph("gitEmail.contributor", (builder) => {
builder.select("id", "name");
})
.whereBetween("activity_logs.date", [startDate, endDate])
.orderBy("date");
.whereBetween("activity_logs.date", [startDate, endDate]);

if (projectId) {
query.where("activity_logs.projectId", projectId);
}

const activityLogs = await query.orderBy("date");

return {
items: activityLogs.map((activityLog) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,17 +133,23 @@ class ActivityLogService implements Service {

public async findAll({
endDate,
projectId,
startDate,
}: ActivityLogQueryParameters): Promise<ActivityLogGetAllAnalyticsResponseDto> {
const activityLogsEntities = await this.activityLogRepository.findAll({
endDate,
projectId,
startDate,
});

const activityLogs = activityLogsEntities.items.map((item) =>
item.toObject(),
);
const allContributors = await this.contributorService.findAll();

const allContributors = projectId
? await this.contributorService.findAllByProjectId(projectId)
: await this.contributorService.findAll();

const dateRange = getDateRange(startDate, endDate);

const INITIAL_COMMITS_NUMBER = 0;
Expand Down
10 changes: 2 additions & 8 deletions apps/backend/src/modules/projects/project.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,17 +227,11 @@ class ProjectController extends BaseController {
*/
private async findAll(
options: APIHandlerOptions<{
query: ProjectGetAllRequestDto;
query: object | ProjectGetAllRequestDto;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
query: object | ProjectGetAllRequestDto;
query: ProjectGetAllRequestDto;

specify type

}>,
): Promise<APIHandlerResponse> {
const { name, page, pageSize } = options.query;

return {
payload: await this.projectService.findAll({
name,
page: Number(page),
pageSize: Number(pageSize),
}),
payload: await this.projectService.findAll(options.query),
status: HTTPCode.OK,
};
}
Expand Down
25 changes: 19 additions & 6 deletions apps/backend/src/modules/projects/project.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,33 @@ class ProjectRepository implements Repository {
return item ? ProjectEntity.initialize(item) : null;
}

public async findAll({
name,
page,
pageSize,
}: ProjectGetAllRequestDto): Promise<PaginationResponseDto<ProjectEntity>> {
public async findAll(
parameters: object | ProjectGetAllRequestDto,
): Promise<PaginationResponseDto<ProjectEntity>> {
const query = this.projectModel
.query()
.orderBy("created_at", SortType.DESCENDING);

const { name, page, pageSize } = parameters as ProjectGetAllRequestDto;

if (name) {
query.whereILike("name", `%${name}%`);
}

const { results, total } = await query.page(page, pageSize);
let results;
let total;

if (page && pageSize) {
const { results: pageResults, total: pageTotal } = await query.page(
page,
pageSize,
);
results = pageResults;
total = pageTotal;
} else {
results = await query;
total = results.length;
}

return {
items: results.map((project) => ProjectEntity.initialize(project)),
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/modules/projects/project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class ProjectService implements Service {
}

public async findAll(
parameters: ProjectGetAllRequestDto,
parameters: object | ProjectGetAllRequestDto,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

): Promise<ProjectGetAllResponseDto> {
const { items, totalItems } =
await this.projectRepository.findAll(parameters);
Expand Down
4 changes: 3 additions & 1 deletion apps/frontend/src/libs/components/select/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import styles from "./styles.module.css";
type Properties<TFieldValues extends FieldValues, TOptionValue> = {
background?: "primary" | "secondary";
control: Control<TFieldValues, null>;
isClearable?: boolean;
isLabelHidden?: boolean;
isMulti?: boolean;
isSearchable?: boolean;
Expand All @@ -30,6 +31,7 @@ type Properties<TFieldValues extends FieldValues, TOptionValue> = {
const Select = <TFieldValues extends FieldValues, TOptionValue>({
background = "secondary",
control,
isClearable = false,
isLabelHidden = false,
isMulti = false,
isSearchable = false,
Expand Down Expand Up @@ -122,7 +124,7 @@ const Select = <TFieldValues extends FieldValues, TOptionValue>({
),
valueContainer: () => styles["value-container"] as string,
}}
isClearable={false}
isClearable={isClearable}
isMulti={isMulti}
isSearchable={isSearchable}
name={name}
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/modules/activity/activity-logs-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class ActivityLogApi extends BaseHTTPApi {
query: {
endDate: String(query.endDate),
startDate: String(query.startDate),
...(query.projectId && { projectId: String(query.projectId) }),
},
},
);
Expand Down
16 changes: 10 additions & 6 deletions apps/frontend/src/modules/projects/projects-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,22 @@ class ProjectApi extends BaseHTTPApi {
}

public async getAll(
query: ProjectGetAllRequestDto,
query: ProjectGetAllRequestDto | undefined,
): Promise<ProjectGetAllResponseDto> {
const queryParameters: Record<string, string> = {
...(query?.name && { name: query.name }),
...(query?.page !== undefined && { page: String(query.page) }),
...(query?.pageSize !== undefined && {
pageSize: String(query.pageSize),
}),
};

const response = await this.load(
this.getFullEndpoint(ProjectsApiPath.ROOT, {}),
{
hasAuth: true,
method: "GET",
query: {
name: query.name,
page: String(query.page),
pageSize: String(query.pageSize),
},
query: queryParameters,
},
);

Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/modules/projects/slices/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const getById = createAsyncThunk<

const loadAll = createAsyncThunk<
ProjectGetAllResponseDto,
ProjectGetAllRequestDto,
ProjectGetAllRequestDto | undefined,
AsyncThunkConfig
>(`${sliceName}/load-all`, async (query, { extra }) => {
const { projectApi } = extra;
Expand Down
6 changes: 3 additions & 3 deletions apps/frontend/src/modules/projects/slices/project.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ const { actions, name, reducer } = createSlice({
});
builder.addCase(loadAll.fulfilled, (state, action) => {
const { items, totalItems } = action.payload;
const { page } = action.meta.arg;

state.projects =
const page = action.meta.arg?.page;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const page = action.meta.arg?.page;
const { page } = action.meta.arg;

const projectsItems =
page === FIRST_PAGE ? items : [...state.projects, ...items];
state.projects = page ? projectsItems : items;
state.projectsTotalCount = totalItems;
state.dataStatus = DataStatus.FULFILLED;
});
Expand Down
41 changes: 35 additions & 6 deletions apps/frontend/src/pages/analytics/analytics.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { DateInput, Loader, PageLayout } from "~/libs/components/components.js";
import {
DateInput,
Loader,
PageLayout,
Select,
} from "~/libs/components/components.js";
import { DataStatus } from "~/libs/enums/enums.js";
import { subtractDays } from "~/libs/helpers/helpers.js";
import {
Expand All @@ -10,9 +15,11 @@ import {
useFormWatch,
} from "~/libs/hooks/hooks.js";
import { actions as activityLogActions } from "~/modules/activity/activity.js";
import { actions as projectActions } from "~/modules/projects/projects.js";

import { AnalyticsTable } from "./libs/components/components.js";
import { ANALYTICS_DATE_MAX_RANGE } from "./libs/constants/constants.js";
import { getProjectOptions } from "./libs/helpers/helpers.js";
import styles from "./styles.module.css";

const Analytics = (): JSX.Element => {
Expand All @@ -22,26 +29,34 @@ const Analytics = (): JSX.Element => {
const { activityLogs, dataStatus } = useAppSelector(
({ activityLogs }) => activityLogs,
);
const { projects } = useAppSelector(({ projects }) => projects);

useEffect(() => {
void dispatch(projectActions.loadAll());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to load these projects separately, specifically for analytics module, so it should be in analytics slice. Also on the backend side we need to split service method for pagination and without and the same for repository. Take a look at how it's done here #491

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did I understand correctly that I can make a separate request on the backend in order to get all the projects (without pagination)? And use it in the analytics slice? I thought it would be better that way, I asked, but I didn't get an answer then

Copy link
Collaborator

@liza-veis liza-veis Sep 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, let's make it as a separate request and keep it separately

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably missed your question in the chat

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything is fine, I hope it is correct now

}, [dispatch]);

const { control, handleSubmit, isDirty } = useAppForm({
defaultValues: {
dateRange: [
subtractDays(todayDate, ANALYTICS_DATE_MAX_RANGE),
todayDate,
] as [Date, Date],
project: null,
},
});

const dateRangeValue = useFormWatch({ control, name: "dateRange" });
const projectValue = useFormWatch({ control, name: "project" });

const handleLoadLogs = useCallback(
([startDate, endDate]: [Date, Date]) => {
([startDate, endDate]: [Date, Date], projectId?: null | number) => {
const formattedStartDate = startDate.toISOString();
const formattedEndDate = endDate.toISOString();

void dispatch(
activityLogActions.loadAll({
endDate: formattedEndDate,
projectId: projectId ?? undefined,
startDate: formattedStartDate,
}),
);
Expand All @@ -50,23 +65,25 @@ const Analytics = (): JSX.Element => {
);

useEffect(() => {
handleLoadLogs(dateRangeValue);
}, [dateRangeValue, handleLoadLogs]);
handleLoadLogs(dateRangeValue, projectValue);
}, [dateRangeValue, projectValue, handleLoadLogs]);

const handleFormSubmit = useCallback(
(event_?: React.BaseSyntheticEvent): void => {
void handleSubmit((formData) => {
handleLoadLogs(formData.dateRange);
handleLoadLogs(formData.dateRange, formData.project);
})(event_);
},
[handleLoadLogs, handleSubmit],
);

const projectOptions = getProjectOptions(projects);

useEffect(() => {
if (isDirty) {
handleFormSubmit();
}
}, [dateRangeValue, isDirty, handleFormSubmit]);
}, [dateRangeValue, projectValue, isDirty, handleFormSubmit]);

const isLoading =
dataStatus === DataStatus.IDLE || dataStatus === DataStatus.PENDING;
Expand All @@ -76,6 +93,18 @@ const Analytics = (): JSX.Element => {
<h1 className={styles["title"]}>Analytics</h1>
<section>
<form className={styles["filters-form"]} onSubmit={handleFormSubmit}>
<div className={styles["wraper-selector"]}>
GvoFor marked this conversation as resolved.
Show resolved Hide resolved
<Select
control={control}
isClearable
isLabelHidden
isSearchable
label="Select project"
name="project"
options={projectOptions}
placeholder="Select project"
/>
</div>
<DateInput
control={control}
maxDate={todayDate}
Expand Down
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Place helpers in their separate folders

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { type SelectOption } from "~/libs/types/select-option.type.js";
import { type ProjectGetAllItemResponseDto } from "~/modules/projects/projects.js";

const getProjectOptions = (
projects: ProjectGetAllItemResponseDto[],
): SelectOption<number>[] =>
projects.map((project) => ({
label: project.name,
value: project.id,
}));

export { getProjectOptions };
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { getAnalyticsColumns } from "./get-analytics-columns/get-analytics-columns.helper.jsx";
export { getAnalyticsRows } from "./get-analytics-rows/get-analytics-rows.helper.js";
export { getProjectOptions } from "./get-project-options.helper.js";
6 changes: 6 additions & 0 deletions apps/frontend/src/pages/analytics/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,11 @@

.filters-form {
display: flex;
gap: 12px;
align-items: center;
margin-top: 24px;
}

.wraper-selector {
min-width: 270px;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
type ActivityLogQueryParameters = {
endDate: string;
projectId?: number | undefined;
startDate: string;
};

Expand Down