Commit a100552f authored by Dhiraj Bodicherla's avatar Dhiraj Bodicherla

Added warning state in usage quota table

This MR adds warning state in the usage quota
table if a project storage is in warning state.
The changes are behind a feature flag. A tooltip
is also added to the warning icon
parent a30d40df
...@@ -5,7 +5,8 @@ ...@@ -5,7 +5,8 @@
* looks similar to project.vue component so that once the flag is * looks similar to project.vue component so that once the flag is
* lifted this component could replace and be used mainstream. * lifted this component could replace and be used mainstream.
*/ */
import { GlLink, GlIcon } from '@gitlab/ui'; import { GlLink, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
import ProjectAvatar from '~/vue_shared/components/project_avatar/default.vue'; import ProjectAvatar from '~/vue_shared/components/project_avatar/default.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
...@@ -16,6 +17,9 @@ export default { ...@@ -16,6 +17,9 @@ export default {
GlLink, GlLink,
ProjectAvatar, ProjectAvatar,
}, },
directives: {
GlTooltip: GlTooltipDirective,
},
props: { props: {
project: { project: {
required: true, required: true,
...@@ -41,15 +45,32 @@ export default { ...@@ -41,15 +45,32 @@ export default {
excessStorageSize() { excessStorageSize() {
return numberToHumanSize(this.project.statistics?.excessStorageSize ?? 0); return numberToHumanSize(this.project.statistics?.excessStorageSize ?? 0);
}, },
hasError() { status() {
// The project default limit will be sent by backend. // The project default limit will be sent by backend.
// This is being added here just for testing purposes. // This is being added here just for testing purposes.
// This entire component is rendered behind the // This entire component is rendered behind the
// additional_repo_storage_by_namespace feature flag. This // additional_repo_storage_by_namespace feature flag. This
// piece will be removed along with the flag. // piece will be removed along with the flag and the logic
// will be mostly on the backend.
const PROJECT_DEFAULT_LIMIT = 10000000000; const PROJECT_DEFAULT_LIMIT = 10000000000;
const projectLimit = this.project.statistics?.projectLimit ?? PROJECT_DEFAULT_LIMIT; const PROJECT_DEFAULT_WARNING_LIMIT = 9000000000;
return this.project.statistics.storageSize > projectLimit;
if (this.project.statistics.storageSize > PROJECT_DEFAULT_LIMIT) {
return {
bgColor: { 'gl-bg-red-50': true },
iconClass: { 'gl-text-red-500': true },
linkClass: 'gl-text-red-500!',
tooltipText: s__('UsageQuota|This project is locked.'),
};
} else if (this.project.statistics.storageSize > PROJECT_DEFAULT_WARNING_LIMIT) {
return {
bgColor: { 'gl-bg-orange-50': true },
iconClass: 'gl-text-orange-500',
tooltipText: s__('UsageQuota|This project is at risk of being locked.'),
};
}
return {};
}, },
}, },
}; };
...@@ -57,7 +78,7 @@ export default { ...@@ -57,7 +78,7 @@ export default {
<template> <template>
<div <div
class="gl-responsive-table-row gl-border-solid gl-border-b-1 gl-pt-3 gl-pb-3 gl-border-b-gray-100" class="gl-responsive-table-row gl-border-solid gl-border-b-1 gl-pt-3 gl-pb-3 gl-border-b-gray-100"
:class="{ 'gl-bg-red-50': hasError }" :class="status.bgColor"
role="row" role="row"
data-testid="projectTableRow" data-testid="projectTableRow"
> >
...@@ -75,13 +96,18 @@ export default { ...@@ -75,13 +96,18 @@ export default {
<div> <div>
<project-avatar :project="projectAvatar" :size="32" /> <project-avatar :project="projectAvatar" :size="32" />
</div> </div>
<div v-if="hasError"> <div v-if="status.iconClass">
<gl-icon name="status_warning" class="gl-text-red-500 gl-mr-3" /> <gl-icon
v-gl-tooltip="{ title: status.tooltipText }"
name="status_warning"
class="gl-mr-3"
:class="status.iconClass"
/>
</div> </div>
<gl-link <gl-link
:href="project.webUrl" :href="project.webUrl"
class="gl-font-weight-bold gl-text-gray-900!" class="gl-font-weight-bold gl-text-gray-900!"
:class="{ 'gl-text-red-500!': hasError }" :class="status.linkClass"
>{{ name }}</gl-link >{{ name }}</gl-link
> >
</div> </div>
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ProjectWithExcessStorage from 'ee/storage_counter/components/project_with_excess_storage.vue'; import ProjectWithExcessStorage from 'ee/storage_counter/components/project_with_excess_storage.vue';
import ProjectAvatar from '~/vue_shared/components/project_avatar/default.vue'; import ProjectAvatar from '~/vue_shared/components/project_avatar/default.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
...@@ -10,15 +11,19 @@ let wrapper; ...@@ -10,15 +11,19 @@ let wrapper;
const createComponent = (propsData = {}) => { const createComponent = (propsData = {}) => {
wrapper = shallowMount(ProjectWithExcessStorage, { wrapper = shallowMount(ProjectWithExcessStorage, {
propsData: { propsData: {
project: projects[1], project: projects[0],
...propsData, ...propsData,
}, },
directives: {
GlTooltip: createMockDirective(),
},
}); });
}; };
const findTableRow = () => wrapper.find('[data-testid="projectTableRow"]'); const findTableRow = () => wrapper.find('[data-testid="projectTableRow"]');
const findWarningIcon = () => wrapper.find({ name: 'status_warning' }); const findWarningIcon = () => wrapper.find({ name: 'status_warning' });
const findProjectLink = () => wrapper.find(GlLink); const findProjectLink = () => wrapper.find(GlLink);
const getWarningIconTooltipText = () => getBinding(findWarningIcon().element, 'gl-tooltip').value;
describe('Storage Counter project component', () => { describe('Storage Counter project component', () => {
beforeEach(() => { beforeEach(() => {
...@@ -34,11 +39,11 @@ describe('Storage Counter project component', () => { ...@@ -34,11 +39,11 @@ describe('Storage Counter project component', () => {
}); });
it('renders project name', () => { it('renders project name', () => {
expect(wrapper.text()).toContain(projects[1].nameWithNamespace); expect(wrapper.text()).toContain(projects[0].nameWithNamespace);
}); });
it('renders formatted storage size', () => { it('renders formatted storage size', () => {
expect(wrapper.text()).toContain(numberToHumanSize(projects[1].statistics.storageSize)); expect(wrapper.text()).toContain(numberToHumanSize(projects[0].statistics.storageSize));
}); });
it('does not render the warning icon if project is not in error state', () => { it('does not render the warning icon if project is not in error state', () => {
...@@ -65,5 +70,31 @@ describe('Storage Counter project component', () => { ...@@ -65,5 +70,31 @@ describe('Storage Counter project component', () => {
it('with error icon', () => { it('with error icon', () => {
expect(findWarningIcon().exists()).toBe(true); expect(findWarningIcon().exists()).toBe(true);
}); });
it('with tooltip', () => {
expect(getWarningIconTooltipText().title).toBe('This project is locked.');
});
});
describe('renders the row in warning state', () => {
beforeEach(() => {
createComponent({ project: projects[1] });
});
it('with error state background', () => {
expect(findTableRow().classes('gl-bg-orange-50')).toBe(true);
});
it('with project link in default gray state', () => {
expect(findProjectLink().classes('gl-text-gray-900!')).toBe(true);
});
it('with warning icon', () => {
expect(findWarningIcon().exists()).toBe(true);
});
it('with tooltip', () => {
expect(getWarningIconTooltipText().title).toBe('This project is at risk of being locked.');
});
}); });
}); });
...@@ -24,7 +24,7 @@ export const projects = [ ...@@ -24,7 +24,7 @@ export const projects = [
name: 'Html5 Boilerplate', name: 'Html5 Boilerplate',
statistics: { statistics: {
commitCount: 0, commitCount: 0,
storageSize: 1293346, storageSize: 9933460120,
repositorySize: 0, repositorySize: 0,
lfsObjectsSize: 0, lfsObjectsSize: 0,
buildArtifactsSize: 1272375, buildArtifactsSize: 1272375,
......
...@@ -27947,6 +27947,12 @@ msgstr "" ...@@ -27947,6 +27947,12 @@ msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners" msgid "UsageQuota|This namespace has no projects which use shared runners"
msgstr "" msgstr ""
msgid "UsageQuota|This project is at risk of being locked."
msgstr ""
msgid "UsageQuota|This project is locked."
msgstr ""
msgid "UsageQuota|Unlimited" msgid "UsageQuota|Unlimited"
msgstr "" 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