Commit abae33a8 authored by Phil Hughes's avatar Phil Hughes

Merge branch '41856-backport-stacked-progress-bar' into 'master'

Backport StackedProgressBar component from GitLab EE

Closes #41856

See merge request gitlab-org/gitlab-ce!16457
parents f730a7ac ab68111f
<script>
import tooltip from '~/vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
props: {
cssClass: {
type: String,
required: false,
default: '',
},
successLabel: {
type: String,
required: true,
},
failureLabel: {
type: String,
required: true,
},
neutralLabel: {
type: String,
required: true,
},
successCount: {
type: Number,
required: true,
},
failureCount: {
type: Number,
required: true,
},
totalCount: {
type: Number,
required: true,
},
},
computed: {
neutralCount() {
return this.totalCount - this.successCount - this.failureCount;
},
successPercent() {
return this.getPercent(this.successCount);
},
successBarStyle() {
return this.barStyle(this.successPercent);
},
successTooltip() {
return this.getTooltip(this.successLabel, this.successCount);
},
failurePercent() {
return this.getPercent(this.failureCount);
},
failureBarStyle() {
return this.barStyle(this.failurePercent);
},
failureTooltip() {
return this.getTooltip(this.failureLabel, this.failureCount);
},
neutralPercent() {
return this.getPercent(this.neutralCount);
},
neutralBarStyle() {
return this.barStyle(this.neutralPercent);
},
neutralTooltip() {
return this.getTooltip(this.neutralLabel, this.neutralCount);
},
},
methods: {
getPercent(count) {
return Math.ceil((count / this.totalCount) * 100);
},
barStyle(percent) {
return `width: ${percent}%;`;
},
getTooltip(label, count) {
return `${label}: ${count}`;
},
},
};
</script>
<template>
<div
class="stacked-progress-bar"
:class="cssClass"
>
<span
v-if="!totalCount"
class="status-unavailable"
>
{{ __("Not available") }}
</span>
<span
v-tooltip
v-if="successPercent"
class="status-green"
data-placement="bottom"
:title="successTooltip"
:style="successBarStyle"
>
{{ successPercent }}%
</span>
<span
v-tooltip
v-if="neutralPercent"
class="status-neutral"
data-placement="bottom"
:title="neutralTooltip"
:style="neutralBarStyle"
>
{{ neutralPercent }}%
</span>
<span
v-tooltip
v-if="failurePercent"
class="status-red"
data-placement="bottom"
:title="failureTooltip"
:style="failureBarStyle"
>
{{ failurePercent }}%
</span>
</div>
</template>
......@@ -59,3 +59,4 @@
@import "framework/snippets";
@import "framework/memory_graph";
@import "framework/responsive_tables";
@import "framework/stacked-progress-bar";
.stacked-progress-bar {
display: flex;
height: 16px;
border-radius: 10px;
overflow: hidden;
background-color: $theme-gray-100;
.status-unavailable,
.status-green,
.status-neutral,
.status-red, {
height: 100%;
min-width: 25px;
padding: 0 5px;
font-size: $tooltip-font-size;
font-weight: normal;
color: $white-light;
line-height: 16px;
&:hover {
cursor: pointer;
}
}
.status-unavailable {
padding: 0 10px;
color: $theme-gray-700;
}
.status-green {
background-color: $green-500;
&:hover {
background-color: $green-600;
}
}
.status-neutral {
background-color: $theme-gray-200;
color: $gl-gray-dark;
&:hover {
background-color: $theme-gray-300;
}
}
.status-red {
background-color: $red-500;
&:hover {
background-color: $red-600;
}
}
}
import Vue from 'vue';
import stackedProgressBarComponent from '~/vue_shared/components/stacked_progress_bar.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (config) => {
const Component = Vue.extend(stackedProgressBarComponent);
const defaultConfig = Object.assign({}, {
successLabel: 'Synced',
failureLabel: 'Failed',
neutralLabel: 'Out of sync',
successCount: 10,
failureCount: 5,
totalCount: 20,
}, config);
return mountComponent(Component, defaultConfig);
};
describe('StackedProgressBarComponent', () => {
let vm;
beforeEach(() => {
vm = createComponent();
});
afterEach(() => {
vm.$destroy();
});
describe('computed', () => {
describe('neutralCount', () => {
it('returns neutralCount based on totalCount, successCount and failureCount', () => {
expect(vm.neutralCount).toBe(5); // 20 - 10 - 5
});
});
});
describe('methods', () => {
describe('getPercent', () => {
it('returns percentage from provided count based on `totalCount`', () => {
expect(vm.getPercent(10)).toBe(50);
});
});
describe('barStyle', () => {
it('returns style string based on percentage provided', () => {
expect(vm.barStyle(50)).toBe('width: 50%;');
});
});
describe('getTooltip', () => {
it('returns label string based on label and count provided', () => {
expect(vm.getTooltip('Synced', 10)).toBe('Synced: 10');
});
});
});
describe('template', () => {
it('renders container element', () => {
expect(vm.$el.classList.contains('stacked-progress-bar')).toBeTruthy();
});
it('renders empty state when count is unavailable', () => {
const vmX = createComponent({ totalCount: 0, successCount: 0, failureCount: 0 });
expect(vmX.$el.querySelectorAll('.status-unavailable').length).not.toBe(0);
vmX.$destroy();
});
it('renders bar elements when count is available', () => {
expect(vm.$el.querySelectorAll('.status-green').length).not.toBe(0);
expect(vm.$el.querySelectorAll('.status-neutral').length).not.toBe(0);
expect(vm.$el.querySelectorAll('.status-red').length).not.toBe(0);
});
});
});
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