Skip to content
Snippets Groups Projects
Commit f5b2618f authored by Florie Guibert's avatar Florie Guibert :cyclone:
Browse files

Merge branch...

Merge branch '406265-fe-detailed-visibility-storage-visibility-into-project-file-details' into 'master'

Adds links to storage entity pages on usage quota

See merge request gitlab-org/gitlab!119683



Merged-by: default avatarFlorie Guibert <fguibert@gitlab.com>
Approved-by: default avatarFlorie Guibert <fguibert@gitlab.com>
Approved-by: default avatarAlex Pennells <apennells@gitlab.com>
Approved-by: default avatarNick Brandt <nbrandt@gitlab.com>
Reviewed-by: default avatarKos Palchyk <kpalchyk@gitlab.com>
Reviewed-by: default avatarAlex Pennells <apennells@gitlab.com>
Co-authored-by: default avatarKos Palchyk <kpalchyk@gitlab.com>
parents a9dd61aa dc39b7e8
No related branches found
No related tags found
No related merge requests found
Showing
with 102 additions and 50 deletions
......@@ -82,7 +82,15 @@ export default {
/>
<div>
<p class="gl-font-weight-bold gl-mb-0" :data-testid="`${item.storageType.id}-name`">
{{ item.storageType.name }}
<gl-link
v-if="item.storageType.detailsPath && item.value"
:data-testid="`${item.storageType.id}-details-link`"
:href="item.storageType.detailsPath"
>{{ item.storageType.name }}</gl-link
>
<template v-else>
{{ item.storageType.name }}
</template>
<gl-link
v-if="item.storageType.helpPath"
:href="item.storageType.helpPath"
......
......@@ -14,10 +14,10 @@ export default {
iconName(storageTypeName) {
const defaultStorageTypeIcon = 'disk';
const storageTypeIconMap = {
lfsObjectsSize: 'doc-image',
snippetsSize: 'snippet',
repositorySize: 'infrastructure-registry',
packagesSize: 'package',
lfsObjects: 'doc-image',
snippets: 'snippet',
repository: 'infrastructure-registry',
packages: 'package',
};
return storageTypeIconMap[`${storageTypeName}`] ?? defaultStorageTypeIcon;
......
<script>
import { numberToHumanSize } from '~/lib/utils/number_utils';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { PROJECT_STORAGE_TYPES } from '../constants';
import { descendingStorageUsageSort } from '../utils';
export default {
mixins: [glFeatureFlagMixin()],
name: 'UsageGraph',
props: {
rootStorageStatistics: {
required: true,
......@@ -36,49 +35,49 @@ export default {
return [
{
id: 'repositorySize',
id: 'repository',
style: this.usageStyle(this.barRatio(repositorySize)),
class: 'gl-bg-data-viz-blue-500',
size: repositorySize,
},
{
id: 'lfsObjectsSize',
id: 'lfsObjects',
style: this.usageStyle(this.barRatio(lfsObjectsSize)),
class: 'gl-bg-data-viz-orange-600',
size: lfsObjectsSize,
},
{
id: 'packagesSize',
id: 'packages',
style: this.usageStyle(this.barRatio(packagesSize)),
class: 'gl-bg-data-viz-aqua-500',
size: packagesSize,
},
{
id: 'containerRegistrySize',
id: 'containerRegistry',
style: this.usageStyle(this.barRatio(containerRegistrySize)),
class: 'gl-bg-data-viz-aqua-800',
size: containerRegistrySize,
},
{
id: 'buildArtifactsSize',
id: 'buildArtifacts',
style: this.usageStyle(this.barRatio(buildArtifactsSize)),
class: 'gl-bg-data-viz-green-500',
size: buildArtifactsSize,
},
{
id: 'pipelineArtifactsSize',
id: 'pipelineArtifacts',
style: this.usageStyle(this.barRatio(pipelineArtifactsSize)),
class: 'gl-bg-data-viz-green-800',
size: pipelineArtifactsSize,
},
{
id: 'wikiSize',
id: 'wiki',
style: this.usageStyle(this.barRatio(wikiSize)),
class: 'gl-bg-data-viz-magenta-500',
size: wikiSize,
},
{
id: 'snippetsSize',
id: 'snippets',
style: this.usageStyle(this.barRatio(snippetsSize)),
class: 'gl-bg-data-viz-orange-800',
size: snippetsSize,
......
......@@ -26,44 +26,44 @@ export const PROJECT_TABLE_LABEL_USAGE = s__('UsageQuota|Usage');
export const PROJECT_STORAGE_TYPES = [
{
id: 'containerRegistrySize',
id: 'containerRegistry',
name: __('Container Registry'),
description: s__(
'UsageQuota|Gitlab-integrated Docker Container Registry for storing Docker Images.',
),
},
{
id: 'buildArtifactsSize',
id: 'buildArtifacts',
name: __('Job artifacts'),
description: s__('UsageQuota|Job artifacts created by CI/CD.'),
},
{
id: 'pipelineArtifactsSize',
id: 'pipelineArtifacts',
name: __('Pipeline artifacts'),
description: s__('UsageQuota|Pipeline artifacts created by CI/CD.'),
},
{
id: 'lfsObjectsSize',
id: 'lfsObjects',
name: __('LFS'),
description: s__('UsageQuota|Audio samples, videos, datasets, and graphics.'),
},
{
id: 'packagesSize',
id: 'packages',
name: __('Packages'),
description: s__('UsageQuota|Code packages and container images.'),
},
{
id: 'repositorySize',
id: 'repository',
name: __('Repository'),
description: s__('UsageQuota|Git repository.'),
},
{
id: 'snippetsSize',
id: 'snippets',
name: __('Snippets'),
description: s__('UsageQuota|Shared bits of code and text.'),
},
{
id: 'wikiSize',
id: 'wiki',
name: __('Wiki'),
description: s__('UsageQuota|Wiki content.'),
},
......
query getProjectStorageStatistics($fullPath: ID!) {
project(fullPath: $fullPath) {
id
statisticsDetailsPaths {
containerRegistry
buildArtifacts
packages
repository
snippets
wiki
}
statistics {
containerRegistrySize
buildArtifactsSize
......
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { PROJECT_STORAGE_TYPES } from './constants';
export const getStorageTypesFromProjectStatistics = (projectStatistics, helpLinks = {}) =>
export const getStorageTypesFromProjectStatistics = (
projectStatistics,
helpLinks = {},
statisticsDetailsPaths = {},
) =>
PROJECT_STORAGE_TYPES.reduce((types, currentType) => {
const helpPathKey = currentType.id.replace(`Size`, ``);
const helpPath = helpLinks[helpPathKey];
const helpPath = helpLinks[currentType.id];
const value = projectStatistics[`${currentType.id}Size`];
const detailsPath = statisticsDetailsPaths[currentType.id];
return types.concat({
storageType: {
...currentType,
helpPath,
detailsPath,
},
value: projectStatistics[currentType.id],
value,
});
}, []);
......@@ -27,7 +33,11 @@ export const parseGetProjectStorageResults = (data, helpLinks) => {
return {};
}
const { storageSize } = projectStatistics;
const storageTypes = getStorageTypesFromProjectStatistics(projectStatistics, helpLinks);
const storageTypes = getStorageTypesFromProjectStatistics(
projectStatistics,
helpLinks,
data?.project?.statisticsDetailsPaths,
);
return {
storage: {
......
......@@ -26,7 +26,7 @@ describe('ProjectStorageDetail', () => {
);
};
const generateStorageType = (id = 'buildArtifactsSize') => {
const generateStorageType = (id = 'buildArtifacts') => {
return {
storageType: {
id,
......@@ -56,7 +56,7 @@ describe('ProjectStorageDetail', () => {
expect(wrapper.findByTestId(`${id}-description`).text()).toBe(description);
expect(wrapper.findByTestId(`${id}-icon`).props('name')).toBe(id);
expect(wrapper.findByTestId(`${id}-help-link`).attributes('href')).toBe(
projectHelpLinks[id.replace(`Size`, ``)],
projectHelpLinks[id],
);
},
);
......@@ -74,6 +74,14 @@ describe('ProjectStorageDetail', () => {
});
});
describe('with details links', () => {
it.each(storageTypes)('each $storageType.id', (item) => {
const shouldExist = Boolean(item.storageType.detailsPath && item.value);
const detailsLink = wrapper.findByTestId(`${item.storageType.id}-details-link`);
expect(detailsLink.exists()).toBe(shouldExist);
});
});
describe('without storage types', () => {
beforeEach(() => {
createComponent({ storageTypes: [] });
......
......@@ -18,11 +18,11 @@ describe('StorageTypeIcon', () => {
describe('rendering icon', () => {
it.each`
expected | provided
${'doc-image'} | ${'lfsObjectsSize'}
${'snippet'} | ${'snippetsSize'}
${'infrastructure-registry'} | ${'repositorySize'}
${'package'} | ${'packagesSize'}
${'disk'} | ${'wikiSize'}
${'doc-image'} | ${'lfsObjects'}
${'snippet'} | ${'snippets'}
${'infrastructure-registry'} | ${'repository'}
${'package'} | ${'packages'}
${'disk'} | ${'wiki'}
${'disk'} | ${'anything-else'}
`(
'renders icon with name of $expected when name prop is $provided',
......
......@@ -9,25 +9,27 @@ export const projectData = {
storageTypes: [
{
storageType: {
id: 'containerRegistrySize',
id: 'containerRegistry',
name: 'Container Registry',
description: 'Gitlab-integrated Docker Container Registry for storing Docker Images.',
helpPath: '/container_registry',
detailsPath: 'http://localhost/frontend-fixtures/builds-project/container_registry',
},
value: 3_900_000,
value: 3900000,
},
{
storageType: {
id: 'buildArtifactsSize',
id: 'buildArtifacts',
name: 'Job artifacts',
description: 'Job artifacts created by CI/CD.',
helpPath: '/build-artifacts',
detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/artifacts',
},
value: 400000,
},
{
storageType: {
id: 'pipelineArtifactsSize',
id: 'pipelineArtifacts',
name: 'Pipeline artifacts',
description: 'Pipeline artifacts created by CI/CD.',
helpPath: '/pipeline-artifacts',
......@@ -36,7 +38,7 @@ export const projectData = {
},
{
storageType: {
id: 'lfsObjectsSize',
id: 'lfsObjects',
name: 'LFS',
description: 'Audio samples, videos, datasets, and graphics.',
helpPath: '/lsf-objects',
......@@ -45,37 +47,41 @@ export const projectData = {
},
{
storageType: {
id: 'packagesSize',
id: 'packages',
name: 'Packages',
description: 'Code packages and container images.',
helpPath: '/packages',
detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/packages',
},
value: 3800000,
},
{
storageType: {
id: 'repositorySize',
id: 'repository',
name: 'Repository',
description: 'Git repository.',
helpPath: '/repository',
detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/tree/master',
},
value: 3900000,
},
{
storageType: {
id: 'snippetsSize',
id: 'snippets',
name: 'Snippets',
description: 'Shared bits of code and text.',
helpPath: '/snippets',
detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/snippets',
},
value: 0,
},
{
storageType: {
id: 'wikiSize',
id: 'wiki',
name: 'Wiki',
description: 'Wiki content.',
helpPath: '/wiki',
detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/wikis/pages',
},
value: 300000,
},
......
......@@ -12,7 +12,10 @@ import {
} from './mock_data';
describe('getStorageTypesFromProjectStatistics', () => {
const projectStatistics = mockGetProjectStorageStatisticsGraphQLResponse.data.project.statistics;
const {
statistics: projectStatistics,
statisticsDetailsPaths,
} = mockGetProjectStorageStatisticsGraphQLResponse.data.project;
describe('matches project statistics value with matching storage type', () => {
const typesWithStats = getStorageTypesFromProjectStatistics(projectStatistics);
......@@ -22,29 +25,39 @@ describe('getStorageTypesFromProjectStatistics', () => {
storageType: expect.objectContaining({
id,
}),
value: projectStatistics[id],
value: projectStatistics[`${id}Size`],
});
});
});
it('adds helpPath to a relevant type', () => {
const trimTypeId = (id) => id.replace('Size', '');
const helpLinks = PROJECT_STORAGE_TYPES.reduce((acc, { id }) => {
const key = trimTypeId(id);
return {
...acc,
[key]: `url://${id}`,
[id]: `url://${id}`,
};
}, {});
const typesWithStats = getStorageTypesFromProjectStatistics(projectStatistics, helpLinks);
typesWithStats.forEach((type) => {
const key = trimTypeId(type.storageType.id);
const key = type.storageType.id;
expect(type.storageType.helpPath).toBe(helpLinks[key]);
});
});
it('adds details page path', () => {
const typesWithStats = getStorageTypesFromProjectStatistics(
projectStatistics,
{},
statisticsDetailsPaths,
);
typesWithStats.forEach((type) => {
expect(type.storageType.detailsPath).toBe(statisticsDetailsPaths[type.storageType.id]);
});
});
});
describe('parseGetProjectStorageResults', () => {
it('parses project statistics correctly', () => {
expect(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment