Commit 12aebfff authored by nicolasdular's avatar nicolasdular

Show usage graph for each storage type

Adds a graph to show the usage of different storage types of a
namespace.
parent ee8c4dbc
.storage-type-usage {
&:first-child {
@include gl-rounded-top-left-base;
@include gl-rounded-bottom-left-base;
}
&:last-child {
@include gl-rounded-top-right-base;
@include gl-rounded-bottom-right-base;
}
&:not(:first-child) {
@include gl-border-l-1;
@include gl-border-l-solid;
@include gl-border-white;
}
&:not(:last-child) {
@include gl-border-r-1;
@include gl-border-r-solid;
@include gl-border-white;
}
}
<script>
import { GlLink } from '@gitlab/ui';
import Project from './project.vue';
import UsageGraph from './usage_graph.vue';
import query from '../queries/storage.graphql';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import Icon from '~/vue_shared/components/icon.vue';
......@@ -10,6 +11,7 @@ export default {
Project,
GlLink,
Icon,
UsageGraph,
},
props: {
namespacePath: {
......@@ -41,6 +43,7 @@ export default {
data.namespace.rootStorageStatistics && data.namespace.rootStorageStatistics.storageSize
? numberToHumanSize(data.namespace.rootStorageStatistics.storageSize)
: 'N/A',
rootStorageStatistics: data.namespace.rootStorageStatistics,
}),
},
},
......@@ -53,23 +56,29 @@ export default {
</script>
<template>
<div>
<div class="pipeline-quota container-fluid">
<div class="row">
<div class="col-sm-6">
<strong>{{ s__('UsageQuota|Usage since') }}</strong>
<div>
<div class="pipeline-quota container-fluid py-4 px-2 m-0">
<div class="row py-0">
<div class="col-sm-12">
<strong>{{ s__('UsageQuota|Storage usage:') }}</strong>
<span class="js-total-usage">
{{ namespace.totalUsage }}
<gl-link
:href="helpPagePath"
target="_blank"
:aria-label="__('Usage quotas help link')"
:aria-label="s__('UsageQuota|Usage quotas help link')"
>
<icon name="question" :size="12" />
</gl-link>
</span>
</div>
</div>
<div class="row py-0">
<div class="col-sm-12">
<usage-graph
v-if="namespace.rootStorageStatistics"
:root-storage-statistics="namespace.rootStorageStatistics"
/>
</div>
</div>
</div>
<div class="ci-table" role="grid">
......
<script>
import { s__ } from '~/locale';
import { numberToHumanSize } from '~/lib/utils/number_utils';
export default {
props: {
rootStorageStatistics: {
required: true,
type: Object,
},
},
computed: {
storageTypes() {
const {
buildArtifactsSize,
lfsObjectsSize,
packagesSize,
repositorySize,
storageSize,
wikiSize,
} = this.rootStorageStatistics;
if (storageSize === 0) {
return null;
}
return [
{
name: s__('UsageQuota|Repositories'),
percentage: this.sizePercentage(repositorySize),
class: 'gl-bg-data-viz-blue-500',
size: repositorySize,
},
{
name: s__('UsageQuota|LFS Objects'),
percentage: this.sizePercentage(lfsObjectsSize),
class: 'gl-bg-data-viz-orange-600',
size: lfsObjectsSize,
},
{
name: s__('UsageQuota|Packages'),
percentage: this.sizePercentage(packagesSize),
class: 'gl-bg-data-viz-aqua-500',
size: packagesSize,
},
{
name: s__('UsageQuota|Build Artifacts'),
percentage: this.sizePercentage(buildArtifactsSize),
class: 'gl-bg-data-viz-green-600',
size: buildArtifactsSize,
},
{
name: s__('UsageQuota|Wikis'),
percentage: this.sizePercentage(wikiSize),
class: 'gl-bg-data-viz-magenta-500',
size: wikiSize,
},
]
.filter(data => data.size !== 0)
.sort((a, b) => b.size - a.size);
},
},
methods: {
formatSize(size) {
return numberToHumanSize(size);
},
sizePercentage(size) {
const { storageSize } = this.rootStorageStatistics;
return (size / storageSize) * 100;
},
},
};
</script>
<template>
<div v-if="storageTypes" class="gl-display-flex gl-flex-direction-column w-100">
<div class="gl-h-6 my-3">
<div
v-for="storageType in storageTypes"
:key="storageType.name"
class="storage-type-usage gl-h-full gl-display-inline-block"
:class="storageType.class"
:style="{ width: `${storageType.percentage}%` }"
></div>
</div>
<div class="row py-0">
<div
v-for="storageType in storageTypes"
:key="storageType.name"
class="col-md-auto gl-display-flex gl-align-items-center"
data-testid="storage-type"
>
<div class="gl-h-2 gl-w-5 gl-mr-2 gl-display-inline-block" :class="storageType.class"></div>
<span class="gl-mr-2 gl-font-weight-bold gl-font-sm">
{{ storageType.name }}
</span>
<span class="gl-text-gray-700 gl-font-sm">
{{ formatSize(storageType.size) }}
</span>
</div>
</div>
</div>
</template>
......@@ -3,6 +3,11 @@ query getStorageCounter($fullPath: ID!) {
id
rootStorageStatistics {
storageSize
repositorySize
lfsObjectsSize
buildArtifactsSize
packagesSize
wikiSize
}
projects(includeSubgroups: true) {
edges {
......
---
title: Show usage graph for each storage type
merge_request: 31649
author:
type: added
import { shallowMount } from '@vue/test-utils';
import UsageGraph from 'ee/storage_counter/components/usage_graph.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
const data = {
wikiSize: 5000,
repositorySize: 4000,
packagesSize: 3000,
lfsObjectsSize: 2000,
buildArtifactsSize: 1000,
storageSize: 15000,
};
let wrapper;
function mountComponent(rootStorageStatistics) {
wrapper = shallowMount(UsageGraph, {
propsData: {
rootStorageStatistics,
},
});
}
describe('Storage Counter usage graph component', () => {
beforeEach(() => {
mountComponent(data);
});
afterEach(() => {
wrapper.destroy();
});
it('renders the legend in order', () => {
const types = wrapper.findAll('[data-testid="storage-type"]');
expect(types.at(0).text()).toContain('Wikis');
expect(types.at(1).text()).toContain('Repositories');
expect(types.at(2).text()).toContain('Packages');
expect(types.at(3).text()).toContain('LFS Objects');
expect(types.at(4).text()).toContain('Build Artifacts');
});
it('renders formatted data in the legend', () => {
expect(wrapper.text()).toContain(numberToHumanSize(data.buildArtifactsSize));
expect(wrapper.text()).toContain(numberToHumanSize(data.lfsObjectsSize));
expect(wrapper.text()).toContain(numberToHumanSize(data.packagesSize));
expect(wrapper.text()).toContain(numberToHumanSize(data.repositorySize));
expect(wrapper.text()).toContain(numberToHumanSize(data.wikiSize));
});
describe('when storage type is not used', () => {
beforeEach(() => {
data.wikiSize = 0;
mountComponent(data);
});
it('filters the storage type', () => {
expect(wrapper.text()).not.toContain('Wikis');
});
});
describe('when there is no storage usage', () => {
beforeEach(() => {
mountComponent({ storageSize: 0 });
});
it('it does not render', () => {
expect(wrapper.html()).toEqual('');
});
});
});
......@@ -23515,9 +23515,6 @@ msgstr ""
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage quotas help link"
msgstr ""
msgid "Usage statistics"
msgstr ""
......@@ -23527,12 +23524,18 @@ msgstr ""
msgid "UsageQuota|Artifacts"
msgstr ""
msgid "UsageQuota|Build Artifacts"
msgstr ""
msgid "UsageQuota|Buy additional minutes"
msgstr ""
msgid "UsageQuota|Current period usage"
msgstr ""
msgid "UsageQuota|LFS Objects"
msgstr ""
msgid "UsageQuota|LFS Storage"
msgstr ""
......@@ -23542,12 +23545,18 @@ msgstr ""
msgid "UsageQuota|Pipelines"
msgstr ""
msgid "UsageQuota|Repositories"
msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
msgid "UsageQuota|Storage"
msgstr ""
msgid "UsageQuota|Storage usage:"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
msgstr ""
......@@ -23566,12 +23575,18 @@ msgstr ""
msgid "UsageQuota|Usage of resources across your projects"
msgstr ""
msgid "UsageQuota|Usage quotas help link"
msgstr ""
msgid "UsageQuota|Usage since"
msgstr ""
msgid "UsageQuota|Wiki"
msgstr ""
msgid "UsageQuota|Wikis"
msgstr ""
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment