From c73de4893c897b012047e661e301162023ee89d3 Mon Sep 17 00:00:00 2001 From: slaveeks Date: Wed, 21 Jan 2026 19:57:37 +0300 Subject: [PATCH 1/3] feat(affected-users): added chart data --- src/api/events/index.ts | 50 +++++++-- src/api/events/queries.ts | 29 +++++ src/components/event/UsersAffected.vue | 44 ++++++++ src/store/modules/events/actionTypes.js | 5 + src/store/modules/events/index.ts | 137 +++++++++++++++++++----- src/types/events.ts | 5 + src/utils.ts | 1 - 7 files changed, 236 insertions(+), 35 deletions(-) diff --git a/src/api/events/index.ts b/src/api/events/index.ts index 1296e257a..a7b4798e7 100644 --- a/src/api/events/index.ts +++ b/src/api/events/index.ts @@ -6,7 +6,8 @@ import { QUERY_EVENT, QUERY_EVENT_REPETITIONS_PORTION, QUERY_PROJECT_DAILY_EVENTS, - QUERY_CHART_DATA + QUERY_CHART_DATA, + QUERY_AFFECTED_USERS_CHART_DATA } from './queries'; import * as api from '@/api'; import type { @@ -20,7 +21,7 @@ import { EventsSortOrder } from '@/types/events'; import type { User } from '@/types/user'; -import type { EventChartItem, ChartLine } from '@/types/chart'; +import type { ChartLine } from '@/types/chart'; import type { APIResponse } from '../../types/api'; /** @@ -82,8 +83,10 @@ export async function fetchDailyEventsPortion( response.errors.forEach(e => console.error(e)); } - return project?.dailyEventsPortion ?? { cursor: null, - dailyEventsPortion: [] }; + return project?.dailyEventsPortion ?? { + cursor: null, + dailyEventsPortion: [], + }; } /** @@ -96,8 +99,16 @@ export async function fetchDailyEventsPortion( */ export async function getRepetitionsPortion( projectId: string, originalEventId: string, limit: number, cursor?: string -): Promise> { +): Promise> { const response = await api.call(QUERY_EVENT_REPETITIONS_PORTION, { limit, projectId, @@ -151,8 +162,10 @@ export async function toggleEventMark(projectId: string, eventId: string, mark: * @param eventId - original event id * @param assignee - user id to assign */ -export async function updateAssignee(projectId: string, eventId: string, assignee: string): Promise<{ success: boolean; - record: User; }> { +export async function updateAssignee(projectId: string, eventId: string, assignee: string): Promise<{ + success: boolean; + record: User; +}> { return (await api.callOld(MUTATION_UPDATE_EVENT_ASSIGNEE, { input: { projectId, @@ -196,3 +209,24 @@ export async function fetchChartData( timezoneOffset, })).project.event.chartData; } + +/** + * Fetch data for affected users daily chart + * @param projectId - id of the project owning the event + * @param originalEventId - id of the original event + * @param days - how many days we need to fetch for displaying in chart + * @param timezoneOffset - user's local timezone + */ +export async function fetchAffectedUsersChartData( + projectId: string, + originalEventId: string, + days: number, + timezoneOffset: number +): Promise { + return (await api.callOld(QUERY_AFFECTED_USERS_CHART_DATA, { + projectId, + originalEventId, + days, + timezoneOffset, + })).project.event.affectedUsersChartData; +} diff --git a/src/api/events/queries.ts b/src/api/events/queries.ts index 1c7a7ab61..da9b8a259 100644 --- a/src/api/events/queries.ts +++ b/src/api/events/queries.ts @@ -103,6 +103,35 @@ export const QUERY_CHART_DATA = ` } `; +// language=GraphQL +/** + * Fetch data for affected users chart + * Display affected users count for few days + */ +export const QUERY_AFFECTED_USERS_CHART_DATA = ` + query EventAffectedUsersChartData ( + $projectId: ID! + $originalEventId: ID! + $days: Int! + $timezoneOffset: Int! + ) { + project(projectId: $projectId) { + event(eventId: $originalEventId, originalEventId: $originalEventId) { + affectedUsersChartData( + days: $days, + timezoneOffset: $timezoneOffset + ) { + label + data { + timestamp + count + } + } + } + } + } +`; + // language=GraphQL /** * GraphQL Mutation to mark event as visited diff --git a/src/components/event/UsersAffected.vue b/src/components/event/UsersAffected.vue index 016c4d683..155c7c77d 100644 --- a/src/components/event/UsersAffected.vue +++ b/src/components/event/UsersAffected.vue @@ -11,15 +11,27 @@ {{ $t('event.usersAffected.users', { n: event.usersAffected }) }} +
+
+ {{ $t('event.daily.lastTwoWeeks') }} +
+ +
diff --git a/src/store/modules/events/actionTypes.js b/src/store/modules/events/actionTypes.js index 82d0f49e1..39c92d91b 100644 --- a/src/store/modules/events/actionTypes.js +++ b/src/store/modules/events/actionTypes.js @@ -58,6 +58,11 @@ export const SET_EVENTS_FILTERS = 'SET_EVENTS_FILTERS'; */ export const GET_CHART_DATA = 'GET_CHART_DATA'; +/** + * Get affected users chart data for target event + */ +export const GET_AFFECTED_USERS_CHART_DATA = 'GET_AFFECTED_USERS_CHART_DATA'; + /** * Get list project with dailyEvents portion */ diff --git a/src/store/modules/events/index.ts b/src/store/modules/events/index.ts index 98200153c..0c0be9f9f 100644 --- a/src/store/modules/events/index.ts +++ b/src/store/modules/events/index.ts @@ -9,7 +9,8 @@ import { TOGGLE_EVENT_MARK, UPDATE_EVENT_ASSIGNEE, VISIT_EVENT, - GET_CHART_DATA + GET_CHART_DATA, + GET_AFFECTED_USERS_CHART_DATA } from './actionTypes'; import { RESET_STORE } from '../../methodsTypes'; import type { Module } from 'vuex'; @@ -26,7 +27,7 @@ import { EventsSortOrder } from '@/types/events'; import type { User } from '@/types/user'; -import type { EventChartItem } from '@/types/chart'; +import type { ChartLine } from '@/types/chart'; /** * Mutations enum for this module @@ -70,6 +71,11 @@ enum MutationTypes { */ SaveChartData = 'SAVE_CHART_DATA', + /** + * Get affected users chart data for an event for a few days + */ + SaveAffectedUsersChartData = 'SAVE_AFFECTED_USERS_CHART_DATA', + /** * Set project search */ @@ -253,12 +259,16 @@ const module: Module = { * @param payload.search - event searching regex string * @param payload.nextCursor - pointer to the first daily event of the portion to fetch */ - async [FETCH_PROJECT_OVERVIEW]({ commit }, { projectId, search, nextCursor, release }: { projectId: string; + async [FETCH_PROJECT_OVERVIEW]({ commit }, { projectId, search, nextCursor, release }: { + projectId: string; search: string; nextCursor: DailyEventsCursor | null; - release?: string; }): - Promise<{ dailyEventsWithEventsLinked: DailyEventWithEventLinked[]; - nextCursor: string | null; }> { + release?: string; + }): + Promise<{ + dailyEventsWithEventsLinked: DailyEventWithEventLinked[]; + nextCursor: string | null; + }> { const eventsSortOrder = this.getters.getProjectOrder(projectId); const dailyEventsPortion = await eventsApi.fetchDailyEventsPortion( projectId, @@ -294,8 +304,10 @@ const module: Module = { }; }); - return { dailyEventsWithEventsLinked, - nextCursor: dailyEventsPortion.nextCursor }; + return { + dailyEventsWithEventsLinked, + nextCursor: dailyEventsPortion.nextCursor, + }; }, /** @@ -311,12 +323,16 @@ const module: Module = { */ async [FETCH_EVENT_REPETITIONS]( { commit }, - { projectId, originalEventId, limit, cursor }: { projectId: string; + { projectId, originalEventId, limit, cursor }: { + projectId: string; originalEventId: string; limit: number; - cursor?: string; } - ): Promise<{ repetitions: HawkEvent[]; - nextCursor?: string; }> { + cursor?: string; + } + ): Promise<{ + repetitions: HawkEvent[]; + nextCursor?: string; + }> { const response = await eventsApi.getRepetitionsPortion(projectId, originalEventId, limit, cursor); let repetitions: HawkEvent[] = []; @@ -336,8 +352,10 @@ const module: Module = { eventsList: repetitions, }); - return { repetitions, - nextCursor }; + return { + repetitions, + nextCursor, + }; }, /** @@ -433,9 +451,11 @@ const module: Module = { * @param payload.eventId - event id * @param payload.assignee - user to assign to this event */ - async [UPDATE_EVENT_ASSIGNEE]({ commit }, { projectId, eventId, assignee }: { projectId: string; + async [UPDATE_EVENT_ASSIGNEE]({ commit }, { projectId, eventId, assignee }: { + projectId: string; eventId: string; - assignee: User; }): Promise { + assignee: User; + }): Promise { const event: HawkEvent = this.getters.getProjectEventById(projectId, eventId); const result = await eventsApi.updateAssignee(projectId, event.originalEventId, assignee.id); @@ -457,8 +477,10 @@ const module: Module = { * @param payload.projectId - project id * @param payload.eventId - event id */ - async [REMOVE_EVENT_ASSIGNEE]({ commit }, { projectId, eventId }: { projectId: string; - eventId: string; }): Promise { + async [REMOVE_EVENT_ASSIGNEE]({ commit }, { projectId, eventId }: { + projectId: string; + eventId: string; + }): Promise { const result = await eventsApi.removeAssignee(projectId, eventId); const event: HawkEvent = this.getters.getProjectEventById(projectId, eventId); @@ -541,6 +563,43 @@ const module: Module = { data: chartData, }); }, + + /** + * Get affected users chart data for an event for a specified period + * @param context - vuex action context + * @param context.commit - VueX commit method + * @param context.dispatch - Vuex dispatch method + * @param project - object of project data + * @param project.projectId - project's id + * @param project.eventId - event's id + * @param project.startDate - start date + * @param project.endDate - end date + * @param project.groupBy - grouping interval in minutes + */ + async [GET_AFFECTED_USERS_CHART_DATA]( + { commit }, + { projectId, eventId, originalEventId, days }: + { + projectId: string; + eventId: string; + originalEventId: string; + days: number; + } + ): Promise { + const timezoneOffset = (new Date()).getTimezoneOffset(); + const chartData = await eventsApi.fetchAffectedUsersChartData( + projectId, + originalEventId, + days, + timezoneOffset + ); + + commit(MutationTypes.SaveAffectedUsersChartData, { + projectId, + eventId, + data: chartData, + }); + }, }, mutations: { /** @@ -678,8 +737,10 @@ const module: Module = { * @param project.order - order to set * @param project.projectId - project to set order for */ - [SET_EVENTS_ORDER](state: EventsModuleState, { order, projectId }: { order: EventsSortOrder; - projectId: string; }): void { + [SET_EVENTS_ORDER](state: EventsModuleState, { order, projectId }: { + order: EventsSortOrder; + projectId: string; + }): void { if (!state.filters[projectId]) { state.filters[projectId] = {}; } @@ -694,8 +755,10 @@ const module: Module = { * @param project.filters - filters object to set * @param project.projectId - project to set filters for */ - [SET_EVENTS_FILTERS](state: EventsModuleState, { filters, projectId }: { filters: EventsFilters; - projectId: string; }): void { + [SET_EVENTS_FILTERS](state: EventsModuleState, { filters, projectId }: { + filters: EventsFilters; + projectId: string; + }): void { if (!state.filters[projectId]) { state.filters[projectId] = {}; } @@ -711,15 +774,35 @@ const module: Module = { * @param project.eventId - event ID * @param project.data - array of dots */ - [MutationTypes.SaveChartData](state: EventsModuleState, { projectId, eventId, data }: { projectId: string; + [MutationTypes.SaveChartData](state: EventsModuleState, { projectId, eventId, data }: { + projectId: string; eventId: string; - data: EventChartItem[]; }): void { + data: ChartLine[]; + }): void { const key = getEventsListKey(projectId, eventId); // const event = state.events[key]; state.events[key].chartData = data; }, + /** + * Save event's affected users chart data + * @param state - module state + * @param project - object for project data + * @param project.projectId - project ID + * @param project.eventId - event ID + * @param project.data - array of dots + */ + [MutationTypes.SaveAffectedUsersChartData](state: EventsModuleState, { projectId, eventId, data }: { + projectId: string; + eventId: string; + data: ChartLine[]; + }): void { + const key = getEventsListKey(projectId, eventId); + + state.events[key].affectedUsersChartData = data; + }, + /** * Resets module state * @param state - Vuex state @@ -735,8 +818,10 @@ const module: Module = { * @param payload.projectId - project id * @param payload.search - search string */ - [MutationTypes.SetProjectSearch](state: EventsModuleState, { projectId, search }: { projectId: string; - search: string; }): void { + [MutationTypes.SetProjectSearch](state: EventsModuleState, { projectId, search }: { + projectId: string; + search: string; + }): void { state.search[projectId] = search; }, }, diff --git a/src/types/events.ts b/src/types/events.ts index fff927d55..c648b0ec4 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -130,6 +130,11 @@ export interface HawkEvent { */ chartData?: ChartLine[]; + /** + * Event affected users chart data for a few days + */ + affectedUsersChartData?: ChartLine[]; + /** * Event release */ diff --git a/src/utils.ts b/src/utils.ts index 0eb62cdd6..cceb53e9e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -591,4 +591,3 @@ export * from './utils/filters'; // Export async component utilities export * from './utils/loadAsyncComponent'; - From 103ef10a6ceb7ab0a3a50aa9c7ab062c5537ecb9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:34:06 +0000 Subject: [PATCH 2/3] Initial plan From a6d679f8d9af3ee4d056c9976165912b225fa970 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:37:29 +0000 Subject: [PATCH 3/3] fix(events): update JSDoc for GET_AFFECTED_USERS_CHART_DATA action Co-authored-by: n0str <988885+n0str@users.noreply.github.com> --- src/store/modules/events/index.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/store/modules/events/index.ts b/src/store/modules/events/index.ts index 0c0be9f9f..94d03579e 100644 --- a/src/store/modules/events/index.ts +++ b/src/store/modules/events/index.ts @@ -568,13 +568,11 @@ const module: Module = { * Get affected users chart data for an event for a specified period * @param context - vuex action context * @param context.commit - VueX commit method - * @param context.dispatch - Vuex dispatch method - * @param project - object of project data - * @param project.projectId - project's id - * @param project.eventId - event's id - * @param project.startDate - start date - * @param project.endDate - end date - * @param project.groupBy - grouping interval in minutes + * @param payload - object of payload data + * @param payload.projectId - project's id + * @param payload.eventId - event's id + * @param payload.originalEventId - original event's id + * @param payload.days - number of days to fetch chart data for */ async [GET_AFFECTED_USERS_CHART_DATA]( { commit },