summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/usage_quotas
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/usage_quotas')
-rw-r--r--app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue134
-rw-r--r--app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue142
-rw-r--r--app/assets/javascripts/usage_quotas/storage/components/storage_type_icon.vue35
-rw-r--r--app/assets/javascripts/usage_quotas/storage/components/usage_graph.vue170
-rw-r--r--app/assets/javascripts/usage_quotas/storage/constants.js100
-rw-r--r--app/assets/javascripts/usage_quotas/storage/init_project_storage.js34
-rw-r--r--app/assets/javascripts/usage_quotas/storage/queries/project_storage.query.graphql17
-rw-r--r--app/assets/javascripts/usage_quotas/storage/utils.js49
8 files changed, 681 insertions, 0 deletions
diff --git a/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue b/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue
new file mode 100644
index 00000000000..94bc15fa0d0
--- /dev/null
+++ b/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue
@@ -0,0 +1,134 @@
+<script>
+import { GlAlert, GlButton, GlLink, GlLoadingIcon } from '@gitlab/ui';
+import { sprintf } from '~/locale';
+import { updateRepositorySize } from '~/api/projects_api';
+import {
+ ERROR_MESSAGE,
+ LEARN_MORE_LABEL,
+ USAGE_QUOTAS_LABEL,
+ TOTAL_USAGE_TITLE,
+ TOTAL_USAGE_SUBTITLE,
+ TOTAL_USAGE_DEFAULT_TEXT,
+ HELP_LINK_ARIA_LABEL,
+ RECALCULATE_REPOSITORY_LABEL,
+ projectContainerRegistryPopoverContent,
+} from '../constants';
+import getProjectStorageStatistics from '../queries/project_storage.query.graphql';
+import { parseGetProjectStorageResults } from '../utils';
+import UsageGraph from './usage_graph.vue';
+import ProjectStorageDetail from './project_storage_detail.vue';
+
+export default {
+ name: 'ProjectStorageApp',
+ components: {
+ GlAlert,
+ GlButton,
+ GlLink,
+ GlLoadingIcon,
+ UsageGraph,
+ ProjectStorageDetail,
+ },
+ inject: ['projectPath', 'helpLinks'],
+ provide: {
+ containerRegistryPopoverContent: projectContainerRegistryPopoverContent,
+ },
+ apollo: {
+ project: {
+ query: getProjectStorageStatistics,
+ variables() {
+ return {
+ fullPath: this.projectPath,
+ };
+ },
+ update(data) {
+ return parseGetProjectStorageResults(data, this.helpLinks);
+ },
+ error() {
+ this.error = ERROR_MESSAGE;
+ },
+ },
+ },
+ data() {
+ return {
+ project: {},
+ error: '',
+ loadingRecalculateSize: false,
+ };
+ },
+ computed: {
+ totalUsage() {
+ return this.project?.storage?.totalUsage || TOTAL_USAGE_DEFAULT_TEXT;
+ },
+ storageTypes() {
+ return this.project?.storage?.storageTypes || [];
+ },
+ },
+ methods: {
+ clearError() {
+ this.error = '';
+ },
+ helpLinkAriaLabel(linkTitle) {
+ return sprintf(HELP_LINK_ARIA_LABEL, {
+ linkTitle,
+ });
+ },
+ async postRecalculateSize() {
+ const alertEl = document.querySelector('.js-recalculation-started-alert');
+
+ this.loadingRecalculateSize = true;
+
+ await updateRepositorySize(this.projectPath);
+
+ this.loadingRecalculateSize = false;
+ alertEl?.classList.remove('gl-display-none');
+ },
+ },
+ LEARN_MORE_LABEL,
+ USAGE_QUOTAS_LABEL,
+ TOTAL_USAGE_TITLE,
+ TOTAL_USAGE_SUBTITLE,
+ RECALCULATE_REPOSITORY_LABEL,
+};
+</script>
+<template>
+ <gl-loading-icon v-if="$apollo.queries.project.loading" class="gl-mt-5" size="lg" />
+ <gl-alert v-else-if="error" variant="danger" @dismiss="clearError">
+ {{ error }}
+ </gl-alert>
+ <div v-else>
+ <div class="gl-pt-5 gl-px-3">
+ <div class="gl-display-flex gl-justify-content-space-between gl-align-items-center">
+ <div>
+ <p class="gl-m-0 gl-font-lg gl-font-weight-bold">{{ $options.TOTAL_USAGE_TITLE }}</p>
+ <p class="gl-m-0 gl-text-gray-400">
+ {{ $options.TOTAL_USAGE_SUBTITLE }}
+ <gl-link
+ :href="helpLinks.usageQuotas"
+ target="_blank"
+ :aria-label="helpLinkAriaLabel($options.USAGE_QUOTAS_LABEL)"
+ data-testid="usage-quotas-help-link"
+ >
+ {{ $options.LEARN_MORE_LABEL }}
+ </gl-link>
+ </p>
+ </div>
+ <p class="gl-m-0 gl-font-size-h-display gl-font-weight-bold" data-testid="total-usage">
+ {{ totalUsage }}
+ </p>
+ </div>
+ </div>
+ <div v-if="project.statistics" class="gl-w-full">
+ <usage-graph :root-storage-statistics="project.statistics" :limit="0" />
+ </div>
+ <div class="gl-w-full gl-my-5">
+ <gl-button
+ :loading="loadingRecalculateSize"
+ category="secondary"
+ @click="postRecalculateSize"
+ >
+ {{ $options.RECALCULATE_REPOSITORY_LABEL }}
+ </gl-button>
+ </div>
+ <project-storage-detail :storage-types="storageTypes" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue b/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue
new file mode 100644
index 00000000000..2b97886e650
--- /dev/null
+++ b/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue
@@ -0,0 +1,142 @@
+<script>
+import { GlIcon, GlLink, GlSprintf, GlTableLite, GlPopover } from '@gitlab/ui';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import { thWidthPercent } from '~/lib/utils/table_utility';
+import { sprintf } from '~/locale';
+import {
+ HELP_LINK_ARIA_LABEL,
+ PROJECT_TABLE_LABEL_STORAGE_TYPE,
+ PROJECT_TABLE_LABEL_USAGE,
+ containerRegistryId,
+ containerRegistryPopoverId,
+ uploadsId,
+ uploadsPopoverId,
+ uploadsPopoverContent,
+} from '../constants';
+import { descendingStorageUsageSort } from '../utils';
+import StorageTypeIcon from './storage_type_icon.vue';
+
+export default {
+ name: 'ProjectStorageDetail',
+ components: {
+ GlLink,
+ GlIcon,
+ GlTableLite,
+ GlSprintf,
+ StorageTypeIcon,
+ GlPopover,
+ },
+ inject: ['containerRegistryPopoverContent'],
+ props: {
+ storageTypes: {
+ type: Array,
+ required: true,
+ },
+ },
+ computed: {
+ sizeSortedStorageTypes() {
+ const warnings = {
+ [containerRegistryId]: {
+ popoverId: containerRegistryPopoverId,
+ popoverContent: this.containerRegistryPopoverContent,
+ },
+ [uploadsId]: {
+ popoverId: uploadsPopoverId,
+ popoverContent: this.$options.i18n.uploadsPopoverContent,
+ },
+ };
+
+ return this.storageTypes
+ .map((type) => {
+ const warning = warnings[type.storageType.id] || null;
+ return {
+ warning,
+ ...type,
+ };
+ })
+ .sort(descendingStorageUsageSort('value'));
+ },
+ },
+ methods: {
+ helpLinkAriaLabel(linkTitle) {
+ return sprintf(HELP_LINK_ARIA_LABEL, {
+ linkTitle,
+ });
+ },
+ numberToHumanSize,
+ },
+ projectTableFields: [
+ {
+ key: 'storageType',
+ label: PROJECT_TABLE_LABEL_STORAGE_TYPE,
+ thClass: thWidthPercent(90),
+ },
+ {
+ key: 'value',
+ label: PROJECT_TABLE_LABEL_USAGE,
+ thClass: thWidthPercent(10),
+ },
+ ],
+ i18n: {
+ uploadsPopoverContent,
+ },
+};
+</script>
+<template>
+ <gl-table-lite :items="sizeSortedStorageTypes" :fields="$options.projectTableFields">
+ <template #cell(storageType)="{ item }">
+ <div class="gl-display-flex gl-flex-direction-row">
+ <storage-type-icon
+ :name="item.storageType.id"
+ :data-testid="`${item.storageType.id}-icon`"
+ />
+ <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.helpPath"
+ :href="item.storageType.helpPath"
+ target="_blank"
+ :aria-label="helpLinkAriaLabel(item.storageType.name)"
+ :data-testid="`${item.storageType.id}-help-link`"
+ >
+ <gl-icon name="question" :size="12" />
+ </gl-link>
+ </p>
+ <p class="gl-mb-0" :data-testid="`${item.storageType.id}-description`">
+ {{ item.storageType.description }}
+ </p>
+ <p v-if="item.storageType.warningMessage" class="gl-mb-0 gl-font-sm">
+ <gl-icon name="warning" :size="12" />
+ <gl-sprintf :message="item.storageType.warningMessage">
+ <template #warningLink="{ content }">
+ <gl-link :href="item.storageType.warningLink" target="_blank" class="gl-font-sm">{{
+ content
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </div>
+ </div>
+ </template>
+
+ <template #cell(value)="{ item }">
+ {{ numberToHumanSize(item.value, 1) }}
+
+ <template v-if="item.warning">
+ <gl-icon
+ :id="item.warning.popoverId"
+ name="warning"
+ class="gl-mt-2 gl-lg-mt-0 gl-lg-ml-2"
+ />
+ <gl-popover
+ triggers="hover focus"
+ placement="top"
+ :target="item.warning.popoverId"
+ :content="item.warning.popoverContent"
+ :data-testid="item.warning.popoverId"
+ />
+ </template>
+ </template>
+ </gl-table-lite>
+</template>
diff --git a/app/assets/javascripts/usage_quotas/storage/components/storage_type_icon.vue b/app/assets/javascripts/usage_quotas/storage/components/storage_type_icon.vue
new file mode 100644
index 00000000000..bc7cd42df1e
--- /dev/null
+++ b/app/assets/javascripts/usage_quotas/storage/components/storage_type_icon.vue
@@ -0,0 +1,35 @@
+<script>
+import { GlIcon } from '@gitlab/ui';
+
+export default {
+ components: { GlIcon },
+ props: {
+ name: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ methods: {
+ iconName(storageTypeName) {
+ const defaultStorageTypeIcon = 'disk';
+ const storageTypeIconMap = {
+ lfsObjectsSize: 'doc-image',
+ snippetsSize: 'snippet',
+ uploadsSize: 'upload',
+ repositorySize: 'infrastructure-registry',
+ packagesSize: 'package',
+ };
+
+ return storageTypeIconMap[`${storageTypeName}`] ?? defaultStorageTypeIcon;
+ },
+ },
+};
+</script>
+<template>
+ <span
+ class="gl-display-inline-flex gl-align-items-flex-start gl-justify-content-center gl-min-w-8 gl-pr-2 gl-pt-1"
+ >
+ <gl-icon :name="iconName(name)" :size="16" class="gl-mt-1" />
+ </span>
+</template>
diff --git a/app/assets/javascripts/usage_quotas/storage/components/usage_graph.vue b/app/assets/javascripts/usage_quotas/storage/components/usage_graph.vue
new file mode 100644
index 00000000000..7e001685060
--- /dev/null
+++ b/app/assets/javascripts/usage_quotas/storage/components/usage_graph.vue
@@ -0,0 +1,170 @@
+<script>
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
+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 {
+ components: {
+ GlIcon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [glFeatureFlagMixin()],
+ props: {
+ rootStorageStatistics: {
+ required: true,
+ type: Object,
+ },
+ limit: {
+ required: true,
+ type: Number,
+ },
+ },
+ computed: {
+ storageTypes() {
+ const {
+ containerRegistrySize,
+ buildArtifactsSize,
+ pipelineArtifactsSize,
+ lfsObjectsSize,
+ packagesSize,
+ repositorySize,
+ storageSize,
+ wikiSize,
+ snippetsSize,
+ uploadsSize,
+ } = this.rootStorageStatistics;
+ const artifactsSize = buildArtifactsSize + pipelineArtifactsSize;
+
+ if (storageSize === 0) {
+ return null;
+ }
+
+ return [
+ {
+ id: 'repositorySize',
+ style: this.usageStyle(this.barRatio(repositorySize)),
+ class: 'gl-bg-data-viz-blue-500',
+ size: repositorySize,
+ },
+ {
+ id: 'lfsObjectsSize',
+ style: this.usageStyle(this.barRatio(lfsObjectsSize)),
+ class: 'gl-bg-data-viz-orange-600',
+ size: lfsObjectsSize,
+ },
+ {
+ id: 'packagesSize',
+ style: this.usageStyle(this.barRatio(packagesSize)),
+ class: 'gl-bg-data-viz-aqua-500',
+ size: packagesSize,
+ },
+ {
+ id: 'containerRegistrySize',
+ style: this.usageStyle(this.barRatio(containerRegistrySize)),
+ class: 'gl-bg-data-viz-aqua-800',
+ size: containerRegistrySize,
+ },
+ {
+ id: 'buildArtifactsSize',
+ style: this.usageStyle(this.barRatio(artifactsSize)),
+ class: 'gl-bg-data-viz-green-600',
+ size: artifactsSize,
+ },
+ {
+ id: 'wikiSize',
+ style: this.usageStyle(this.barRatio(wikiSize)),
+ class: 'gl-bg-data-viz-magenta-500',
+ size: wikiSize,
+ },
+ {
+ id: 'snippetsSize',
+ style: this.usageStyle(this.barRatio(snippetsSize)),
+ class: 'gl-bg-data-viz-orange-800',
+ size: snippetsSize,
+ },
+ {
+ id: 'uploadsSize',
+ style: this.usageStyle(this.barRatio(uploadsSize)),
+ class: 'gl-bg-data-viz-aqua-700',
+ size: uploadsSize,
+ },
+ ]
+ .filter((data) => data.size !== 0)
+ .sort(descendingStorageUsageSort('size'))
+ .map((storageType) => {
+ const storageTypeExtraData = PROJECT_STORAGE_TYPES.find(
+ (type) => storageType.id === type.id,
+ );
+ const { name, tooltip } = storageTypeExtraData || {};
+
+ return {
+ name,
+ tooltip,
+ ...storageType,
+ };
+ });
+ },
+ },
+ methods: {
+ formatSize(size) {
+ return numberToHumanSize(size);
+ },
+ usageStyle(ratio) {
+ return { flex: ratio };
+ },
+ barRatio(size) {
+ let max = this.rootStorageStatistics.storageSize;
+
+ if (this.limit !== 0 && max <= this.limit) {
+ max = this.limit;
+ }
+
+ return size / max;
+ },
+ },
+};
+</script>
+<template>
+ <div v-if="storageTypes" class="gl-display-flex gl-flex-direction-column w-100">
+ <div class="gl-h-6 gl-my-5 gl-bg-gray-50 gl-rounded-base gl-display-flex">
+ <div
+ v-for="storageType in storageTypes"
+ :key="storageType.name"
+ class="storage-type-usage gl-h-full gl-display-inline-block"
+ :class="storageType.class"
+ :style="storageType.style"
+ data-testid="storage-type-usage"
+ ></div>
+ </div>
+ <div class="row gl-mb-4">
+ <div
+ v-for="storageType in storageTypes"
+ :key="storageType.name"
+ class="col-md-auto gl-display-flex gl-align-items-center"
+ data-testid="storage-type-legend"
+ data-qa-selector="storage_type_legend"
+ >
+ <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-500 gl-font-sm">
+ {{ formatSize(storageType.size) }}
+ </span>
+ <span
+ v-if="storageType.tooltip"
+ v-gl-tooltip
+ :title="storageType.tooltip"
+ :aria-label="storageType.tooltip"
+ class="gl-ml-2"
+ >
+ <gl-icon name="question" :size="12" />
+ </span>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/usage_quotas/storage/constants.js b/app/assets/javascripts/usage_quotas/storage/constants.js
new file mode 100644
index 00000000000..fab18cefc60
--- /dev/null
+++ b/app/assets/javascripts/usage_quotas/storage/constants.js
@@ -0,0 +1,100 @@
+import { s__, __ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
+
+export const ERROR_MESSAGE = s__(
+ 'UsageQuota|Something went wrong while fetching project storage statistics',
+);
+export const LEARN_MORE_LABEL = __('Learn more.');
+export const USAGE_QUOTAS_LABEL = s__('UsageQuota|Usage Quotas');
+export const TOTAL_USAGE_TITLE = s__('UsageQuota|Usage breakdown');
+export const TOTAL_USAGE_SUBTITLE = s__(
+ 'UsageQuota|Includes artifacts, repositories, wiki, uploads, and other items.',
+);
+export const TOTAL_USAGE_DEFAULT_TEXT = __('Not applicable.');
+export const HELP_LINK_ARIA_LABEL = s__('UsageQuota|%{linkTitle} help link');
+export const RECALCULATE_REPOSITORY_LABEL = s__('UsageQuota|Recalculate repository usage');
+
+export const projectContainerRegistryPopoverContent = s__(
+ 'UsageQuotas|The project-level storage statistics for the Container Registry are directional only and do not include savings for instance-wide deduplication.',
+);
+
+export const containerRegistryId = 'containerRegistrySize';
+export const containerRegistryPopoverId = 'container-registry-popover';
+export const uploadsId = 'uploadsSize';
+export const uploadsPopoverId = 'uploads-popover';
+export const uploadsPopoverContent = s__(
+ 'NamespaceStorage|Uploads are not counted in namespace storage quotas.',
+);
+
+export const PROJECT_TABLE_LABEL_PROJECT = __('Project');
+export const PROJECT_TABLE_LABEL_STORAGE_TYPE = s__('UsageQuota|Storage type');
+export const PROJECT_TABLE_LABEL_USAGE = s__('UsageQuota|Usage');
+
+export const PROJECT_STORAGE_TYPES = [
+ {
+ id: 'containerRegistrySize',
+ name: s__('UsageQuota|Container Registry'),
+ description: s__(
+ 'UsageQuota|Gitlab-integrated Docker Container Registry for storing Docker Images.',
+ ),
+ },
+ {
+ id: 'buildArtifactsSize',
+ name: s__('UsageQuota|Artifacts'),
+ description: s__('UsageQuota|Pipeline artifacts and job artifacts, created with CI/CD.'),
+ tooltip: s__('UsageQuota|Artifacts is a sum of build and pipeline artifacts.'),
+ },
+ {
+ id: 'lfsObjectsSize',
+ name: s__('UsageQuota|LFS storage'),
+ description: s__('UsageQuota|Audio samples, videos, datasets, and graphics.'),
+ },
+ {
+ id: 'packagesSize',
+ name: s__('UsageQuota|Packages'),
+ description: s__('UsageQuota|Code packages and container images.'),
+ },
+ {
+ id: 'repositorySize',
+ name: s__('UsageQuota|Repository'),
+ description: s__('UsageQuota|Git repository.'),
+ },
+ {
+ id: 'snippetsSize',
+ name: s__('UsageQuota|Snippets'),
+ description: s__('UsageQuota|Shared bits of code and text.'),
+ },
+ {
+ id: 'uploadsSize',
+ name: s__('UsageQuota|Uploads'),
+ description: s__('UsageQuota|File attachments and smaller design graphics.'),
+ },
+ {
+ id: 'wikiSize',
+ name: s__('UsageQuota|Wiki'),
+ description: s__('UsageQuota|Wiki content.'),
+ },
+];
+
+export const projectHelpPaths = {
+ containerRegistry: helpPagePath(
+ 'user/packages/container_registry/reduce_container_registry_storage',
+ ),
+ usageQuotas: helpPagePath('user/usage_quotas'),
+ usageQuotasNamespaceStorageLimit: helpPagePath('user/usage_quotas', {
+ anchor: 'namespace-storage-limit',
+ }),
+ buildArtifacts: helpPagePath('ci/pipelines/job_artifacts', {
+ anchor: 'when-job-artifacts-are-deleted',
+ }),
+ packages: helpPagePath('user/packages/package_registry/index.md', {
+ anchor: 'reduce-storage-usage',
+ }),
+ repository: helpPagePath('user/project/repository/reducing_the_repo_size_using_git'),
+ snippets: helpPagePath('user/snippets', {
+ anchor: 'reduce-snippets-repository-size',
+ }),
+ wiki: helpPagePath('administration/wikis/index.md', {
+ anchor: 'reduce-wiki-repository-size',
+ }),
+};
diff --git a/app/assets/javascripts/usage_quotas/storage/init_project_storage.js b/app/assets/javascripts/usage_quotas/storage/init_project_storage.js
new file mode 100644
index 00000000000..00cb274902d
--- /dev/null
+++ b/app/assets/javascripts/usage_quotas/storage/init_project_storage.js
@@ -0,0 +1,34 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import { projectHelpPaths as helpLinks } from './constants';
+import ProjectStorageApp from './components/project_storage_app.vue';
+
+Vue.use(VueApollo);
+
+export default (containerId = 'js-project-storage-count-app') => {
+ const el = document.getElementById(containerId);
+
+ if (!el) {
+ return false;
+ }
+
+ const { projectPath } = el.dataset;
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ return new Vue({
+ el,
+ apolloProvider,
+ name: 'ProjectStorageApp',
+ provide: {
+ projectPath,
+ helpLinks,
+ },
+ render(createElement) {
+ return createElement(ProjectStorageApp);
+ },
+ });
+};
diff --git a/app/assets/javascripts/usage_quotas/storage/queries/project_storage.query.graphql b/app/assets/javascripts/usage_quotas/storage/queries/project_storage.query.graphql
new file mode 100644
index 00000000000..6637e5e0865
--- /dev/null
+++ b/app/assets/javascripts/usage_quotas/storage/queries/project_storage.query.graphql
@@ -0,0 +1,17 @@
+query getProjectStorageStatistics($fullPath: ID!) {
+ project(fullPath: $fullPath) {
+ id
+ statistics {
+ containerRegistrySize
+ buildArtifactsSize
+ pipelineArtifactsSize
+ lfsObjectsSize
+ packagesSize
+ repositorySize
+ snippetsSize
+ storageSize
+ uploadsSize
+ wikiSize
+ }
+ }
+}
diff --git a/app/assets/javascripts/usage_quotas/storage/utils.js b/app/assets/javascripts/usage_quotas/storage/utils.js
new file mode 100644
index 00000000000..443788f650d
--- /dev/null
+++ b/app/assets/javascripts/usage_quotas/storage/utils.js
@@ -0,0 +1,49 @@
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import { PROJECT_STORAGE_TYPES } from './constants';
+
+export const getStorageTypesFromProjectStatistics = (projectStatistics, helpLinks = {}) =>
+ PROJECT_STORAGE_TYPES.reduce((types, currentType) => {
+ const helpPathKey = currentType.id.replace(`Size`, ``);
+ const helpPath = helpLinks[helpPathKey];
+
+ return types.concat({
+ storageType: {
+ ...currentType,
+ helpPath,
+ },
+ value: projectStatistics[currentType.id],
+ });
+ }, []);
+
+/**
+ * This method parses the results from `getProjectStorageStatistics` call.
+ *
+ * @param {Object} data graphql result
+ * @returns {Object}
+ */
+export const parseGetProjectStorageResults = (data, helpLinks) => {
+ const projectStatistics = data?.project?.statistics;
+ if (!projectStatistics) {
+ return {};
+ }
+ const { storageSize } = projectStatistics;
+ const storageTypes = getStorageTypesFromProjectStatistics(projectStatistics, helpLinks);
+
+ return {
+ storage: {
+ totalUsage: numberToHumanSize(storageSize, 1),
+ storageTypes,
+ },
+ statistics: projectStatistics,
+ };
+};
+
+/**
+ * Creates a sorting function to sort storage types by usage in the graph and in the table
+ *
+ * @param {string} storageUsageKey key storing value of storage usage
+ * @returns {Function} sorting function
+ */
+export function descendingStorageUsageSort(storageUsageKey) {
+ return (a, b) => b[storageUsageKey] - a[storageUsageKey];
+}