Commit 0e6bedd9 authored by Simon Knox's avatar Simon Knox

Merge branch...

Merge branch '344212-project-variables-fields-desperately-need-to-be-wider-for-a-good-ux-experience' into 'master'

Add tooltip to project variables in CI/CD settings

See merge request gitlab-org/gitlab!79778
parents 9c33cd44 78261494
...@@ -3,6 +3,7 @@ import { GlTable, GlButton, GlModalDirective, GlIcon, GlTooltipDirective } from ...@@ -3,6 +3,7 @@ import { GlTable, GlButton, GlModalDirective, GlIcon, GlTooltipDirective } from
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { ADD_CI_VARIABLE_MODAL_ID } from '../constants'; import { ADD_CI_VARIABLE_MODAL_ID } from '../constants';
import CiVariablePopover from './ci_variable_popover.vue'; import CiVariablePopover from './ci_variable_popover.vue';
...@@ -52,10 +53,11 @@ export default { ...@@ -52,10 +53,11 @@ export default {
}, },
], ],
components: { components: {
GlTable, CiVariablePopover,
GlButton, GlButton,
GlIcon, GlIcon,
CiVariablePopover, GlTable,
TooltipOnTruncate,
}, },
directives: { directives: {
GlModalDirective, GlModalDirective,
...@@ -67,8 +69,8 @@ export default { ...@@ -67,8 +69,8 @@ export default {
valuesButtonText() { valuesButtonText() {
return this.valuesHidden ? __('Reveal values') : __('Hide values'); return this.valuesHidden ? __('Reveal values') : __('Hide values');
}, },
tableIsNotEmpty() { isTableEmpty() {
return this.variables && this.variables.length > 0; return !this.variables || this.variables.length === 0;
}, },
fields() { fields() {
return this.$options.fields; return this.$options.fields;
...@@ -103,12 +105,14 @@ export default { ...@@ -103,12 +105,14 @@ export default {
<col v-for="field in scope.fields" :key="field.key" :style="field.customStyle" /> <col v-for="field in scope.fields" :key="field.key" :style="field.customStyle" />
</template> </template>
<template #cell(key)="{ item }"> <template #cell(key)="{ item }">
<div class="gl-display-flex truncated-container gl-align-items-center"> <div class="gl-display-flex gl-align-items-center">
<span <tooltip-on-truncate :title="item.key" truncate-target="child">
:id="`ci-variable-key-${item.id}`" <span
class="gl-display-inline-block gl-max-w-full gl-text-truncate" :id="`ci-variable-key-${item.id}`"
>{{ item.key }}</span class="gl-display-inline-block gl-max-w-full gl-text-truncate"
> >{{ item.key }}</span
>
</tooltip-on-truncate>
<gl-button <gl-button
v-gl-tooltip v-gl-tooltip
category="tertiary" category="tertiary"
...@@ -120,7 +124,7 @@ export default { ...@@ -120,7 +124,7 @@ export default {
</div> </div>
</template> </template>
<template #cell(value)="{ item }"> <template #cell(value)="{ item }">
<div class="gl-display-flex gl-align-items-center truncated-container"> <div class="gl-display-flex gl-align-items-center">
<span v-if="valuesHidden">*********************</span> <span v-if="valuesHidden">*********************</span>
<span <span
v-else v-else
...@@ -147,10 +151,12 @@ export default { ...@@ -147,10 +151,12 @@ export default {
<gl-icon v-else :size="$options.iconSize" :name="$options.falseIcon" /> <gl-icon v-else :size="$options.iconSize" :name="$options.falseIcon" />
</template> </template>
<template #cell(environment_scope)="{ item }"> <template #cell(environment_scope)="{ item }">
<div class="d-flex truncated-container"> <div class="gl-display-flex">
<span :id="`ci-variable-env-${item.id}`" class="d-inline-block mw-100 text-truncate">{{ <span
item.environment_scope :id="`ci-variable-env-${item.id}`"
}}</span> class="gl-display-inline-block gl-max-w-full gl-text-truncate"
>{{ item.environment_scope }}</span
>
<ci-variable-popover <ci-variable-popover
:target="`ci-variable-env-${item.id}`" :target="`ci-variable-env-${item.id}`"
:value="item.environment_scope" :value="item.environment_scope"
...@@ -160,7 +166,6 @@ export default { ...@@ -160,7 +166,6 @@ export default {
</template> </template>
<template #cell(actions)="{ item }"> <template #cell(actions)="{ item }">
<gl-button <gl-button
ref="edit-ci-variable"
v-gl-modal-directive="$options.modalId" v-gl-modal-directive="$options.modalId"
icon="pencil" icon="pencil"
:aria-label="__('Edit')" :aria-label="__('Edit')"
...@@ -169,17 +174,16 @@ export default { ...@@ -169,17 +174,16 @@ export default {
/> />
</template> </template>
<template #empty> <template #empty>
<p ref="empty-variables" class="text-center empty-variables text-plain"> <p class="gl-text-center gl-py-6 gl-text-black-normal gl-mb-0">
{{ __('There are no variables yet.') }} {{ __('There are no variables yet.') }}
</p> </p>
</template> </template>
</gl-table> </gl-table>
<div <div
class="ci-variable-actions gl-display-flex" class="ci-variable-actions gl-display-flex"
:class="{ 'justify-content-center': !tableIsNotEmpty }" :class="{ 'gl-justify-content-center': isTableEmpty }"
> >
<gl-button <gl-button
ref="add-ci-variable"
v-gl-modal-directive="$options.modalId" v-gl-modal-directive="$options.modalId"
class="gl-mr-3" class="gl-mr-3"
data-qa-selector="add_ci_variable_button" data-qa-selector="add_ci_variable_button"
...@@ -188,8 +192,7 @@ export default { ...@@ -188,8 +192,7 @@ export default {
>{{ __('Add variable') }}</gl-button >{{ __('Add variable') }}</gl-button
> >
<gl-button <gl-button
v-if="tableIsNotEmpty" v-if="!isTableEmpty"
ref="secret-value-reveal-button"
data-qa-selector="reveal_ci_variable_value_button" data-qa-selector="reveal_ci_variable_value_button"
@click="toggleValues(!valuesHidden)" @click="toggleValues(!valuesHidden)"
>{{ valuesButtonText }}</gl-button >{{ valuesButtonText }}</gl-button
......
...@@ -130,10 +130,6 @@ ...@@ -130,10 +130,6 @@
border-radius: $border-radius-base; border-radius: $border-radius-base;
} }
.empty-variables {
padding: 20px 0;
}
.warning-title { .warning-title {
color: $gray-900; color: $gray-900;
} }
......
import { mount } from '@vue/test-utils'; import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import CiVariableTable from '~/ci_variable_list/components/ci_variable_table.vue'; import CiVariableTable from '~/ci_variable_list/components/ci_variable_table.vue';
import createStore from '~/ci_variable_list/store'; import createStore from '~/ci_variable_list/store';
import mockData from '../services/mock_data'; import mockData from '../services/mock_data';
...@@ -14,15 +14,15 @@ describe('Ci variable table', () => { ...@@ -14,15 +14,15 @@ describe('Ci variable table', () => {
const createComponent = () => { const createComponent = () => {
store = createStore(); store = createStore();
jest.spyOn(store, 'dispatch').mockImplementation(); jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = mount(CiVariableTable, { wrapper = mountExtended(CiVariableTable, {
attachTo: document.body, attachTo: document.body,
store, store,
}); });
}; };
const findRevealButton = () => wrapper.find({ ref: 'secret-value-reveal-button' }); const findRevealButton = () => wrapper.findByText('Reveal values');
const findEditButton = () => wrapper.find({ ref: 'edit-ci-variable' }); const findEditButton = () => wrapper.findByLabelText('Edit');
const findEmptyVariablesPlaceholder = () => wrapper.find({ ref: 'empty-variables' }); const findEmptyVariablesPlaceholder = () => wrapper.findByText('There are no variables yet.');
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
...@@ -36,18 +36,36 @@ describe('Ci variable table', () => { ...@@ -36,18 +36,36 @@ describe('Ci variable table', () => {
expect(store.dispatch).toHaveBeenCalledWith('fetchVariables'); expect(store.dispatch).toHaveBeenCalledWith('fetchVariables');
}); });
describe('Renders correct data', () => { describe('When table is empty', () => {
it('displays empty message when variables are not present', () => { beforeEach(() => {
store.state.variables = [];
});
it('displays empty message', () => {
expect(findEmptyVariablesPlaceholder().exists()).toBe(true); expect(findEmptyVariablesPlaceholder().exists()).toBe(true);
}); });
it('displays correct amount of variables present and no empty message', async () => { it('hides the reveal button', () => {
expect(findRevealButton().exists()).toBe(false);
});
});
describe('When table has variables', () => {
beforeEach(() => {
store.state.variables = mockData.mockVariables; store.state.variables = mockData.mockVariables;
});
await nextTick(); it('does not display the empty message', () => {
expect(wrapper.findAll('.js-ci-variable-row').length).toBe(1);
expect(findEmptyVariablesPlaceholder().exists()).toBe(false); expect(findEmptyVariablesPlaceholder().exists()).toBe(false);
}); });
it('displays the reveal button', () => {
expect(findRevealButton().exists()).toBe(true);
});
it('displays the correct amount of variables', async () => {
expect(wrapper.findAll('.js-ci-variable-row')).toHaveLength(1);
});
}); });
describe('Table click actions', () => { describe('Table click actions', () => {
......
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