diff --git a/doc/user/analytics/img/enhanced_issue_analytics_v16_7.png b/doc/user/analytics/img/enhanced_issue_analytics_v16_7.png new file mode 100644 index 0000000000000000000000000000000000000000..519e56acaa5cf1db2c5501ce2ebfd8c8e308e89d Binary files /dev/null and b/doc/user/analytics/img/enhanced_issue_analytics_v16_7.png differ diff --git a/doc/user/analytics/img/issues_closed_analytics_v16_4.png b/doc/user/analytics/img/issues_closed_analytics_v16_4.png deleted file mode 100644 index 5e1fe4eaa8ccb0e05f53d9a91dfeb67e4b8a7458..0000000000000000000000000000000000000000 Binary files a/doc/user/analytics/img/issues_closed_analytics_v16_4.png and /dev/null differ diff --git a/doc/user/analytics/issue_analytics.md b/doc/user/analytics/issue_analytics.md index 8f29c008d751ad99d6b0f8005639a7d4d0954f0f..35771a26366c7ff3b5c80d245b9f0ba8d4a71e63 100644 --- a/doc/user/analytics/issue_analytics.md +++ b/doc/user/analytics/issue_analytics.md @@ -50,7 +50,7 @@ available. This feature is not ready for production use. Enhanced issue analytics display the additional metric "Issues closed", which represents the total number of resolved issues in your project over a selected period. You can use this metric to improve the overall turn-around time and value delivered to your customers. - + ## Drill into the information diff --git a/doc/user/group/issues_analytics/img/enhanced_issue_analytics_v16_7.png b/doc/user/group/issues_analytics/img/enhanced_issue_analytics_v16_7.png new file mode 100644 index 0000000000000000000000000000000000000000..519e56acaa5cf1db2c5501ce2ebfd8c8e308e89d Binary files /dev/null and b/doc/user/group/issues_analytics/img/enhanced_issue_analytics_v16_7.png differ diff --git a/doc/user/group/issues_analytics/img/issues_closed_analytics_v16_4.png b/doc/user/group/issues_analytics/img/issues_closed_analytics_v16_4.png deleted file mode 100644 index 5e1fe4eaa8ccb0e05f53d9a91dfeb67e4b8a7458..0000000000000000000000000000000000000000 Binary files a/doc/user/group/issues_analytics/img/issues_closed_analytics_v16_4.png and /dev/null differ diff --git a/doc/user/group/issues_analytics/index.md b/doc/user/group/issues_analytics/index.md index 4f1c7b4be7a4362f02d8bc6cb6be99f0ba6b8216..3dc3248ab6a8860079b74d8871f4daecb09edb87 100644 --- a/doc/user/group/issues_analytics/index.md +++ b/doc/user/group/issues_analytics/index.md @@ -50,7 +50,7 @@ available. This feature is not ready for production use. Enhanced issue analytics display the additional metric "Issues closed", which represents the total number of resolved issues in your group over a selected period. You can use this metric to improve the overall turn-around time and value delivered to your customers. - + ## Drill into the information diff --git a/ee/app/assets/javascripts/issues_analytics/components/total_issues_analytics_chart.vue b/ee/app/assets/javascripts/issues_analytics/components/total_issues_analytics_chart.vue index 73a3a2dd6bb40dcb2db61c1eea80e18e9e15c8c8..5c94d37b924e98c44054fbccb35121e06256fdf9 100644 --- a/ee/app/assets/javascripts/issues_analytics/components/total_issues_analytics_chart.vue +++ b/ee/app/assets/javascripts/issues_analytics/components/total_issues_analytics_chart.vue @@ -3,7 +3,7 @@ import { GlLoadingIcon, GlAlert } from '@gitlab/ui'; import { GlStackedColumnChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import * as Sentry from '~/sentry/sentry_browser_wrapper'; import { s__, n__, sprintf, __ } from '~/locale'; -import { isValidDate } from '~/lib/utils/datetime_utility'; +import { isValidDate, differenceInMonths } from '~/lib/utils/datetime_utility'; import { generateChartDateRangeData } from '../utils'; import issuesAnalyticsCountsQueryBuilder from '../graphql/issues_analytics_counts_query_builder'; import { extractIssuesAnalyticsCounts } from '../api'; @@ -162,27 +162,32 @@ export default { monthYearLabels() { return this.dates.map(({ month, year }) => `${month} ${year}`); }, + firstMonthYearLabel() { + return this.monthYearLabels[0]; + }, monthsCount() { - return this.dates.length; + return differenceInMonths(this.startDate, this.endDate); }, dateRange() { - const { monthYearLabels, monthsCount } = this; - - const [startMonthYear] = monthYearLabels; - - if (monthsCount === 1) return startMonthYear; + const { monthYearLabels, firstMonthYearLabel } = this; return sprintf(__('%{startDate} – %{dueDate}'), { - startDate: startMonthYear, + startDate: firstMonthYearLabel, dueDate: monthYearLabels.at(-1), }); }, xAxisTitle() { - const { monthsCount, dateRange } = this; + const { monthsCount, dateRange, firstMonthYearLabel } = this; + + if (monthsCount === 0) { + return sprintf(s__('IssuesAnalytics|This month (%{currentMonthYear})'), { + currentMonthYear: firstMonthYearLabel, + }); + } return sprintf( n__( - 'IssuesAnalytics|This month (%{dateRange})', + 'IssuesAnalytics|Last month (%{dateRange})', 'IssuesAnalytics|Last %{monthsCount} months (%{dateRange})', monthsCount, ), diff --git a/ee/spec/frontend/issues_analytics/components/total_issues_analytics_chart_spec.js b/ee/spec/frontend/issues_analytics/components/total_issues_analytics_chart_spec.js index af660c27130dfff54397d5dd36918bc282cb08ea..f01e568394680fcce5dac8bab3e7725478667b37 100644 --- a/ee/spec/frontend/issues_analytics/components/total_issues_analytics_chart_spec.js +++ b/ee/spec/frontend/issues_analytics/components/total_issues_analytics_chart_spec.js @@ -33,7 +33,21 @@ describe('TotalIssuesAnalyticsChart', () => { let mockApollo; const fullPath = 'toolbox'; - const mockGroupBy = ['Jul', 'Aug', 'Sep']; + const mockGroupBy = [ + 'Nov', + 'Dec', + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + ]; const queryError = jest.fn().mockRejectedValueOnce(new Error('Something went wrong')); const mockDataNullResponse = mockGraphqlIssuesAnalyticsCountsResponse({ mockDataResponse: null }); const issuesOpenedCountsSuccess = mockGraphqlIssuesAnalyticsCountsResponse({ @@ -123,7 +137,7 @@ describe('TotalIssuesAnalyticsChart', () => { presentation: 'tiled', groupBy: mockGroupBy, xAxisType: 'category', - xAxisTitle: 'Last 3 months (Jul 2023 – Sep 2023)', + xAxisTitle: 'Last 12 months (Nov 2022 – Nov 2023)', yAxisTitle: 'Issues Opened vs Closed', customPalette: TOTAL_ISSUES_ANALYTICS_CHART_COLOR_PALETTE, }); @@ -147,13 +161,19 @@ describe('TotalIssuesAnalyticsChart', () => { }); }); - it('should display correct x-axis title when date range is month to date', async () => { - const mockStartDate = new Date('2023-09-01T00:00:00.000Z'); - - await createComponent({ startDate: mockStartDate }); + it.each` + startDate | expectedXAxisTitle + ${new Date('2023-09-01T00:00:00.000Z')} | ${'Last 2 months (Sep 2023 – Nov 2023)'} + ${new Date('2023-10-01T00:00:00.000Z')} | ${'Last month (Oct 2023 – Nov 2023)'} + ${new Date('2023-11-01T00:00:00.000Z')} | ${'This month (Nov 2023)'} + `( + `should display the correct x-axis title when startDate=$startDate and endDate=${mockIssuesAnalyticsCountsEndDate}`, + async ({ startDate, expectedXAxisTitle }) => { + await createComponent({ startDate }); - expect(findTotalIssuesAnalyticsChart().props('xAxisTitle')).toBe('This month (Sep 2023)'); - }); + expect(findTotalIssuesAnalyticsChart().props('xAxisTitle')).toBe(expectedXAxisTitle); + }, + ); }); describe('when fetching data', () => { diff --git a/ee/spec/frontend/issues_analytics/mock_data.js b/ee/spec/frontend/issues_analytics/mock_data.js index ebc75402e475d554a812d85f430bc5ea9948c56f..4bf11315f47272542e6d5db20e24dd50c2d062ef 100644 --- a/ee/spec/frontend/issues_analytics/mock_data.js +++ b/ee/spec/frontend/issues_analytics/mock_data.js @@ -107,8 +107,8 @@ export const getQueryIssuesAnalyticsResponse = { }, }; -export const mockIssuesAnalyticsCountsStartDate = new Date('2023-07-04T00:00:00.000Z'); -export const mockIssuesAnalyticsCountsEndDate = new Date('2023-09-15T00:00:00.000Z'); +export const mockIssuesAnalyticsCountsStartDate = new Date('2022-11-01T00:00:00.000Z'); +export const mockIssuesAnalyticsCountsEndDate = new Date('2023-11-20T00:00:00.000Z'); export const getMockIssuesAnalyticsCountsQuery = ({ queryAlias, @@ -118,8 +118,128 @@ export const getMockIssuesAnalyticsCountsQuery = ({ namespace: ${isProject ? 'project' : 'group'}(fullPath: $fullPath) { id ${queryAlias}: flowMetrics { + Nov_2022: ${metricType}( + from: "2022-11-01" + to: "2022-12-01" + assigneeUsernames: $assigneeUsernames + authorUsername: $authorUsername + milestoneTitle: $milestoneTitle + labelNames: $labelNames + epicId: $epicId + iterationId: $iterationId + myReactionEmoji: $myReactionEmoji + weight: $weight + not: $not + ) { + value + } + Dec_2022: ${metricType}( + from: "2022-12-01" + to: "2023-01-01" + assigneeUsernames: $assigneeUsernames + authorUsername: $authorUsername + milestoneTitle: $milestoneTitle + labelNames: $labelNames + epicId: $epicId + iterationId: $iterationId + myReactionEmoji: $myReactionEmoji + weight: $weight + not: $not + ) { + value + } + Jan_2023: ${metricType}( + from: "2023-01-01" + to: "2023-02-01" + assigneeUsernames: $assigneeUsernames + authorUsername: $authorUsername + milestoneTitle: $milestoneTitle + labelNames: $labelNames + epicId: $epicId + iterationId: $iterationId + myReactionEmoji: $myReactionEmoji + weight: $weight + not: $not + ) { + value + } + Feb_2023: ${metricType}( + from: "2023-02-01" + to: "2023-03-01" + assigneeUsernames: $assigneeUsernames + authorUsername: $authorUsername + milestoneTitle: $milestoneTitle + labelNames: $labelNames + epicId: $epicId + iterationId: $iterationId + myReactionEmoji: $myReactionEmoji + weight: $weight + not: $not + ) { + value + } + Mar_2023: ${metricType}( + from: "2023-03-01" + to: "2023-04-01" + assigneeUsernames: $assigneeUsernames + authorUsername: $authorUsername + milestoneTitle: $milestoneTitle + labelNames: $labelNames + epicId: $epicId + iterationId: $iterationId + myReactionEmoji: $myReactionEmoji + weight: $weight + not: $not + ) { + value + } + Apr_2023: ${metricType}( + from: "2023-04-01" + to: "2023-05-01" + assigneeUsernames: $assigneeUsernames + authorUsername: $authorUsername + milestoneTitle: $milestoneTitle + labelNames: $labelNames + epicId: $epicId + iterationId: $iterationId + myReactionEmoji: $myReactionEmoji + weight: $weight + not: $not + ) { + value + } + May_2023: ${metricType}( + from: "2023-05-01" + to: "2023-06-01" + assigneeUsernames: $assigneeUsernames + authorUsername: $authorUsername + milestoneTitle: $milestoneTitle + labelNames: $labelNames + epicId: $epicId + iterationId: $iterationId + myReactionEmoji: $myReactionEmoji + weight: $weight + not: $not + ) { + value + } + Jun_2023: ${metricType}( + from: "2023-06-01" + to: "2023-07-01" + assigneeUsernames: $assigneeUsernames + authorUsername: $authorUsername + milestoneTitle: $milestoneTitle + labelNames: $labelNames + epicId: $epicId + iterationId: $iterationId + myReactionEmoji: $myReactionEmoji + weight: $weight + not: $not + ) { + value + } Jul_2023: ${metricType}( - from: "2023-07-04" + from: "2023-07-01" to: "2023-08-01" assigneeUsernames: $assigneeUsernames authorUsername: $authorUsername @@ -150,7 +270,37 @@ export const getMockIssuesAnalyticsCountsQuery = ({ } Sep_2023: ${metricType}( from: "2023-09-01" - to: "2023-09-15" + to: "2023-10-01" + assigneeUsernames: $assigneeUsernames + authorUsername: $authorUsername + milestoneTitle: $milestoneTitle + labelNames: $labelNames + epicId: $epicId + iterationId: $iterationId + myReactionEmoji: $myReactionEmoji + weight: $weight + not: $not + ) { + value + } + Oct_2023: ${metricType}( + from: "2023-10-01" + to: "2023-11-01" + assigneeUsernames: $assigneeUsernames + authorUsername: $authorUsername + milestoneTitle: $milestoneTitle + labelNames: $labelNames + epicId: $epicId + iterationId: $iterationId + myReactionEmoji: $myReactionEmoji + weight: $weight + not: $not + ) { + value + } + Nov_2023: ${metricType}( + from: "2023-11-01" + to: "2023-11-20" assigneeUsernames: $assigneeUsernames authorUsername: $authorUsername milestoneTitle: $milestoneTitle @@ -171,16 +321,56 @@ export const getMockIssuesAnalyticsCountsQuery = ({ export const getMockIssuesOpenedCountsResponse = ({ isProject = false, isEmpty = false } = {}) => ({ id: 'fake-id', issuesOpenedCounts: { + Nov_2022: { + value: isEmpty ? 0 : 18, + __typename: 'ValueStreamAnalyticsMetric', + }, + Dec_2022: { + value: isEmpty ? 0 : 38, + __typename: 'ValueStreamAnalyticsMetric', + }, + Jan_2023: { + value: isEmpty ? 0 : 51, + __typename: 'ValueStreamAnalyticsMetric', + }, + Feb_2023: { + value: isEmpty ? 0 : 39, + __typename: 'ValueStreamAnalyticsMetric', + }, + Mar_2023: { + value: isEmpty ? 0 : 45, + __typename: 'ValueStreamAnalyticsMetric', + }, + Apr_2023: { + value: isEmpty ? 0 : 40, + __typename: 'ValueStreamAnalyticsMetric', + }, + May_2023: { + value: isEmpty ? 0 : 44, + __typename: 'ValueStreamAnalyticsMetric', + }, + Jun_2023: { + value: isEmpty ? 0 : 44, + __typename: 'ValueStreamAnalyticsMetric', + }, Jul_2023: { - value: isEmpty ? 0 : 134, + value: isEmpty ? 0 : 34, __typename: 'ValueStreamAnalyticsMetric', }, Aug_2023: { - value: isEmpty ? 0 : 21, + value: isEmpty ? 0 : 48, __typename: 'ValueStreamAnalyticsMetric', }, Sep_2023: { - value: isEmpty ? 0 : 11, + value: isEmpty ? 0 : 40, + __typename: 'ValueStreamAnalyticsMetric', + }, + Oct_2023: { + value: isEmpty ? 0 : 39, + __typename: 'ValueStreamAnalyticsMetric', + }, + Nov_2023: { + value: isEmpty ? 0 : 20, __typename: 'ValueStreamAnalyticsMetric', }, __typename: isProject @@ -193,16 +383,56 @@ export const getMockIssuesOpenedCountsResponse = ({ isProject = false, isEmpty = export const getMockIssuesClosedCountsResponse = ({ isProject = false, isEmpty = false } = {}) => ({ id: 'fake-id', issuesClosedCounts: { + Nov_2022: { + value: isEmpty ? 0 : 0, + __typename: 'ValueStreamAnalyticsMetric', + }, + Dec_2022: { + value: isEmpty ? 0 : 0, + __typename: 'ValueStreamAnalyticsMetric', + }, + Jan_2023: { + value: isEmpty ? 0 : 1, + __typename: 'ValueStreamAnalyticsMetric', + }, + Feb_2023: { + value: isEmpty ? 0 : 3, + __typename: 'ValueStreamAnalyticsMetric', + }, + Mar_2023: { + value: isEmpty ? 0 : 4, + __typename: 'ValueStreamAnalyticsMetric', + }, + Apr_2023: { + value: isEmpty ? 0 : 9, + __typename: 'ValueStreamAnalyticsMetric', + }, + May_2023: { + value: isEmpty ? 0 : 13, + __typename: 'ValueStreamAnalyticsMetric', + }, + Jun_2023: { + value: isEmpty ? 0 : 12, + __typename: 'ValueStreamAnalyticsMetric', + }, Jul_2023: { - value: isEmpty ? 0 : 110, + value: isEmpty ? 0 : 14, __typename: 'ValueStreamAnalyticsMetric', }, Aug_2023: { - value: isEmpty ? 0 : 1, + value: isEmpty ? 0 : 21, __typename: 'ValueStreamAnalyticsMetric', }, Sep_2023: { - value: isEmpty ? 0 : 15, + value: isEmpty ? 0 : 24, + __typename: 'ValueStreamAnalyticsMetric', + }, + Oct_2023: { + value: isEmpty ? 0 : 45, + __typename: 'ValueStreamAnalyticsMetric', + }, + Nov_2023: { + value: isEmpty ? 0 : 60, __typename: 'ValueStreamAnalyticsMetric', }, __typename: isProject @@ -224,14 +454,8 @@ export const mockProjectIssuesAnalyticsCountsResponseData = getMockTotalIssuesAn ); export const mockIssuesAnalyticsCountsChartData = [ - { - name: 'Opened', - data: [134, 21, 11], - }, - { - name: 'Closed', - data: [110, 1, 15], - }, + { name: 'Opened', data: [18, 38, 51, 39, 45, 40, 44, 44, 34, 48, 40, 39, 20] }, + { name: 'Closed', data: [0, 0, 1, 3, 4, 9, 13, 12, 14, 21, 24, 45, 60] }, ]; export const mockChartDateRangeData = [ diff --git a/ee/spec/frontend/issues_analytics/utils_spec.js b/ee/spec/frontend/issues_analytics/utils_spec.js index 75c3cfade957a45185957e4b010163c655281633..9ac89f8c98b6b316add4dd8a36f6820c4136b26c 100644 --- a/ee/spec/frontend/issues_analytics/utils_spec.js +++ b/ee/spec/frontend/issues_analytics/utils_spec.js @@ -1,11 +1,5 @@ import { transformFilters, generateChartDateRangeData } from 'ee/issues_analytics/utils'; -import { - mockOriginalFilters, - mockFilters, - mockIssuesAnalyticsCountsStartDate, - mockIssuesAnalyticsCountsEndDate, - mockChartDateRangeData, -} from './mock_data'; +import { mockOriginalFilters, mockFilters, mockChartDateRangeData } from './mock_data'; describe('Issues Analytics utils', () => { describe('transformFilters', () => { @@ -46,8 +40,8 @@ describe('Issues Analytics utils', () => { }); describe('generateChartDateRangeData', () => { - const startDate = mockIssuesAnalyticsCountsStartDate; - const endDate = mockIssuesAnalyticsCountsEndDate; + const startDate = new Date('2023-07-04T00:00:00.000Z'); + const endDate = new Date('2023-09-15T00:00:00.000Z'); it('returns the data as expected', () => { const chartDateRangeData = generateChartDateRangeData(startDate, endDate); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c5e4593a9cb8310b63031a0e715fc20ab9003151..61eded1962564d6786923aa620712052da9d82a6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -26565,6 +26565,11 @@ msgstr "" msgid "IssuesAnalytics|Last 12 months (%{chartDateRange})" msgstr "" +msgid "IssuesAnalytics|Last month (%{dateRange})" +msgid_plural "IssuesAnalytics|Last %{monthsCount} months (%{dateRange})" +msgstr[0] "" +msgstr[1] "" + msgid "IssuesAnalytics|Opened" msgstr "" @@ -26574,10 +26579,8 @@ msgstr "" msgid "IssuesAnalytics|Sorry, your filter produced no results" msgstr "" -msgid "IssuesAnalytics|This month (%{dateRange})" -msgid_plural "IssuesAnalytics|Last %{monthsCount} months (%{dateRange})" -msgstr[0] "" -msgstr[1] "" +msgid "IssuesAnalytics|This month (%{currentMonthYear})" +msgstr "" msgid "IssuesAnalytics|To widen your search, change or remove filters in the filter bar above." msgstr ""