diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-06 06:10:24 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-06 06:10:24 +0300 |
commit | 66089ccf1ab842eb5f7608502eaf8d2642e350b5 (patch) | |
tree | ac7da536104695d325c2fe9764b24524d3431213 | |
parent | 5eac1a5d627e9cdfa02b4dfd9d39d693c5ce65b8 (diff) |
Add latest changes from gitlab-org/gitlab@master
14 files changed, 167 insertions, 9 deletions
diff --git a/app/assets/javascripts/analytics/cycle_analytics/components/base.vue b/app/assets/javascripts/analytics/cycle_analytics/components/base.vue index a688e2f497b..a0416100e49 100644 --- a/app/assets/javascripts/analytics/cycle_analytics/components/base.vue +++ b/app/assets/javascripts/analytics/cycle_analytics/components/base.vue @@ -4,7 +4,7 @@ import { mapActions, mapState, mapGetters } from 'vuex'; import { getCookie, setCookie } from '~/lib/utils/common_utils'; import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue'; import { VSA_METRICS_GROUPS } from '~/analytics/shared/constants'; -import { toYmd } from '~/analytics/shared/utils'; +import { toYmd, generateValueStreamsDashboardLink } from '~/analytics/shared/utils'; import PathNavigation from '~/analytics/cycle_analytics/components/path_navigation.vue'; import StageTable from '~/analytics/cycle_analytics/components/stage_table.vue'; import ValueStreamFilters from '~/analytics/cycle_analytics/components/value_stream_filters.vue'; @@ -98,8 +98,22 @@ export default { } return 0; }, + hasCycleAnalyticsForGroups() { + return this.features?.cycleAnalyticsForGroups; + }, metricsRequests() { - return this.features?.cycleAnalyticsForGroups ? METRICS_REQUESTS : SUMMARY_METRICS_REQUEST; + return this.hasCycleAnalyticsForGroups ? METRICS_REQUESTS : SUMMARY_METRICS_REQUEST; + }, + showLinkToDashboard() { + return this.hasCycleAnalyticsForGroups && this.features?.groupAnalyticsDashboardsPage; + }, + dashboardsPath() { + const { + endpoints: { groupPath, fullPath }, + } = this; + return this.showLinkToDashboard + ? generateValueStreamsDashboardLink(groupPath, [fullPath]) + : null; }, query() { return { @@ -173,6 +187,7 @@ export default { :request-params="filterParams" :requests="metricsRequests" :group-by="$options.VSA_METRICS_GROUPS" + :dashboards-path="dashboardsPath" /> <gl-loading-icon v-if="isLoading" size="lg" /> <stage-table diff --git a/app/assets/javascripts/analytics/cycle_analytics/utils.js b/app/assets/javascripts/analytics/cycle_analytics/utils.js index 428bb11b950..0326aa1e70e 100644 --- a/app/assets/javascripts/analytics/cycle_analytics/utils.js +++ b/app/assets/javascripts/analytics/cycle_analytics/utils.js @@ -78,6 +78,7 @@ export const filterStagesByHiddenStatus = (stages = [], isHidden = true) => const extractFeatures = (gon) => ({ cycleAnalyticsForGroups: Boolean(gon?.licensed_features?.cycleAnalyticsForGroups), + groupAnalyticsDashboardsPage: Boolean(gon?.features?.groupAnalyticsDashboardsPage), }); /** diff --git a/app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue b/app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue index cc7b554f32c..f917248cd13 100644 --- a/app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue +++ b/app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue @@ -4,6 +4,7 @@ import { isEqual, keyBy } from 'lodash'; import { createAlert } from '~/flash'; import { sprintf, s__ } from '~/locale'; import { fetchMetricsData, removeFlash } from '../utils'; +import ValueStreamsDashboardLink from './value_streams_dashboard_link.vue'; import MetricTile from './metric_tile.vue'; const extractMetricsGroupData = (keyList = [], data = []) => { @@ -28,6 +29,7 @@ export default { components: { GlSkeletonLoader, MetricTile, + ValueStreamsDashboardLink, }, props: { requestPath: { @@ -52,6 +54,11 @@ export default { required: false, default: () => [], }, + dashboardsPath: { + type: String, + required: false, + default: null, + }, }, data() { return { @@ -76,6 +83,10 @@ export default { this.fetchData(); }, methods: { + shouldDisplayDashboardLink(index) { + // When we have groups of metrics, we should only display the link for the first group + return index === 0 && this.dashboardsPath; + }, fetchData() { removeFlash(); this.isLoading = true; @@ -110,7 +121,7 @@ export default { <template v-else> <div v-if="hasGroupedMetrics" class="gl-flex-direction-column"> <div - v-for="group in groupedMetrics" + v-for="(group, groupIndex) in groupedMetrics" :key="group.key" class="gl-mb-7" data-testid="vsa-metrics-group" @@ -123,6 +134,11 @@ export default { :metric="metric" class="gl-mt-5 gl-pr-10" /> + <value-streams-dashboard-link + v-if="shouldDisplayDashboardLink(groupIndex)" + class="gl-mt-5" + :request-path="dashboardsPath" + /> </div> </div> </div> diff --git a/app/assets/javascripts/analytics/shared/components/value_streams_dashboard_link.vue b/app/assets/javascripts/analytics/shared/components/value_streams_dashboard_link.vue new file mode 100644 index 00000000000..59c14f5e2ac --- /dev/null +++ b/app/assets/javascripts/analytics/shared/components/value_streams_dashboard_link.vue @@ -0,0 +1,30 @@ +<script> +import { GlIcon, GlLink } from '@gitlab/ui'; +import { __ } from '~/locale'; + +export default { + name: 'ValueStreamsDashboardLink', + components: { GlIcon, GlLink }, + props: { + requestPath: { + type: String, + required: true, + }, + }, + i18n: { + title: __('Related'), + linkText: __('Value Streams Dashboard | DORA'), + }, +}; +</script> +<template> + <div class="gl-display-flex gl-flex-direction-column"> + <div class="gl-display-flex gl-mb-2"> + <span>{{ $options.i18n.title }}</span> + </div> + <div class="gl-display-flex gl-align-items-baseline"> + <gl-link :href="requestPath">{{ $options.i18n.linkText }}</gl-link + > <gl-icon name="dashboard" /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/analytics/shared/utils.js b/app/assets/javascripts/analytics/shared/utils.js index aafbf642766..701cdcf0a2d 100644 --- a/app/assets/javascripts/analytics/shared/utils.js +++ b/app/assets/javascripts/analytics/shared/utils.js @@ -1,6 +1,7 @@ import { flatten } from 'lodash'; import dateFormat from '~/lib/dateformat'; import { slugify } from '~/lib/utils/text_utility'; +import { joinPaths } from '~/lib/utils/url_utility'; import { urlQueryToFilter } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; import { dateFormats, METRICS_POPOVER_CONTENT } from './constants'; @@ -119,3 +120,21 @@ export const fetchMetricsData = (requests = [], requestPath, params) => { prepareTimeMetricsData(flatten(responses), METRICS_POPOVER_CONTENT), ); }; + +/** + * Generates a URL link to the VSD dashboard based on the group + * and project paths passed into the method. + * + * @param {String} groupPath - Path of the specified group + * @param {Array} projectPaths - Array of project paths to include in the `query` parameter + * @returns a URL or blank string if there is no groupPath set + */ +export const generateValueStreamsDashboardLink = (groupPath, projectPaths = []) => { + if (groupPath.length) { + const query = projectPaths.length ? `?query=${projectPaths.join(',')}` : ''; + const dashboardsSlug = '/-/analytics/dashboards'; + const segments = [gon.relative_url_root || '', '/groups', groupPath, dashboardsSlug]; + return joinPaths(...segments).concat(query); + } + return ''; +}; diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb index dd2b3b8a067..e6f15eb05a5 100644 --- a/app/controllers/projects/cycle_analytics_controller.rb +++ b/app/controllers/projects/cycle_analytics_controller.rb @@ -22,6 +22,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController before_action do push_licensed_feature(:cycle_analytics_for_groups) if project.licensed_feature_available?(:cycle_analytics_for_groups) + push_frontend_feature_flag(:group_analytics_dashboards_page) end def show diff --git a/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md b/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md index 483c15d648c..6a41128a8d4 100644 --- a/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md +++ b/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md @@ -16,8 +16,8 @@ To detect the licenses in use, License Compliance relies on running the [Dependency Scanning CI Jobs](../../application_security/dependency_scanning/index.md), and analyzing the [CycloneDX](https://cyclonedx.org/) Software Bill of Materials (SBOM) generated by those jobs. Other 3rd party scanners may also be used as long as they produce a CycloneDX file with a list of dependencies for [one of our supported languages](#supported-languages-and-package-managers). -This method of scanning is also capable of parsing and identifying over 500 different types of licenses -and can extract license information from packages that are dual-licensed or have multiple different licenses that apply. +This method of scanning is also capable of parsing and identifying over 500 different types of licenses, as defined in [the SPDX list](https://spdx.org/licenses/). +Licenses not in the SPDX list are reported as "Unknown". License information can also be extracted from packages that are dual-licensed, or have multiple different licenses that apply. To enable license detection using Dependency Scanning in a project, include the `Jobs/Dependency-Scanning.yml` template in its CI configuration, diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d2d43c5a255..53b08377674 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -35719,6 +35719,9 @@ msgstr "" msgid "Relate to %{issuable_type} %{add_related_issue_link}" msgstr "" +msgid "Related" +msgstr "" + msgid "Related feature flags" msgstr "" @@ -47182,6 +47185,9 @@ msgstr "" msgid "Value Streams Dashboard (Beta)" msgstr "" +msgid "Value Streams Dashboard | DORA" +msgstr "" + msgid "Value might contain a variable reference" msgstr "" diff --git a/spec/frontend/analytics/cycle_analytics/base_spec.js b/spec/frontend/analytics/cycle_analytics/base_spec.js index 58588ff49ce..f27c3746c76 100644 --- a/spec/frontend/analytics/cycle_analytics/base_spec.js +++ b/spec/frontend/analytics/cycle_analytics/base_spec.js @@ -157,6 +157,10 @@ describe('Value stream analytics component', () => { expect(findPagination().exists()).toBe(true); }); + it('does not render a link to the value streams dashboard', () => { + expect(findOverviewMetrics().props('dashboardsPath')).toBeNull(); + }); + describe('with `cycleAnalyticsForGroups=true` license', () => { beforeEach(() => { wrapper = createComponent({ initialState: { features: { cycleAnalyticsForGroups: true } } }); @@ -167,6 +171,23 @@ describe('Value stream analytics component', () => { }); }); + describe('with `groupAnalyticsDashboardsPage=true` and `cycleAnalyticsForGroups=true` license', () => { + beforeEach(() => { + wrapper = createComponent({ + initialState: { + features: { groupAnalyticsDashboardsPage: true, cycleAnalyticsForGroups: true }, + }, + }); + }); + + it('renders a link to the value streams dashboard', () => { + expect(findOverviewMetrics().props('dashboardsPath')).toBeDefined(); + expect(findOverviewMetrics().props('dashboardsPath')).toBe( + '/groups/foo/-/analytics/dashboards?query=full/path/to/foo', + ); + }); + }); + describe('isLoading = true', () => { beforeEach(() => { wrapper = createComponent({ diff --git a/spec/frontend/analytics/cycle_analytics/utils_spec.js b/spec/frontend/analytics/cycle_analytics/utils_spec.js index fe412bf7498..a79abff1dff 100644 --- a/spec/frontend/analytics/cycle_analytics/utils_spec.js +++ b/spec/frontend/analytics/cycle_analytics/utils_spec.js @@ -164,7 +164,7 @@ describe('Value stream analytics utils', () => { ...rawData, gon: { licensed_features: fakeFeatures }, }); - expect(res.features).toEqual(fakeFeatures); + expect(res.features).toMatchObject(fakeFeatures); }); }); }); diff --git a/spec/frontend/analytics/cycle_analytics/value_stream_metrics_spec.js b/spec/frontend/analytics/cycle_analytics/value_stream_metrics_spec.js index 948dc5c9be2..b96580eeb2d 100644 --- a/spec/frontend/analytics/cycle_analytics/value_stream_metrics_spec.js +++ b/spec/frontend/analytics/cycle_analytics/value_stream_metrics_spec.js @@ -8,6 +8,7 @@ import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api'; import { VSA_METRICS_GROUPS, METRICS_POPOVER_CONTENT } from '~/analytics/shared/constants'; import { prepareTimeMetricsData } from '~/analytics/shared/utils'; import MetricTile from '~/analytics/shared/components/metric_tile.vue'; +import ValueStreamsDashboardLink from '~/analytics/shared/components/value_streams_dashboard_link.vue'; import { createAlert } from '~/flash'; import { group } from './mock_data'; @@ -37,6 +38,7 @@ describe('ValueStreamMetrics', () => { }); }; + const findVSDLink = () => wrapper.findComponent(ValueStreamsDashboardLink); const findMetrics = () => wrapper.findAllComponents(MetricTile); const findMetricsGroups = () => wrapper.findAllByTestId('vsa-metrics-group'); @@ -168,6 +170,25 @@ describe('ValueStreamMetrics', () => { }); }); + describe('Value Streams Dashboard Link', () => { + it('will render when a dashboardsPath is set', async () => { + wrapper = createComponent({ groupBy: VSA_METRICS_GROUPS, dashboardsPath: 'fake-group-path' }); + await waitForPromises(); + + const vsdLink = findVSDLink(); + + expect(vsdLink.exists()).toBe(true); + expect(vsdLink.props()).toEqual({ requestPath: 'fake-group-path' }); + }); + + it('does not render without a dashboardsPath', async () => { + wrapper = createComponent({ groupBy: VSA_METRICS_GROUPS }); + await waitForPromises(); + + expect(findVSDLink().exists()).toBe(false); + }); + }); + describe('with a request failing', () => { beforeEach(async () => { mockGetValueStreamSummaryMetrics = jest.fn().mockRejectedValue(); diff --git a/spec/frontend/analytics/shared/utils_spec.js b/spec/frontend/analytics/shared/utils_spec.js index b48e2d971b5..0b4346de9dc 100644 --- a/spec/frontend/analytics/shared/utils_spec.js +++ b/spec/frontend/analytics/shared/utils_spec.js @@ -5,6 +5,7 @@ import { extractPaginationQueryParameters, getDataZoomOption, prepareTimeMetricsData, + generateValueStreamsDashboardLink, } from '~/analytics/shared/utils'; import { slugify } from '~/lib/utils/text_utility'; import { objectToQuery } from '~/lib/utils/url_utility'; @@ -212,3 +213,30 @@ describe('prepareTimeMetricsData', () => { ]); }); }); + +describe('generateValueStreamsDashboardLink', () => { + it.each` + groupPath | projectPaths | result + ${''} | ${[]} | ${''} + ${'fake-group'} | ${[]} | ${'/groups/fake-group/-/analytics/dashboards'} + ${'fake-group'} | ${['fake-path/project_1']} | ${'/groups/fake-group/-/analytics/dashboards?query=fake-path/project_1'} + ${'fake-group'} | ${['fake-path/project_1', 'fake-path/project_2']} | ${'/groups/fake-group/-/analytics/dashboards?query=fake-path/project_1,fake-path/project_2'} + `( + 'generates the dashboard link when groupPath=$groupPath and projectPaths=$projectPaths', + ({ groupPath, projectPaths, result }) => { + expect(generateValueStreamsDashboardLink(groupPath, projectPaths)).toBe(result); + }, + ); + + describe('with a relative url rool set', () => { + beforeEach(() => { + gon.relative_url_root = '/foobar'; + }); + + it('with includes a relative path if one is set', () => { + expect(generateValueStreamsDashboardLink('fake-path', ['project_1'])).toBe( + '/foobar/groups/fake-path/-/analytics/dashboards?query=project_1', + ); + }); + }); +}); diff --git a/workhorse/go.mod b/workhorse/go.mod index c584ce4b310..6d0af2c69fe 100644 --- a/workhorse/go.mod +++ b/workhorse/go.mod @@ -7,7 +7,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/FZambia/sentinel v1.1.1 github.com/alecthomas/chroma/v2 v2.5.0 - github.com/aws/aws-sdk-go v1.44.208 + github.com/aws/aws-sdk-go v1.44.209 github.com/disintegration/imaging v1.6.2 github.com/getsentry/raven-go v0.2.0 github.com/golang-jwt/jwt/v4 v4.5.0 diff --git a/workhorse/go.sum b/workhorse/go.sum index db8289b93fc..2773a57eeed 100644 --- a/workhorse/go.sum +++ b/workhorse/go.sum @@ -544,8 +544,8 @@ github.com/aws/aws-sdk-go v1.43.11/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4 github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.128/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.151/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= -github.com/aws/aws-sdk-go v1.44.208 h1:xk1E2zWAIskrOP+huXuCYFR9ZdQWfTVid8Cjiwj2H1o= -github.com/aws/aws-sdk-go v1.44.208/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.209 h1:wZuiaA4eaqYZmoZXqGgNHqVD7y7kUGFvACDGBgowTps= +github.com/aws/aws-sdk-go v1.44.209/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.17.1 h1:02c72fDJr87N8RAC2s3Qu0YuvMRZKNZJ9F+lAehCazk= github.com/aws/aws-sdk-go-v2 v1.17.1/go.mod h1:JLnGeGONAyi2lWXI1p0PCIOIy333JMVK1U7Hf0aRFLw= |