Commit 9fe7b97d authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '207054-truncate-long-value-stream-analytics-names-in-the-stage-list' into 'master'

Truncate long value stream analytics names in the stage list

Closes #207054

See merge request gitlab-org/gitlab!25943
parents 09f08640 a59901b0
...@@ -45,10 +45,13 @@ export default { ...@@ -45,10 +45,13 @@ export default {
:class="{ active: isActive }" :class="{ active: isActive }"
class="stage-nav-item d-flex pl-4 pr-4 m-0 mb-1 ml-2 rounded border-color-default border-style-solid border-width-1px" class="stage-nav-item d-flex pl-4 pr-4 m-0 mb-1 ml-2 rounded border-color-default border-style-solid border-width-1px"
> >
<div class="stage-nav-item-cell stage-name p-0" :class="{ 'font-weight-bold': isActive }"> <div
class="stage-nav-item-cell stage-name w-50 pr-2"
:class="{ 'font-weight-bold': isActive }"
>
{{ title }} {{ title }}
</div> </div>
<div class="stage-nav-item-cell stage-median mr-4"> <div class="stage-nav-item-cell stage-median w-50">
<template v-if="isUserAllowed"> <template v-if="isUserAllowed">
<span v-if="hasValue">{{ value }}</span> <span v-if="hasValue">{{ value }}</span>
<span v-else class="stage-empty">{{ __('Not enough data') }}</span> <span v-else class="stage-empty">{{ __('Not enough data') }}</span>
......
...@@ -51,11 +51,11 @@ ...@@ -51,11 +51,11 @@
} }
.stage-header { .stage-header {
width: 18.5%; width: 20.5%;
} }
.median-header { .median-header {
width: 21.5%; width: 19.5%;
} }
.event-header { .event-header {
......
<script> <script>
import { GlButton } from '@gitlab/ui'; import { GlButton, GlIcon, GlTooltip } from '@gitlab/ui';
import { approximateDuration } from '~/lib/utils/datetime_utility'; import { approximateDuration } from '~/lib/utils/datetime_utility';
import Icon from '~/vue_shared/components/icon.vue';
import StageCardListItem from './stage_card_list_item.vue'; import StageCardListItem from './stage_card_list_item.vue';
export default { export default {
name: 'StageNavItem', name: 'StageNavItem',
components: { components: {
StageCardListItem, StageCardListItem,
Icon, GlIcon,
GlButton, GlButton,
GlTooltip,
}, },
props: { props: {
isDefaultStage: { isDefaultStage: {
...@@ -40,6 +40,7 @@ export default { ...@@ -40,6 +40,7 @@ export default {
data() { data() {
return { return {
isHover: false, isHover: false,
isTitleOverflowing: false,
}; };
}, },
computed: { computed: {
...@@ -52,6 +53,15 @@ export default { ...@@ -52,6 +53,15 @@ export default {
editable() { editable() {
return this.canEdit; return this.canEdit;
}, },
menuOpen() {
return this.canEdit && this.isHover;
},
openMenuClasses() {
return this.menuOpen ? 'd-flex justify-content-end' : '';
},
},
mounted() {
this.checkIfTitleOverflows();
}, },
methods: { methods: {
handleDropdownAction(action) { handleDropdownAction(action) {
...@@ -67,62 +77,69 @@ export default { ...@@ -67,62 +77,69 @@ export default {
handleHover(hoverState = false) { handleHover(hoverState = false) {
this.isHover = hoverState; this.isHover = hoverState;
}, },
checkIfTitleOverflows() {
const [titleEl] = this.$refs.title?.children;
if (titleEl) {
this.isTitleOverflowing = titleEl.scrollWidth > this.$refs.title.offsetWidth;
}
},
}, },
}; };
</script> </script>
<template> <template>
<li @click="handleSelectStage" @mouseover="handleHover(true)" @mouseleave="handleHover()"> <li @click="handleSelectStage" @mouseover="handleHover(true)" @mouseleave="handleHover()">
<stage-card-list-item :is-active="isActive" :can-edit="editable"> <stage-card-list-item
<div class="stage-nav-item-cell stage-name p-0" :class="{ 'font-weight-bold': isActive }"> :is-active="isActive"
:can-edit="editable"
class="d-flex justify-space-between"
>
<div
ref="title"
class="stage-nav-item-cell stage-name text-truncate w-50 pr-2"
:class="{ 'font-weight-bold': isActive }"
>
<gl-tooltip v-if="isTitleOverflowing" :target="() => $refs.titleSpan">
{{ title }} {{ title }}
</gl-tooltip>
<span ref="titleSpan">{{ title }}</span>
</div> </div>
<div class="stage-nav-item-cell stage-median mr-4"> <div class="stage-nav-item-cell w-50 d-flex justify-content-between">
<div ref="median" class="stage-median w-75 align-items-start">
<span v-if="hasValue">{{ median }}</span> <span v-if="hasValue">{{ median }}</span>
<span v-else class="stage-empty">{{ __('Not enough data') }}</span> <span v-else class="stage-empty">{{ __('Not enough data') }}</span>
</div> </div>
<div v-show="canEdit && isHover" ref="dropdown" class="dropdown"> <div v-show="menuOpen" ref="dropdown" :class="[openMenuClasses]" class="dropdown w-25">
<gl-button <gl-button
:title="__('More actions')" :title="__('More actions')"
class="more-actions-toggle btn btn-transparent p-0" class="more-actions-toggle btn btn-transparent p-0"
data-toggle="dropdown" data-toggle="dropdown"
> >
<icon class="icon" name="ellipsis_v" /> <gl-icon class="icon" name="ellipsis_v" />
</gl-button> </gl-button>
<ul class="more-actions-dropdown dropdown-menu dropdown-open-left"> <ul class="more-actions-dropdown dropdown-menu dropdown-open-left">
<template v-if="isDefaultStage"> <template v-if="isDefaultStage">
<li> <li>
<button <gl-button @click="handleDropdownAction('hide', $event)">
type="button"
class="btn-default btn-transparent"
@click="handleDropdownAction('hide', $event)"
>
{{ __('Hide stage') }} {{ __('Hide stage') }}
</button> </gl-button>
</li> </li>
</template> </template>
<template v-else> <template v-else>
<li> <li>
<button <gl-button @click="handleDropdownAction('edit', $event)">
type="button"
class="btn-default btn-transparent"
@click="handleDropdownAction('edit', $event)"
>
{{ __('Edit stage') }} {{ __('Edit stage') }}
</button> </gl-button>
</li> </li>
<li> <li>
<button <gl-button @click="handleDropdownAction('remove', $event)">
type="button"
class="btn-danger danger"
@click="handleDropdownAction('remove', $event)"
>
{{ __('Remove stage') }} {{ __('Remove stage') }}
</button> </gl-button>
</li> </li>
</template> </template>
</ul> </ul>
</div> </div>
</div>
</stage-card-list-item> </stage-card-list-item>
</li> </li>
</template> </template>
// NOTE: more tests will be added in https://gitlab.com/gitlab-org/gitlab/issues/121613 // NOTE: more tests will be added in https://gitlab.com/gitlab-org/gitlab/issues/121613
import { GlTooltip } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import StageNavItem from 'ee/analytics/cycle_analytics/components/stage_nav_item.vue'; import StageNavItem from 'ee/analytics/cycle_analytics/components/stage_nav_item.vue';
import { approximateDuration } from '~/lib/utils/datetime_utility'; import { approximateDuration } from '~/lib/utils/datetime_utility';
...@@ -7,31 +8,27 @@ describe('StageNavItem', () => { ...@@ -7,31 +8,27 @@ describe('StageNavItem', () => {
const title = 'Rad stage'; const title = 'Rad stage';
const median = 50; const median = 50;
const $sel = { function createComponent({ props = {}, opts = {} } = {}) {
title: '.stage-name',
median: '.stage-median',
};
function createComponent(props) {
return shallowMount(StageNavItem, { return shallowMount(StageNavItem, {
propsData: { propsData: {
title, title,
value: median, value: median,
...props, ...props,
}, },
...opts,
}); });
} }
let wrapper = null; let wrapper = null;
const findStageTitle = () => wrapper.find($sel.title); const findStageTitle = () => wrapper.find({ ref: 'title' });
const findStageMedian = () => wrapper.find($sel.median); const findStageMedian = () => wrapper.find({ ref: 'median' });
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
it('with no median value', () => { it('with no median value', () => {
wrapper = createComponent({ value: null }); wrapper = createComponent({ props: { value: null } });
expect(findStageMedian().text()).toEqual('Not enough data'); expect(findStageMedian().text()).toEqual('Not enough data');
}); });
...@@ -48,4 +45,31 @@ describe('StageNavItem', () => { ...@@ -48,4 +45,31 @@ describe('StageNavItem', () => {
expect(findStageTitle().text()).toEqual(title); expect(findStageTitle().text()).toEqual(title);
}); });
}); });
describe('with a really long name', () => {
const longTitle = 'This is a very long stage name that is intended to break the ui';
beforeEach(() => {
wrapper = createComponent({
props: { title: longTitle },
opts: {
data() {
return { isTitleOverflowing: true };
},
methods: {
// making tbis a noop so it wont toggle 'isTitleOverflowing' on mount
checkIfTitleOverflows: () => {},
},
},
});
});
it('renders the tooltip', () => {
expect(wrapper.find(GlTooltip).exists()).toBe(true);
});
it('tooltip has the correct stage title', () => {
expect(wrapper.find(GlTooltip).text()).toBe(longTitle);
});
});
}); });
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