diff --git a/app/assets/javascripts/admin/groups/index/constants.js b/app/assets/javascripts/admin/groups/index/constants.js index bda8c6faa3b7d7a8ffa2dd1f3a8301ad43bb09ae..2db4d0a805bdea47a9d15fbd37975720a7330c3f 100644 --- a/app/assets/javascripts/admin/groups/index/constants.js +++ b/app/assets/javascripts/admin/groups/index/constants.js @@ -1,7 +1,6 @@ import { get } from 'lodash'; import groupsEmptyStateIllustration from '@gitlab/svgs/dist/illustrations/empty-state/empty-groups-md.svg?url'; import { s__, __ } from '~/locale'; -import { joinPaths } from '~/lib/utils/url_utility'; import { SORT_LABEL_NAME, SORT_LABEL_CREATED, @@ -44,12 +43,11 @@ export const SORT_OPTIONS = [ const baseTab = { formatter: (groups) => formatGraphQLGroups(groups, (group) => { - const adminPath = joinPaths('/', gon.relative_url_root, '/admin/groups/', group.fullPath); const canAdminAllResources = get(group.userPermissions, 'adminAllResources', true); return { - avatarLabelLink: adminPath, - editPath: `${adminPath}/edit`, + avatarLabelLink: group.adminShowPath, + editPath: group.adminEditPath, availableActions: canAdminAllResources ? group.availableActions : [], }; }), @@ -97,7 +95,7 @@ export const INACTIVE_TAB = { export const ADMIN_GROUPS_TABS = [ACTIVE_TAB, INACTIVE_TAB]; -export const BASE_ROUTE = '/admin/groups'; +export const BASE_ROUTE = '/'; export const ADMIN_GROUPS_ROUTE_NAME = 'admin-groups'; diff --git a/app/assets/javascripts/admin/groups/index/graphql/fragments/admin_group.fragment.graphql b/app/assets/javascripts/admin/groups/index/graphql/fragments/admin_group.fragment.graphql index 2110cc12e45d9d83b6d454374ef4c93f35f89cb6..424f013367836b3152825d844072ac0c04666d07 100644 --- a/app/assets/javascripts/admin/groups/index/graphql/fragments/admin_group.fragment.graphql +++ b/app/assets/javascripts/admin/groups/index/graphql/fragments/admin_group.fragment.graphql @@ -1,6 +1,8 @@ # id field is requested by ~/graphql_shared/fragments/group.fragment.graphql # eslint-disable-next-line @graphql-eslint/require-selections fragment AdminGroup on Group { + adminShowPath + adminEditPath projectStatistics { storageSize } diff --git a/app/assets/javascripts/admin/groups/index/index.js b/app/assets/javascripts/admin/groups/index/index.js index 6aae1b0221f29c865852e17e02d7642079eab266..b36631ae8ea51304b676caaea6d4fb0e4d95d1a3 100644 --- a/app/assets/javascripts/admin/groups/index/index.js +++ b/app/assets/javascripts/admin/groups/index/index.js @@ -2,16 +2,17 @@ import Vue from 'vue'; import VueRouter from 'vue-router'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import routes from './routes'; import AdminGroupsApp from './components/app.vue'; Vue.use(VueRouter); -export const createRouter = () => { +export const createRouter = (basePath) => { const router = new VueRouter({ routes, mode: 'history', - base: gon.relative_url_root || '/', + base: basePath, }); return router; @@ -22,13 +23,19 @@ export const initAdminGroups = () => { if (!el) return false; + const { + dataset: { appData }, + } = el; + + const { basePath } = convertObjectPropsToCamelCase(JSON.parse(appData)); + const apolloProvider = new VueApollo({ defaultClient: createDefaultClient(), }); return new Vue({ el, - router: createRouter(), + router: createRouter(basePath), apolloProvider, name: 'AdminGroupsRoot', render(createElement) { diff --git a/app/assets/javascripts/admin/groups/index/routes.js b/app/assets/javascripts/admin/groups/index/routes.js index bf85baf95f9f45bf8bc3ca8bfe138e380021c02f..37b17637dd0238c5da446854c7608eeff1f7dce6 100644 --- a/app/assets/javascripts/admin/groups/index/routes.js +++ b/app/assets/javascripts/admin/groups/index/routes.js @@ -10,7 +10,7 @@ export default [ }, ...ADMIN_GROUPS_TABS.map(({ value }) => ({ name: value, - path: `${BASE_ROUTE}/${value}`, + path: `${BASE_ROUTE}${value}`, component: AdminGroupsApp, })), ]; diff --git a/app/assets/javascripts/admin/projects/index/constants.js b/app/assets/javascripts/admin/projects/index/constants.js index 80fb1ce7436fdf1a5b3dd48e7d8bf7171ad784d9..ab237eef9e240de5febb8994f3785c3d42c9fb78 100644 --- a/app/assets/javascripts/admin/projects/index/constants.js +++ b/app/assets/javascripts/admin/projects/index/constants.js @@ -6,7 +6,6 @@ import ResourceListsEmptyState, { TYPES, } from '~/vue_shared/components/resource_lists/empty_state.vue'; import { formatGraphQLProjects } from '~/vue_shared/components/projects_list/formatter'; -import { joinPaths } from '~/lib/utils/url_utility'; import { SORT_LABEL_CREATED, SORT_LABEL_NAME, @@ -67,12 +66,11 @@ const baseTab = { paginationType: PAGINATION_TYPE_KEYSET, formatter: (projects) => formatGraphQLProjects(projects, (project) => { - const adminPath = joinPaths('/', gon.relative_url_root, '/admin/projects', project.fullPath); const canAdminAllResources = get(project.userPermissions, 'adminAllResources', true); return { - editPath: `${adminPath}/edit`, - avatarLabelLink: adminPath, + editPath: project.adminEditPath, + avatarLabelLink: project.adminShowPath, availableActions: canAdminAllResources ? project.availableActions : [], }; }), @@ -107,7 +105,7 @@ export const INACTIVE_TAB = { export const ADMIN_PROJECTS_TABS = [ACTIVE_TAB, INACTIVE_TAB]; -export const BASE_ROUTE = '/admin/projects'; +export const BASE_ROUTE = '/'; export const ADMIN_PROJECTS_ROUTE_NAME = 'admin-projects'; export const FIRST_TAB_ROUTE_NAMES = [ADMIN_PROJECTS_ROUTE_NAME]; diff --git a/app/assets/javascripts/admin/projects/index/graphql/fragments/admin_project.fragment.graphql b/app/assets/javascripts/admin/projects/index/graphql/fragments/admin_project.fragment.graphql index dde94ae89f83bfe1d69353ef6ebb393c3505ef96..b9032c7ea47abb6991fdd48ba9a1fe3a18dcb9bb 100644 --- a/app/assets/javascripts/admin/projects/index/graphql/fragments/admin_project.fragment.graphql +++ b/app/assets/javascripts/admin/projects/index/graphql/fragments/admin_project.fragment.graphql @@ -5,6 +5,8 @@ fragment AdminProject on Project { adminAllResources } ... on Project { + adminShowPath + adminEditPath statistics { storageSize } diff --git a/app/assets/javascripts/admin/projects/index/index.js b/app/assets/javascripts/admin/projects/index/index.js index bd74043d50feb3d92c7e4606ede1eb775a63ad8a..3f4a816046bf5a5ffe4552e80225d8cf0d425921 100644 --- a/app/assets/javascripts/admin/projects/index/index.js +++ b/app/assets/javascripts/admin/projects/index/index.js @@ -8,11 +8,11 @@ import routes from './routes'; Vue.use(VueRouter); -export const createRouter = () => { +export const createRouter = (basePath) => { const router = new VueRouter({ routes, mode: 'history', - base: gon.relative_url_root || '/', + base: basePath, }); return router; @@ -27,7 +27,7 @@ export const initAdminProjects = () => { dataset: { appData }, } = el; - const { programmingLanguages } = convertObjectPropsToCamelCase(JSON.parse(appData)); + const { programmingLanguages, basePath } = convertObjectPropsToCamelCase(JSON.parse(appData)); const apolloProvider = new VueApollo({ defaultClient: createDefaultClient(), @@ -35,7 +35,7 @@ export const initAdminProjects = () => { return new Vue({ el, - router: createRouter(), + router: createRouter(basePath), apolloProvider, name: 'AdminProjectsRoot', render(createElement) { diff --git a/app/assets/javascripts/admin/projects/index/routes.js b/app/assets/javascripts/admin/projects/index/routes.js index 02647cbed73def632947ba1b932cfeefc5328bda..d17116b81047c3a4dfaf5223a7dd1c9851c5fcd8 100644 --- a/app/assets/javascripts/admin/projects/index/routes.js +++ b/app/assets/javascripts/admin/projects/index/routes.js @@ -10,7 +10,7 @@ export default [ }, ...ADMIN_PROJECTS_TABS.map(({ value }) => ({ name: value, - path: `${BASE_ROUTE}/${value}`, + path: `${BASE_ROUTE}${value}`, component: AdminProjectsApp, })), ]; diff --git a/ee/app/assets/javascripts/admin/groups/index/graphql/fragments/admin_group.fragment.graphql b/ee/app/assets/javascripts/admin/groups/index/graphql/fragments/admin_group.fragment.graphql index e100dbae9a8ca3deedf768f9cbee4c3ab8202e79..03a84fc47fcc42528b406914d144098e892f7167 100644 --- a/ee/app/assets/javascripts/admin/groups/index/graphql/fragments/admin_group.fragment.graphql +++ b/ee/app/assets/javascripts/admin/groups/index/graphql/fragments/admin_group.fragment.graphql @@ -1,6 +1,8 @@ # id field is requested by ~/graphql_shared/fragments/group.fragment.graphql # eslint-disable-next-line @graphql-eslint/require-selections fragment AdminGroup on Group { + adminShowPath + adminEditPath projectStatistics { storageSize } diff --git a/spec/frontend/admin/groups/index/components/app_spec.js b/spec/frontend/admin/groups/index/components/app_spec.js index d63f71761a762e7481e30c0af4d39e80e1858d37..52313cca02dac7c8ed0b7320ea548c0bff0f08ce 100644 --- a/spec/frontend/admin/groups/index/components/app_spec.js +++ b/spec/frontend/admin/groups/index/components/app_spec.js @@ -94,9 +94,7 @@ describe('AdminGroupsApp', () => { }); }); - it('renders relative URL that supports relative_url_root', async () => { - window.gon = { relative_url_root: '/gitlab' }; - + it('uses adminShowPath for avatar link', async () => { await createComponent({ mountFn: mountExtended, handlers: [[adminGroupsQuery, jest.fn().mockResolvedValue(adminGroupsGraphQlResponse)]], @@ -112,7 +110,29 @@ describe('AdminGroupsApp', () => { } = adminGroupsGraphQlResponse; expect(wrapper.findByRole('link', { name: expectedGroup.fullName }).attributes('href')).toBe( - `/gitlab/admin/groups/${expectedGroup.fullPath}`, + expectedGroup.adminShowPath, + ); + }); + + it('uses adminEditPath for edit link', async () => { + await createComponent({ + mountFn: mountExtended, + handlers: [[adminGroupsQuery, jest.fn().mockResolvedValue(adminGroupsGraphQlResponse)]], + }); + await waitForPromises(); + + const { + data: { + groups: { + nodes: [expectedGroup], + }, + }, + } = adminGroupsGraphQlResponse; + + await wrapper.findByRole('button', { name: 'Actions' }).trigger('click'); + + expect(wrapper.findByRole('link', { name: 'Edit' }).attributes('href')).toBe( + expectedGroup.adminEditPath, ); }); diff --git a/spec/frontend/admin/projects/index/components/app_spec.js b/spec/frontend/admin/projects/index/components/app_spec.js index c0d96ce30c4e38aebd549d222fdeaaa2101caa65..a9adf5fdf360e9f5ae762a4722d6b7452babbb55 100644 --- a/spec/frontend/admin/projects/index/components/app_spec.js +++ b/spec/frontend/admin/projects/index/components/app_spec.js @@ -123,9 +123,7 @@ describe('AdminProjectsApp', () => { expect(wrapper.findByRole('button', { name: 'Delete immediately' }).exists()).toBe(true); }); - it('renders relative URL that supports relative_url_root', async () => { - window.gon = { relative_url_root: '/gitlab' }; - + it('uses adminShowPath for avatar link', async () => { await createComponent({ mountFn: mountExtended, handlers: [[adminProjectsQuery, jest.fn().mockResolvedValue(adminProjectsGraphQlResponse)]], @@ -142,7 +140,29 @@ describe('AdminProjectsApp', () => { expect( wrapper.findByRole('link', { name: expectedProject.nameWithNamespace }).attributes('href'), - ).toBe(`/gitlab/admin/projects/${expectedProject.fullPath}`); + ).toBe(expectedProject.adminShowPath); + }); + + it('uses adminEditPath for edit link', async () => { + await createComponent({ + mountFn: mountExtended, + handlers: [[adminProjectsQuery, jest.fn().mockResolvedValue(adminProjectsGraphQlResponse)]], + }); + await waitForPromises(); + + const { + data: { + projects: { + nodes: [expectedProject], + }, + }, + } = adminProjectsGraphQlResponse; + + await wrapper.findByRole('button', { name: 'Actions' }).trigger('click'); + + expect(wrapper.findByRole('link', { name: 'Edit' }).attributes('href')).toBe( + expectedProject.adminEditPath, + ); }); it('uses keyset pagination', async () => {