diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/usage_overview.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/usage_overview.js index 0df75c29000aeeb81d615a1128cc7b580735c30c..76ece0dda076a85d4afd2aac667ace90945e717f 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/usage_overview.js +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/data_sources/usage_overview.js @@ -77,22 +77,17 @@ export const prepareQuery = (queryKeysToInclude = []) => { return Object.fromEntries(queryIncludeVariables); }; -const extractRootGroup = (requestPath = '') => { - const [rootGroup] = requestPath.split('/'); - return rootGroup; -}; - /** * Fetch usage overview metrics, making sure to only query * the top most group from the namespace. */ -export const fetch = async ({ namespace, query: { include = [] } }) => { +export const fetch = async ({ + rootNamespace: { requestPath: fullPath }, + query: { include = [] }, +}) => { const variableOverrides = prepareQuery(include); const { startDate, endDate } = USAGE_OVERVIEW_DEFAULT_DATE_RANGE; - const fullPath = extractRootGroup(namespace.requestPath); - if (!fullPath.length) return usageOverviewNoData; - try { const { data = {} } = await defaultClient.query({ query: getUsageOverviewQuery, diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/index.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/index.js index 8060fa8b4f66aeca8cf27697af3b4935088d4c63..b34c7640b4f37823b358aa1533fe143c20967e38 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/index.js +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/index.js @@ -40,6 +40,8 @@ export default () => { routerBase, features, availableVisualizations = '', + rootNamespaceName, + rootNamespaceFullPath, } = el.dataset; const analyticsDashboardPointer = buildAnalyticsDashboardPointer(analyticsDashboardPointerJSON); @@ -112,6 +114,8 @@ export default () => { analyticsSettingsPath, features: convertArrayToCamelCase(JSON.parse(features)), vsdAvailableVisualizations, + rootNamespaceName, + rootNamespaceFullPath, }, render(h) { return h(DashboardsApp); diff --git a/ee/app/assets/javascripts/vue_shared/components/customizable_dashboard/panels_base.vue b/ee/app/assets/javascripts/vue_shared/components/customizable_dashboard/panels_base.vue index af91eaa202cfdd69fa89610d81356f364ae6b1cf..e685bb66a3c29402bebe36e3741e6711d935eab4 100644 --- a/ee/app/assets/javascripts/vue_shared/components/customizable_dashboard/panels_base.vue +++ b/ee/app/assets/javascripts/vue_shared/components/customizable_dashboard/panels_base.vue @@ -49,7 +49,14 @@ export default { UsageOverview: () => import('ee/analytics/analytics_dashboards/components/visualizations/usage_overview.vue'), }, - inject: ['namespaceId', 'namespaceFullPath', 'namespaceName', 'isProject'], + inject: [ + 'namespaceId', + 'namespaceFullPath', + 'namespaceName', + 'isProject', + 'rootNamespaceName', + 'rootNamespaceFullPath', + ], props: { visualization: { type: Object, @@ -119,6 +126,13 @@ export default { 'Analytics|Something went wrong while connecting to your data source. See %{linkStart}troubleshooting documentation%{linkEnd}.', ); }, + rootNamespace() { + return { + name: this.rootNamespaceName, + requestPath: this.rootNamespaceFullPath, + isProject: false, + }; + }, namespace() { return { name: this.namespaceName, @@ -128,7 +142,7 @@ export default { }, panelTitle() { return convertToSnakeCase(this.visualization.type) === VISUALIZATION_USAGE_OVERVIEW - ? sprintf(VISUALIZATION_USAGE_TITLE, { namespaceName: this.namespace.name }) + ? sprintf(VISUALIZATION_USAGE_TITLE, { namespaceName: this.rootNamespace.name }) : this.title; }, }, @@ -157,6 +171,7 @@ export default { title: this.title, projectId: this.namespaceId, namespace: this.namespace, + rootNamespace: this.rootNamespace, query, queryOverrides, visualizationType: this.visualization.type, diff --git a/ee/app/helpers/analytics/analytics_dashboards_helper.rb b/ee/app/helpers/analytics/analytics_dashboards_helper.rb index af95074fee0036ecdc675216a8575ca7262f5152..7bfecff12b61eb432e877ac36722c06202baa877 100644 --- a/ee/app/helpers/analytics/analytics_dashboards_helper.rb +++ b/ee/app/helpers/analytics/analytics_dashboards_helper.rb @@ -21,7 +21,9 @@ def analytics_dashboards_list_app_data(namespace) namespace_name: namespace.name, namespace_full_path: namespace.full_path, features: is_project ? enabled_analytics_features(namespace).to_json : [].to_json, - router_base: router_base(namespace) + router_base: router_base(namespace), + root_namespace_name: namespace.root_ancestor.name, + root_namespace_full_path: namespace.root_ancestor.full_path } end diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/usage_overview_spec.js b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/usage_overview_spec.js index 4b85f52345815ca935a4c9326851c21e91bbf8a6..49c1217c955c70a55c87d04d585bb5d19fb55df9 100644 --- a/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/usage_overview_spec.js +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/data_sources/usage_overview_spec.js @@ -21,7 +21,7 @@ import { describe('Usage overview Data Source', () => { let obj; - const namespace = { name: 'cool namespace', requestPath: 'some-group-path' }; + const rootNamespace = { name: 'cool namespace', requestPath: 'some-group-path' }; const queryKeys = [USAGE_OVERVIEW_IDENTIFIER_GROUPS, USAGE_OVERVIEW_IDENTIFIER_MERGE_REQUESTS]; const mockQuery = { include: queryKeys }; const { group: mockGroupUsageMetricsQueryResponse } = mockUsageMetricsQueryResponse; @@ -84,19 +84,19 @@ describe('Usage overview Data Source', () => { describe('fetch', () => { it.each` - label | namespaceParam + label | namespace ${'with a project'} | ${{ requestPath: 'some-group/some-project' }} ${'with a sub group'} | ${{ requestPath: 'some-group/some-subgroup' }} ${'with a group'} | ${{ requestPath: 'some-group' }} - `('$label queries the top level group', async ({ namespaceParam }) => { + `('$label queries the top level group', async ({ namespace }) => { jest.spyOn(defaultClient, 'query').mockResolvedValue({ data: {} }); - obj = await fetch({ namespace: namespaceParam, query: mockQuery }); + obj = await fetch({ rootNamespace, namespace, query: mockQuery }); expect(defaultClient.query).toHaveBeenCalledWith( expect.objectContaining({ variables: { - fullPath: 'some-group', + fullPath: rootNamespace.requestPath, startDate: expect.anything(), endDate: expect.anything(), includeGroups: true, @@ -113,7 +113,7 @@ describe('Usage overview Data Source', () => { jest.spyOn(defaultClient, 'query').mockResolvedValue({ data: {} }); obj = await fetch({ - namespace, + rootNamespace, query: { include: [USAGE_OVERVIEW_IDENTIFIER_MERGE_REQUESTS] }, }); @@ -135,8 +135,8 @@ describe('Usage overview Data Source', () => { it.each` label | data | params - ${'with no data'} | ${{}} | ${{ namespace, query: mockQuery }} - ${'with no namespace.requestPath'} | ${mockUsageMetricsQueryResponse} | ${{ namespace: {}, query: mockQuery }} + ${'with no data'} | ${{}} | ${{ rootNamespace, query: mockQuery }} + ${'with no namespace.requestPath'} | ${mockUsageMetricsQueryResponse} | ${{ rootNamespace: {}, query: mockQuery }} `('$label returns the no data object', async ({ params }) => { jest.spyOn(defaultClient, 'query').mockResolvedValue({ data: {} }); @@ -149,7 +149,7 @@ describe('Usage overview Data Source', () => { beforeEach(() => { jest.spyOn(defaultClient, 'query').mockRejectedValue(); - obj = fetch({ namespace, query: mockQuery }); + obj = fetch({ rootNamespace, query: mockQuery }); }); it('returns the no data object', async () => { @@ -163,7 +163,7 @@ describe('Usage overview Data Source', () => { .spyOn(defaultClient, 'query') .mockResolvedValue({ data: mockUsageMetricsQueryResponse }); - obj = await fetch({ namespace, query: mockQuery }); + obj = await fetch({ rootNamespace, query: mockQuery }); }); it('will fetch the usage metrics', () => { diff --git a/ee/spec/frontend/vue_shared/components/customizable_dashboard/panels_base_spec.js b/ee/spec/frontend/vue_shared/components/customizable_dashboard/panels_base_spec.js index ef438227f0c60742420c1f810c915a70454bea11..fbff726ed3527463f3729bf71503a54f2d87dc2b 100644 --- a/ee/spec/frontend/vue_shared/components/customizable_dashboard/panels_base_spec.js +++ b/ee/spec/frontend/vue_shared/components/customizable_dashboard/panels_base_spec.js @@ -34,6 +34,8 @@ describe('PanelsBase', () => { namespaceId: '1', namespaceName: 'Namespace name', namespaceFullPath: 'namespace/full/path', + rootNamespaceName: 'Root namespace name', + rootNamespaceFullPath: 'namespace', isProject: true, }, propsData: { diff --git a/ee/spec/helpers/analytics/analytics_dashboards_helper_spec.rb b/ee/spec/helpers/analytics/analytics_dashboards_helper_spec.rb index 63b607b8132b4036bbedc0c227bcb9b38a172447..4883db483018fa1dd8c751817fad10b67a39191c 100644 --- a/ee/spec/helpers/analytics/analytics_dashboards_helper_spec.rb +++ b/ee/spec/helpers/analytics/analytics_dashboards_helper_spec.rb @@ -75,6 +75,8 @@ def expected_data(has_permission) analytics_settings_path: "/#{project.full_path}/-/settings/analytics#js-analytics-dashboards-settings", namespace_name: project.name, namespace_full_path: project.full_path, + root_namespace_full_path: group.name, + root_namespace_name: group.full_path, features: (enabled && has_permission ? [:product_analytics] : []).to_json, router_base: '/-/analytics/dashboards' } @@ -111,6 +113,8 @@ def expected_data(collector_host) analytics_settings_path: "/groups/#{sub_group.full_path}/-/edit#js-analytics-dashboards-settings", namespace_name: sub_group.name, namespace_full_path: sub_group.full_path, + root_namespace_full_path: group.name, + root_namespace_name: group.full_path, features: [].to_json, router_base: "/groups/#{sub_group.full_path}/-/analytics/dashboards" } @@ -158,6 +162,8 @@ def expected_data(collector_host) analytics_settings_path: "/groups/#{group.full_path}/-/edit#js-analytics-dashboards-settings", namespace_name: group.name, namespace_full_path: group.full_path, + root_namespace_full_path: group.name, + root_namespace_name: group.full_path, features: [].to_json, router_base: "/groups/#{group.full_path}/-/analytics/dashboards" }