Commit f3d47379 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 90264a0a 67d91db4
<script> <script>
import { import {
GlButton,
GlFormGroup,
GlFormRadio, GlFormRadio,
GlFormRadioGroup, GlFormRadioGroup,
GlLabel, GlLabel,
GlSearchBoxByType,
GlSkeletonLoader,
GlTooltipDirective as GlTooltip, GlTooltipDirective as GlTooltip,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import { ListType } from '~/boards/constants';
import boardsStore from '~/boards/stores/boards_store';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { isScopedLabel } from '~/lib/utils/common_utils'; import { isScopedLabel } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import boardsStore from '../stores/boards_store';
export default { export default {
i18n: {
add: __('Add'),
cancel: __('Cancel'),
formDescription: __('A label list displays all issues with the selected label.'),
newLabelList: __('New label list'),
noLabelSelected: __('No label selected'),
searchPlaceholder: __('Search labels'),
selectLabel: __('Select label'),
selected: __('Selected'),
},
components: { components: {
GlButton, BoardAddNewColumnForm,
GlFormGroup,
GlFormRadio, GlFormRadio,
GlFormRadioGroup, GlFormRadioGroup,
GlLabel, GlLabel,
GlSearchBoxByType,
GlSkeletonLoader,
}, },
directives: { directives: {
GlTooltip, GlTooltip,
...@@ -40,31 +25,27 @@ export default { ...@@ -40,31 +25,27 @@ export default {
inject: ['scopedLabelsAvailable'], inject: ['scopedLabelsAvailable'],
data() { data() {
return { return {
searchTerm: '', selectedId: null,
selectedLabelId: null,
}; };
}, },
computed: { computed: {
...mapState(['labels', 'labelsLoading', 'isEpicBoard']), ...mapState(['labels', 'labelsLoading', 'isEpicBoard']),
...mapGetters(['getListByLabelId', 'shouldUseGraphQL']), ...mapGetters(['getListByLabelId', 'shouldUseGraphQL']),
selectedLabel() { selectedLabel() {
return this.labels.find(({ id }) => id === this.selectedLabelId); if (!this.selectedId) {
return null;
}
return this.labels.find(({ id }) => id === this.selectedId);
},
columnForSelected() {
return this.getListByLabelId(this.selectedId);
}, },
}, },
created() { created() {
this.filterLabels(); this.filterItems();
}, },
methods: { methods: {
...mapActions(['createList', 'fetchLabels', 'highlightList', 'setAddColumnFormVisibility']), ...mapActions(['createList', 'fetchLabels', 'highlightList', 'setAddColumnFormVisibility']),
getListByLabel(label) {
if (this.shouldUseGraphQL || this.isEpicBoard) {
return this.getListByLabelId(label);
}
return boardsStore.findListByLabelId(label.id);
},
columnExists(label) {
return Boolean(this.getListByLabel(label));
},
highlight(listId) { highlight(listId) {
if (this.shouldUseGraphQL || this.isEpicBoard) { if (this.shouldUseGraphQL || this.isEpicBoard) {
this.highlightList(listId); this.highlightList(listId);
...@@ -77,44 +58,35 @@ export default { ...@@ -77,44 +58,35 @@ export default {
} }
}, },
addList() { addList() {
if (!this.selectedLabelId) { if (!this.selectedLabel) {
return;
}
const label = this.selectedLabel;
if (!label) {
return; return;
} }
this.setAddColumnFormVisibility(false); this.setAddColumnFormVisibility(false);
if (this.columnExists({ id: this.selectedLabelId })) { if (this.columnForSelected) {
const listId = this.getListByLabel(label).id; const listId = this.columnForSelected.id;
this.highlight(listId); this.highlight(listId);
return; return;
} }
if (this.shouldUseGraphQL || this.isEpicBoard) { if (this.shouldUseGraphQL || this.isEpicBoard) {
this.createList({ labelId: this.selectedLabelId }); this.createList({ labelId: this.selectedId });
} else { } else {
boardsStore.new({ const listObj = {
title: label.title, labelId: getIdFromGraphQLId(this.selectedId),
title: this.selectedLabel.title,
position: boardsStore.state.lists.length - 2, position: boardsStore.state.lists.length - 2,
list_type: 'label', list_type: ListType.label,
label: { label: this.selectedLabel,
id: label.id, };
title: label.title,
color: label.color,
},
});
this.highlight(boardsStore.findListByLabelId(label.id).id); boardsStore.new(listObj);
} }
}, },
filterLabels() { filterItems(searchTerm) {
this.fetchLabels(this.searchTerm); this.fetchLabels(searchTerm);
}, },
showScopedLabels(label) { showScopedLabels(label) {
...@@ -125,103 +97,43 @@ export default { ...@@ -125,103 +97,43 @@ export default {
</script> </script>
<template> <template>
<div <board-add-new-column-form
class="board-add-new-list board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal gl-flex-shrink-0" :loading="labelsLoading"
data-testid="board-add-new-column" :form-description="__('A label list displays issues with the selected label.')"
data-qa-selector="board_add_new_list" :search-label="__('Select label')"
:search-placeholder="__('Search labels')"
:selected-id="selectedId"
@filter-items="filterItems"
@add-list="addList"
> >
<div <template slot="selected">
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base gl-bg-white" <gl-label
> v-if="selectedLabel"
<h3 v-gl-tooltip
class="gl-font-base gl-px-5 gl-py-5 gl-m-0 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100" :title="selectedLabel.title"
data-testid="board-add-column-form-title" :description="selectedLabel.description"
> :background-color="selectedLabel.color"
{{ $options.i18n.newLabelList }} :scoped="showScopedLabels(selectedLabel)"
</h3> />
</template>
<div class="gl-display-flex gl-flex-direction-column gl-h-full gl-overflow-hidden">
<!-- selectbox is here in EE --> <template slot="items">
<gl-form-radio-group v-model="selectedId" class="gl-overflow-y-auto gl-px-5 gl-pt-3">
<p class="gl-m-5">{{ $options.i18n.formDescription }}</p> <label
v-for="label in labels"
<div class="gl-px-5 gl-pb-4"> :key="label.id"
<label class="gl-mb-2">{{ $options.i18n.selected }}</label> class="gl-display-flex gl-flex-align-items-center gl-mb-5 gl-font-weight-normal"
<div>
<gl-label
v-if="selectedLabel"
v-gl-tooltip
:title="selectedLabel.title"
:description="selectedLabel.description"
:background-color="selectedLabel.color"
:scoped="showScopedLabels(selectedLabel)"
/>
<div v-else class="gl-text-gray-500">{{ $options.i18n.noLabelSelected }}</div>
</div>
</div>
<gl-form-group
class="gl-mx-5 gl-mb-3"
:label="$options.i18n.selectLabel"
label-for="board-available-labels"
>
<gl-search-box-by-type
id="board-available-labels"
v-model.trim="searchTerm"
debounce="250"
:placeholder="$options.i18n.searchPlaceholder"
@input="filterLabels"
/>
</gl-form-group>
<div v-if="labelsLoading" class="gl-m-5">
<gl-skeleton-loader :width="500" :height="172">
<rect width="480" height="20" x="10" y="15" rx="4" />
<rect width="380" height="20" x="10" y="50" rx="4" />
<rect width="430" height="20" x="10" y="85" rx="4" />
</gl-skeleton-loader>
</div>
<gl-form-radio-group
v-else
v-model="selectedLabelId"
class="gl-overflow-y-auto gl-px-5 gl-pt-3"
>
<label
v-for="label in labels"
:key="label.id"
class="gl-display-flex gl-flex-align-items-center gl-mb-5 gl-font-weight-normal"
>
<gl-form-radio :value="label.id" class="gl-mb-0 gl-mr-3" />
<span
class="dropdown-label-box gl-top-0"
:style="{
backgroundColor: label.color,
}"
></span>
<span>{{ label.title }}</span>
</label>
</gl-form-radio-group>
</div>
<div
class="gl-display-flex gl-p-3 gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10"
>
<gl-button
data-testid="cancelAddNewColumn"
class="gl-ml-auto gl-mr-3"
@click="setAddColumnFormVisibility(false)"
>{{ $options.i18n.cancel }}</gl-button
>
<gl-button
data-testid="addNewColumnButton"
:disabled="!selectedLabelId"
variant="success"
class="gl-mr-4"
@click="addList"
>{{ $options.i18n.add }}</gl-button
> >
</div> <gl-form-radio :value="label.id" class="gl-mb-0 gl-mr-3" />
</div> <span
</div> class="dropdown-label-box gl-top-0"
:style="{
backgroundColor: label.color,
}"
></span>
<span>{{ label.title }}</span>
</label>
</gl-form-radio-group>
</template>
</board-add-new-column-form>
</template> </template>
<script>
import { GlButton, GlFormGroup, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
import { mapActions } from 'vuex';
import { __ } from '~/locale';
export default {
i18n: {
add: __('Add'),
cancel: __('Cancel'),
newList: __('New list'),
noneSelected: __('None'),
selected: __('Selected'),
},
components: {
GlButton,
GlFormGroup,
GlSearchBoxByType,
GlSkeletonLoader,
},
props: {
loading: {
type: Boolean,
required: true,
},
formDescription: {
type: String,
required: true,
},
searchLabel: {
type: String,
required: true,
},
searchPlaceholder: {
type: String,
required: true,
},
selectedId: {
type: [Number, String],
required: false,
default: null,
},
},
methods: {
...mapActions(['setAddColumnFormVisibility']),
},
};
</script>
<template>
<div
class="board-add-new-list board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal gl-flex-shrink-0"
data-testid="board-add-new-column"
data-qa-selector="board_add_new_list"
>
<div
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base gl-bg-white"
>
<h3
class="gl-font-base gl-px-5 gl-py-5 gl-m-0 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
data-testid="board-add-column-form-title"
>
{{ $options.i18n.newList }}
</h3>
<div class="gl-display-flex gl-flex-direction-column gl-h-full gl-overflow-hidden">
<slot name="select-list-type">
<div class="gl-mb-5"></div>
</slot>
<p class="gl-px-5">{{ formDescription }}</p>
<div class="gl-px-5 gl-pb-4">
<label class="gl-mb-2">{{ $options.i18n.selected }}</label>
<slot name="selected">
<div class="gl-text-gray-500">{{ $options.i18n.noneSelected }}</div>
</slot>
</div>
<gl-form-group
class="gl-mx-5 gl-mb-3"
:label="searchLabel"
label-for="board-available-column-entities"
>
<gl-search-box-by-type
id="board-available-column-entities"
debounce="250"
:placeholder="searchPlaceholder"
@input="$emit('filter-items', $event)"
/>
</gl-form-group>
<div v-if="loading" class="gl-px-5">
<gl-skeleton-loader :width="500" :height="172">
<rect width="480" height="20" x="10" y="15" rx="4" />
<rect width="380" height="20" x="10" y="50" rx="4" />
<rect width="430" height="20" x="10" y="85" rx="4" />
</gl-skeleton-loader>
</div>
<slot v-else name="items"></slot>
</div>
<div
class="gl-display-flex gl-p-3 gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10"
>
<gl-button
data-testid="cancelAddNewColumn"
class="gl-ml-auto gl-mr-3"
@click="setAddColumnFormVisibility(false)"
>{{ $options.i18n.cancel }}</gl-button
>
<gl-button
data-testid="addNewColumnButton"
:disabled="!selectedId"
variant="success"
class="gl-mr-4"
@click="$emit('add-list')"
>{{ $options.i18n.add }}</gl-button
>
</div>
</div>
</div>
</template>
...@@ -157,8 +157,8 @@ export default { ...@@ -157,8 +157,8 @@ export default {
}, },
}) })
.then(({ data }) => { .then(({ data }) => {
if (data?.boardListCreate?.errors.length) { if (data.boardListCreate?.errors.length) {
commit(types.CREATE_LIST_FAILURE); commit(types.CREATE_LIST_FAILURE, data.boardListCreate.errors[0]);
} else { } else {
const list = data.boardListCreate?.list; const list = data.boardListCreate?.list;
dispatch('addList', list); dispatch('addList', list);
......
...@@ -60,8 +60,11 @@ export default { ...@@ -60,8 +60,11 @@ export default {
state.filterParams = filterParams; state.filterParams = filterParams;
}, },
[mutationTypes.CREATE_LIST_FAILURE]: (state) => { [mutationTypes.CREATE_LIST_FAILURE]: (
state.error = s__('Boards|An error occurred while creating the list. Please try again.'); state,
error = s__('Boards|An error occurred while creating the list. Please try again.'),
) => {
state.error = error;
}, },
[mutationTypes.RECEIVE_LABELS_REQUEST]: (state) => { [mutationTypes.RECEIVE_LABELS_REQUEST]: (state) => {
......
import IntegrationSettingsForm from '~/integrations/integration_settings_form'; import IntegrationSettingsForm from '~/integrations/integration_settings_form';
import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics'; import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
document.addEventListener('DOMContentLoaded', () => { const prometheusSettingsWrapper = document.querySelector('.js-prometheus-metrics-monitoring');
const prometheusSettingsWrapper = document.querySelector('.js-prometheus-metrics-monitoring'); const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); integrationSettingsForm.init();
integrationSettingsForm.init();
if (prometheusSettingsWrapper) { if (prometheusSettingsWrapper) {
const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring'); const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
prometheusMetrics.loadActiveMetrics(); prometheusMetrics.loadActiveMetrics();
} }
});
...@@ -7,61 +7,59 @@ import initWebIdeLink from '~/pages/projects/shared/web_ide_link'; ...@@ -7,61 +7,59 @@ import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import '~/sourcegraph/load'; import '~/sourcegraph/load';
document.addEventListener('DOMContentLoaded', () => { new BlobViewer(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new initBlob();
initBlob();
const CommitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status'); const CommitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status');
const statusLink = document.querySelector('.commit-actions .ci-status-link'); const statusLink = document.querySelector('.commit-actions .ci-status-link');
if (statusLink) { if (statusLink) {
statusLink.remove(); statusLink.remove();
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el: CommitPipelineStatusEl, el: CommitPipelineStatusEl,
components: { components: {
commitPipelineStatus, commitPipelineStatus,
}, },
render(createElement) { render(createElement) {
return createElement('commit-pipeline-status', { return createElement('commit-pipeline-status', {
props: { props: {
endpoint: CommitPipelineStatusEl.dataset.endpoint, endpoint: CommitPipelineStatusEl.dataset.endpoint,
}, },
}); });
}, },
}); });
} }
initWebIdeLink({ el: document.getElementById('js-blob-web-ide-link') }); initWebIdeLink({ el: document.getElementById('js-blob-web-ide-link') });
GpgBadges.fetch(); GpgBadges.fetch();
const codeNavEl = document.getElementById('js-code-navigation'); const codeNavEl = document.getElementById('js-code-navigation');
if (codeNavEl) { if (codeNavEl) {
const { codeNavigationPath, blobPath, definitionPathPrefix } = codeNavEl.dataset; const { codeNavigationPath, blobPath, definitionPathPrefix } = codeNavEl.dataset;
// eslint-disable-next-line promise/catch-or-return // eslint-disable-next-line promise/catch-or-return
import('~/code_navigation').then((m) => import('~/code_navigation').then((m) =>
m.default({ m.default({
blobs: [{ path: blobPath, codeNavigationPath }], blobs: [{ path: blobPath, codeNavigationPath }],
definitionPathPrefix, definitionPathPrefix,
}), }),
); );
} }
const successPipelineEl = document.querySelector('.js-success-pipeline-modal'); const successPipelineEl = document.querySelector('.js-success-pipeline-modal');
if (successPipelineEl) { if (successPipelineEl) {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el: successPipelineEl, el: successPipelineEl,
render(createElement) { render(createElement) {
return createElement(PipelineTourSuccessModal, { return createElement(PipelineTourSuccessModal, {
props: { props: {
...successPipelineEl.dataset, ...successPipelineEl.dataset,
}, },
}); });
}, },
}); });
} }
});
%span.left-label Newer
%span.legend-box.legend-box-0
%span.legend-box.legend-box-1
%span.legend-box.legend-box-2
%span.legend-box.legend-box-3
%span.legend-box.legend-box-4
%span.legend-box.legend-box-5
%span.legend-box.legend-box-6
%span.legend-box.legend-box-7
%span.legend-box.legend-box-8
%span.legend-box.legend-box-9
%span.right-label Older
%tr
%td.blame-commit{ class: commit_data.age_map_class }
.commit
= commit_data.author_avatar
.commit-row-title
%span.item-title.str-truncated-100
= commit_data.commit_link
%span
= commit_data.project_blame_link
&nbsp;
.light
= commit_data.commit_author_link
= _('committed')
#{commit_data.time_ago_tooltip}
%td.line-numbers
- line_count = blame_group[:lines].count
- (current_line...(current_line + line_count)).each do |i|
%a.diff-line-num{ href: "#L#{i}", id: "L#{i}", 'data-line-number' => i }
= link_icon
= i
\
%td.lines
%pre.code.highlight
%code
- blame_group[:lines].each do |line|
#{line}
...@@ -6,18 +6,56 @@ ...@@ -6,18 +6,56 @@
.file-holder .file-holder
= render "projects/blob/header", blob: @blob, blame: true = render "projects/blob/header", blob: @blob, blame: true
.file-blame-legend .file-blame-legend
= render 'age_map_legend' %span.left-label Newer
%span.legend-box.legend-box-0
%span.legend-box.legend-box-1
%span.legend-box.legend-box-2
%span.legend-box.legend-box-3
%span.legend-box.legend-box-4
%span.legend-box.legend-box-5
%span.legend-box.legend-box-6
%span.legend-box.legend-box-7
%span.legend-box.legend-box-8
%span.legend-box.legend-box-9
%span.right-label Older
.table-responsive.file-content.blame.code.js-syntax-highlight .table-responsive.file-content.blame.code.js-syntax-highlight
%table %table
- current_line = 1 - current_line = 1
- @blame.groups.each do |blame_group| - @blame.groups.each do |blame_group|
- commit_data = @blame.commit_data(blame_group[:commit]) - commit_data = @blame.commit_data(blame_group[:commit])
- line_count = blame_group[:lines].count
%tr
%td.blame-commit{ class: commit_data.age_map_class }
.commit
= commit_data.author_avatar
.commit-row-title
%span.item-title.str-truncated-100
= commit_data.commit_link
%span
= commit_data.project_blame_link
&nbsp;
.light
= commit_data.commit_author_link
= _('committed')
#{commit_data.time_ago_tooltip}
%td.line-numbers
- (current_line...(current_line + line_count)).each do |i|
%a.diff-line-num{ href: "#L#{i}", id: "L#{i}", 'data-line-number' => i }
= link_icon
= i
\
= render 'blame_group', %td.lines
blame_group: blame_group, %pre.code.highlight
current_line: current_line, %code
link_icon: link_icon, - blame_group[:lines].each do |line|
commit_data: commit_data #{line}
- current_line += blame_group[:lines].count - current_line += line_count
...@@ -8,5 +8,5 @@ ...@@ -8,5 +8,5 @@
.modal-body.p-3 .modal-body.p-3
%p= _("You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.") % { tag_start: '', tag_end: ''} %p= _("You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.") % { tag_start: '', tag_end: ''}
.modal-footer .modal-footer
= link_to _('Cancel'), '#', class: "btn btn-cancel", "data-dismiss" => "modal" = link_to _('Cancel'), '#', class: "gl-button btn btn-default btn-cancel", "data-dismiss" => "modal"
= link_to _('Fork project'), fork_path, class: 'btn btn-success', data: { qa_selector: 'fork_project_button' }, method: :post = link_to _('Fork project'), fork_path, class: 'gl-button btn btn-confirm', data: { qa_selector: 'fork_project_button' }, method: :post
- if any_projects?(@projects) - if any_projects?(@projects)
.project-item-select-holder.btn-group.gl-ml-auto.gl-mr-auto.gl-py-3.gl-relative.gl-display-flex.gl-overflow-hidden .project-item-select-holder.btn-group.gl-ml-auto.gl-mr-auto.gl-py-3.gl-relative.gl-display-flex.gl-overflow-hidden
%a.btn.gl-button.btn-success.new-project-item-link.block-truncated.qa-new-project-item-link{ href: '', data: { label: local_assigns[:label], type: local_assigns[:type] }, class: "gl-m-0!" } %a.btn.gl-button.btn-confirm.new-project-item-link.block-truncated.qa-new-project-item-link{ href: '', data: { label: local_assigns[:label], type: local_assigns[:type] }, class: "gl-m-0!" }
= loading_icon(color: 'light') = loading_icon(color: 'light')
= project_select_tag :project_path, class: "project-item-select gl-absolute! gl-visibility-hidden", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path], with_shared: local_assigns[:with_shared], include_projects_in_subgroups: local_assigns[:include_projects_in_subgroups] }, with_feature_enabled: local_assigns[:with_feature_enabled] = project_select_tag :project_path, class: "project-item-select gl-absolute! gl-visibility-hidden", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path], with_shared: local_assigns[:with_shared], include_projects_in_subgroups: local_assigns[:include_projects_in_subgroups] }, with_feature_enabled: local_assigns[:with_feature_enabled]
%button.btn.dropdown-toggle.btn-success.btn-md.gl-button.gl-dropdown-toggle.dropdown-toggle-split.new-project-item-select-button.qa-new-project-item-select-button.gl-p-0.gl-w-100{ class: "gl-m-0!", 'aria-label': _('Toggle project select') } %button.btn.dropdown-toggle.btn-confirm.btn-md.gl-button.gl-dropdown-toggle.dropdown-toggle-split.new-project-item-select-button.qa-new-project-item-select-button.gl-p-0.gl-w-100{ class: "gl-m-0!", 'aria-label': _('Toggle project select') }
= sprite_icon('chevron-down') = sprite_icon('chevron-down')
...@@ -20,4 +20,4 @@ ...@@ -20,4 +20,4 @@
- if has_submit - if has_submit
.row-content-block.footer-block .row-content-block.footer-block
= f.submit _("Submit %{humanized_resource_name}") % { humanized_resource_name: humanized_resource_name }, class: 'btn btn-success' = f.submit _("Submit %{humanized_resource_name}") % { humanized_resource_name: humanized_resource_name }, class: 'gl-button btn btn-confirm'
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
- stars = true unless local_assigns[:stars] == false - stars = true unless local_assigns[:stars] == false
- forks = true unless local_assigns[:forks] == false - forks = true unless local_assigns[:forks] == false
- merge_requests = true unless local_assigns[:merge_requests] == false - merge_requests = true unless local_assigns[:merge_requests] == false
- issues = true unless local_assigns[:issues] == false
- pipeline_status = true unless local_assigns[:pipeline_status] == false - pipeline_status = true unless local_assigns[:pipeline_status] == false
- skip_namespace = false unless local_assigns[:skip_namespace] == true - skip_namespace = false unless local_assigns[:skip_namespace] == true
- user = local_assigns[:user] - user = local_assigns[:user]
...@@ -41,7 +40,7 @@ ...@@ -41,7 +40,7 @@
= render "shared/projects/project", project: project, skip_namespace: skip_namespace, = render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, use_creator_avatar: use_creator_avatar, avatar: avatar, stars: stars, css_class: css_class, use_creator_avatar: use_creator_avatar,
forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user, merge_requests: merge_requests, forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user, merge_requests: merge_requests,
issues: issues, pipeline_status: pipeline_status, compact_mode: compact_mode issues: project.issues_enabled?, pipeline_status: pipeline_status, compact_mode: compact_mode
= paginate_collection(projects, remote: remote) unless skip_pagination = paginate_collection(projects, remote: remote) unless skip_pagination
- else - else
- if @contributed_projects - if @contributed_projects
......
...@@ -70,10 +70,10 @@ ...@@ -70,10 +70,10 @@
.form-actions .form-actions
- if @page && @page.persisted? - if @page && @page.persisted?
= f.submit _("Save changes"), class: 'btn gl-button btn-success qa-save-changes-button js-wiki-btn-submit', disabled: 'true' = f.submit _("Save changes"), class: 'btn gl-button btn-confirm qa-save-changes-button js-wiki-btn-submit', disabled: 'true'
.float-right .float-right
= link_to _("Cancel"), wiki_page_path(@wiki, @page), class: 'btn gl-button btn-cancel btn-default' = link_to _("Cancel"), wiki_page_path(@wiki, @page), class: 'btn gl-button btn-cancel btn-default'
- else - else
= f.submit s_("Wiki|Create page"), class: 'btn-success gl-button btn qa-create-page-button rspec-create-page-button js-wiki-btn-submit', disabled: 'true' = f.submit s_("Wiki|Create page"), class: 'btn-confirm gl-button btn qa-create-page-button rspec-create-page-button js-wiki-btn-submit', disabled: 'true'
.float-right .float-right
= link_to _("Cancel"), wiki_path(@wiki), class: 'btn gl-button btn-cancel btn-default' = link_to _("Cancel"), wiki_path(@wiki), class: 'btn gl-button btn-cancel btn-default'
...@@ -2,5 +2,5 @@ ...@@ -2,5 +2,5 @@
= link_to wiki_page_path(@wiki, @page, action: :history), class: "btn gl-button", role: "button", data: { qa_selector: 'page_history_button' } do = link_to wiki_page_path(@wiki, @page, action: :history), class: "btn gl-button", role: "button", data: { qa_selector: 'page_history_button' } do
= s_("Wiki|Page history") = s_("Wiki|Page history")
- if can?(current_user, :create_wiki, @wiki.container) - if can?(current_user, :create_wiki, @wiki.container)
= link_to wiki_path(@wiki, action: :new), class: "btn gl-button btn-success btn-inverted", role: "button", data: { qa_selector: 'new_page_button' } do = link_to wiki_path(@wiki, action: :new), class: "btn gl-button btn-confirm-secondary", role: "button", data: { qa_selector: 'new_page_button' } do
= s_("Wiki|New page") = s_("Wiki|New page")
---
title: Hide issue count and link in project list for projects with disabled issues
merge_request: 54275
author: Simon Stieger @sim0
type: fixed
---
title: Refactor blame view
merge_request: 55488
author:
type: performance
---
title: Move from btn-success to btn-confirm in shared/wikis directory
merge_request: 55316
author: Yogi (@yo)
type: changed
---
title: Move from btn-success to btn-confirm in shared directory
merge_request: 55317
author: Yogi (@yo)
type: changed
...@@ -587,7 +587,7 @@ export default { ...@@ -587,7 +587,7 @@ export default {
}) })
.then(({ data }) => { .then(({ data }) => {
if (data?.epicBoardListCreate?.errors.length) { if (data?.epicBoardListCreate?.errors.length) {
commit(types.CREATE_LIST_FAILURE); commit(types.CREATE_LIST_FAILURE, data.epicBoardListCreate.errors[0]);
} else { } else {
const list = data.epicBoardListCreate?.list; const list = data.epicBoardListCreate?.list;
dispatch('addList', list); dispatch('addList', list);
......
<script> <script>
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import { SAVE_ERROR } from '../constants'; import { SAVE_ERROR } from '../constants';
import createComplianceFrameworkMutation from '../graphql/queries/create_compliance_framework.mutation.graphql'; import createComplianceFrameworkMutation from '../graphql/queries/create_compliance_framework.mutation.graphql';
import { initialiseFormData } from '../utils'; import { initialiseFormData } from '../utils';
...@@ -41,6 +42,7 @@ export default { ...@@ -41,6 +42,7 @@ export default {
}, },
methods: { methods: {
setError(error, userFriendlyText) { setError(error, userFriendlyText) {
this.saving = false;
this.errorMessage = userFriendlyText; this.errorMessage = userFriendlyText;
Sentry.captureException(error); Sentry.captureException(error);
}, },
...@@ -70,16 +72,16 @@ export default { ...@@ -70,16 +72,16 @@ export default {
if (error) { if (error) {
this.setError(new Error(error), error); this.setError(new Error(error), error);
} else { } else {
this.saving = false;
visitUrl(this.groupEditPath); visitUrl(this.groupEditPath);
} }
} catch (e) { } catch (e) {
this.setError(e, SAVE_ERROR); this.setError(e, SAVE_ERROR);
} }
this.saving = false;
}, },
}, },
i18n: {
submitButtonText: s__('ComplianceFrameworks|Add framework'),
},
}; };
</script> </script>
<template> <template>
...@@ -91,6 +93,7 @@ export default { ...@@ -91,6 +93,7 @@ export default {
:description.sync="formData.description" :description.sync="formData.description"
:pipeline-configuration-full-path.sync="formData.pipelineConfigurationFullPath" :pipeline-configuration-full-path.sync="formData.pipelineConfigurationFullPath"
:color.sync="formData.color" :color.sync="formData.color"
:submit-button-text="$options.i18n.submitButtonText"
@submit="onSubmit" @submit="onSubmit"
/> />
</form-status> </form-status>
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { convertToGraphQLId } from '~/graphql_shared/utils'; import { convertToGraphQLId } from '~/graphql_shared/utils';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { FETCH_ERROR, SAVE_ERROR } from '../constants'; import { FETCH_ERROR, SAVE_ERROR } from '../constants';
import getComplianceFrameworkQuery from '../graphql/queries/get_compliance_framework.query.graphql'; import getComplianceFrameworkQuery from '../graphql/queries/get_compliance_framework.query.graphql';
...@@ -104,6 +105,7 @@ export default { ...@@ -104,6 +105,7 @@ export default {
Sentry.captureException(error); Sentry.captureException(error);
}, },
setSavingError(error, userFriendlyText) { setSavingError(error, userFriendlyText) {
this.saving = false;
this.saveErrorMessage = userFriendlyText; this.saveErrorMessage = userFriendlyText;
Sentry.captureException(error); Sentry.captureException(error);
}, },
...@@ -133,16 +135,16 @@ export default { ...@@ -133,16 +135,16 @@ export default {
if (error) { if (error) {
this.setSavingError(new Error(error), error); this.setSavingError(new Error(error), error);
} else { } else {
this.saving = false;
visitUrl(this.groupEditPath); visitUrl(this.groupEditPath);
} }
} catch (e) { } catch (e) {
this.setSavingError(e, SAVE_ERROR); this.setSavingError(e, SAVE_ERROR);
} }
this.saving = false;
}, },
}, },
i18n: {
submitButtonText: __('Save changes'),
},
}; };
</script> </script>
<template> <template>
...@@ -155,6 +157,7 @@ export default { ...@@ -155,6 +157,7 @@ export default {
:description.sync="formData.description" :description.sync="formData.description"
:pipeline-configuration-full-path.sync="formData.pipelineConfigurationFullPath" :pipeline-configuration-full-path.sync="formData.pipelineConfigurationFullPath"
:color.sync="formData.color" :color.sync="formData.color"
:submit-button-text="$options.i18n.submitButtonText"
@submit="onSubmit" @submit="onSubmit"
/> />
</form-status> </form-status>
......
...@@ -98,6 +98,9 @@ export default { ...@@ -98,6 +98,9 @@ export default {
}, },
}, },
methods: { methods: {
dismissAlertMessage() {
this.message = null;
},
markForDeletion(framework) { markForDeletion(framework) {
this.markedForDeletion = framework; this.markedForDeletion = framework;
this.$refs.modal.show(); this.$refs.modal.show();
...@@ -140,6 +143,7 @@ export default { ...@@ -140,6 +143,7 @@ export default {
class="gl-mt-5" class="gl-mt-5"
:variant="alertVariant" :variant="alertVariant"
:dismissible="alertDismissible" :dismissible="alertDismissible"
@dismiss="dismissAlertMessage"
> >
{{ alertMessage }} {{ alertMessage }}
</gl-alert> </gl-alert>
......
...@@ -47,6 +47,10 @@ export default { ...@@ -47,6 +47,10 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
submitButtonText: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -141,7 +145,6 @@ export default { ...@@ -141,7 +145,6 @@ export default {
'ComplianceFrameworks|Could not find this configuration location, please try a different location', 'ComplianceFrameworks|Could not find this configuration location, please try a different location',
), ),
colorInputLabel: __('Background color'), colorInputLabel: __('Background color'),
submitBtnText: __('Save changes'),
cancelBtnText: __('Cancel'), cancelBtnText: __('Cancel'),
}, },
}; };
...@@ -223,7 +226,7 @@ export default { ...@@ -223,7 +226,7 @@ export default {
class="js-no-auto-disable" class="js-no-auto-disable"
data-testid="submit-btn" data-testid="submit-btn"
:disabled="disableSubmitBtn" :disabled="disableSubmitBtn"
>{{ $options.i18n.submitBtnText }}</gl-button >{{ submitButtonText }}</gl-button
> >
<gl-button :href="groupEditPath" data-testid="cancel-btn">{{ <gl-button :href="groupEditPath" data-testid="cancel-btn">{{
$options.i18n.cancelBtnText $options.i18n.cancelBtnText
......
...@@ -44,6 +44,12 @@ export default { ...@@ -44,6 +44,12 @@ export default {
emitInput(value) { emitInput(value) {
this.$emit('input', value); this.$emit('input', value);
}, },
async emitDropdownShow() {
this.$emit('dropdown-show');
// Focus on the search box when the dropdown is opened.
await this.$nextTick();
this.$refs.searchBox?.focusInput();
},
}, },
}; };
</script> </script>
...@@ -56,6 +62,8 @@ export default { ...@@ -56,6 +62,8 @@ export default {
menu-class="dropdown-extended-height" menu-class="dropdown-extended-height"
:header-text="name" :header-text="name"
toggle-class="gl-w-full" toggle-class="gl-w-full"
@show="emitDropdownShow"
@hide="$emit('dropdown-hide')"
> >
<template #button-content> <template #button-content>
<gl-truncate <gl-truncate
...@@ -69,11 +77,14 @@ export default { ...@@ -69,11 +77,14 @@ export default {
<gl-icon name="chevron-down" class="gl-flex-shrink-0 gl-ml-auto" /> <gl-icon name="chevron-down" class="gl-flex-shrink-0 gl-ml-auto" />
</template> </template>
<gl-search-box-by-type <template v-if="showSearchBox" #header>
v-if="showSearchBox" <gl-search-box-by-type
:placeholder="__('Filter...')" ref="searchBox"
@input="emitInput" :placeholder="__('Search')"
/> autocomplete="off"
@input="emitInput"
/>
</template>
<slot> <slot>
<gl-dropdown-text> <gl-dropdown-text>
......
...@@ -1026,14 +1026,14 @@ describe('moveIssue', () => { ...@@ -1026,14 +1026,14 @@ describe('moveIssue', () => {
data: { data: {
epicBoardListCreate: { epicBoardListCreate: {
list: {}, list: {},
errors: [{ foo: 'bar' }], errors: ['foo'],
}, },
}, },
}); });
await actions.createEpicList({ getters, state, commit, dispatch }, { backlog: true }); await actions.createEpicList({ getters, state, commit, dispatch }, { backlog: true });
expect(commit).toHaveBeenCalledWith(types.CREATE_LIST_FAILURE); expect(commit).toHaveBeenCalledWith(types.CREATE_LIST_FAILURE, 'foo');
}); });
it('highlights list and does not re-query if it already exists', async () => { it('highlights list and does not re-query if it already exists', async () => {
......
...@@ -67,6 +67,16 @@ describe('CreateForm', () => { ...@@ -67,6 +67,16 @@ describe('CreateForm', () => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('initialized', () => {
beforeEach(() => {
wrapper = createComponent();
});
it('sets the submit button text on the form', () => {
expect(findForm().props('submitButtonText')).toBe('Add framework');
});
});
describe('loading', () => { describe('loading', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent(); wrapper = createComponent();
...@@ -120,13 +130,13 @@ describe('CreateForm', () => { ...@@ -120,13 +130,13 @@ describe('CreateForm', () => {
expect(captureExceptionSpy).toHaveBeenCalledWith(sentrySaveError); expect(captureExceptionSpy).toHaveBeenCalledWith(sentrySaveError);
}); });
it('saves inputted values and redirects', async () => { it('saves inputted values, redirects and continues to show loading while redirecting', async () => {
wrapper = createComponent([[createComplianceFrameworkMutation, create]]); wrapper = createComponent([[createComplianceFrameworkMutation, create]]);
await submitForm(name, description, pipelineConfigurationFullPath, color); await submitForm(name, description, pipelineConfigurationFullPath, color);
expect(create).toHaveBeenCalledWith(creationProps); expect(create).toHaveBeenCalledWith(creationProps);
expect(findFormStatus().props('loading')).toBe(false); expect(findFormStatus().props('loading')).toBe(true);
expect(visitUrl).toHaveBeenCalledWith(propsData.groupEditPath); expect(visitUrl).toHaveBeenCalledWith(propsData.groupEditPath);
}); });
}); });
......
...@@ -104,6 +104,7 @@ describe('EditForm', () => { ...@@ -104,6 +104,7 @@ describe('EditForm', () => {
name: frameworkFoundResponse.name, name: frameworkFoundResponse.name,
pipelineConfigurationFullPath: frameworkFoundResponse.pipelineConfigurationFullPath, pipelineConfigurationFullPath: frameworkFoundResponse.pipelineConfigurationFullPath,
pipelineConfigurationFullPathEnabled: true, pipelineConfigurationFullPathEnabled: true,
submitButtonText: 'Save changes',
}); });
expect(findForm().exists()).toBe(true); expect(findForm().exists()).toBe(true);
}); });
...@@ -183,7 +184,7 @@ describe('EditForm', () => { ...@@ -183,7 +184,7 @@ describe('EditForm', () => {
expect(Sentry.captureException.mock.calls[0][0]).toStrictEqual(sentrySaveError); expect(Sentry.captureException.mock.calls[0][0]).toStrictEqual(sentrySaveError);
}); });
it('saves inputted values and redirects', async () => { it('saves inputted values, redirects and continues to show loading while redirecting', async () => {
wrapper = createComponent([ wrapper = createComponent([
[getComplianceFrameworkQuery, fetchOne], [getComplianceFrameworkQuery, fetchOne],
[updateComplianceFrameworkMutation, update], [updateComplianceFrameworkMutation, update],
...@@ -192,7 +193,7 @@ describe('EditForm', () => { ...@@ -192,7 +193,7 @@ describe('EditForm', () => {
await submitForm(name, description, pipelineConfigurationFullPath, color); await submitForm(name, description, pipelineConfigurationFullPath, color);
expect(update).toHaveBeenCalledWith(updateProps); expect(update).toHaveBeenCalledWith(updateProps);
expect(findFormStatus().props('loading')).toBe(false); expect(findFormStatus().props('loading')).toBe(true);
expect(visitUrl).toHaveBeenCalledWith(propsData.groupEditPath); expect(visitUrl).toHaveBeenCalledWith(propsData.groupEditPath);
}); });
}); });
......
import { GlAlert, GlButton, GlLoadingIcon, GlTab, GlTabs } from '@gitlab/ui'; import { GlAlert, GlButton, GlLoadingIcon, GlTab, GlTabs } from '@gitlab/ui';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import DeleteModal from 'ee/groups/settings/compliance_frameworks/components/delete_modal.vue'; import DeleteModal from 'ee/groups/settings/compliance_frameworks/components/delete_modal.vue';
...@@ -256,6 +257,14 @@ describe('List', () => { ...@@ -256,6 +257,14 @@ describe('List', () => {
expect(findAlert().props('variant')).toBe('info'); expect(findAlert().props('variant')).toBe('info');
expect(findAlert().text()).toBe('Compliance framework deleted successfully'); expect(findAlert().text()).toBe('Compliance framework deleted successfully');
}); });
it('can dismiss the alert message', async () => {
findAlert().vm.$emit('dismiss');
await nextTick();
expect(findAlert().exists()).toBe(false);
});
}); });
}); });
}); });
......
...@@ -10,7 +10,11 @@ import { GlFormGroup, GlFormInput } from '../stubs'; ...@@ -10,7 +10,11 @@ import { GlFormGroup, GlFormInput } from '../stubs';
describe('SharedForm', () => { describe('SharedForm', () => {
let wrapper; let wrapper;
const defaultPropsData = { groupEditPath: 'group-1', pipelineConfigurationFullPathEnabled: true }; const defaultPropsData = {
groupEditPath: 'group-1',
pipelineConfigurationFullPathEnabled: true,
submitButtonText: 'Save changes',
};
const findForm = () => wrapper.findComponent(GlForm); const findForm = () => wrapper.findComponent(GlForm);
const findNameGroup = () => wrapper.find('[data-testid="name-input-group"]'); const findNameGroup = () => wrapper.find('[data-testid="name-input-group"]');
...@@ -65,6 +69,12 @@ describe('SharedForm', () => { ...@@ -65,6 +69,12 @@ describe('SharedForm', () => {
expect(findNameGroup().text()).toContain('Use :: to create a scoped set (eg. SOX::AWS)'); expect(findNameGroup().text()).toContain('Use :: to create a scoped set (eg. SOX::AWS)');
}); });
it('sets the submit button text from the property', () => {
wrapper = createComponent();
expect(findSubmitBtn().text()).toBe(defaultPropsData.submitButtonText);
});
it.each([true, false])( it.each([true, false])(
'renders the pipeline configuration correctly when enabled is %s', 'renders the pipeline configuration correctly when enabled is %s',
(enabled) => { (enabled) => {
......
...@@ -10,15 +10,16 @@ describe('Filter Body component', () => { ...@@ -10,15 +10,16 @@ describe('Filter Body component', () => {
selectedOptions: [], selectedOptions: [],
}; };
const createComponent = (props, slotContent = '') => { const createComponent = (props, options) => {
wrapper = mount(FilterBody, { wrapper = mount(FilterBody, {
propsData: { ...defaultProps, ...props }, propsData: { ...defaultProps, ...props },
slots: { default: slotContent }, ...options,
}); });
}; };
const dropdown = () => wrapper.findComponent(GlDropdown);
const dropdownButton = () => wrapper.find('.dropdown-toggle'); const dropdownButton = () => wrapper.find('.dropdown-toggle');
const searchBox = () => wrapper.find(GlSearchBoxByType); const searchBox = () => wrapper.findComponent(GlSearchBoxByType);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -28,7 +29,21 @@ describe('Filter Body component', () => { ...@@ -28,7 +29,21 @@ describe('Filter Body component', () => {
createComponent(); createComponent();
expect(wrapper.find('[data-testid="name"]').text()).toBe(defaultProps.name); expect(wrapper.find('[data-testid="name"]').text()).toBe(defaultProps.name);
expect(wrapper.find(GlDropdown).props('headerText')).toBe(defaultProps.name); expect(dropdown().props('headerText')).toBe(defaultProps.name);
});
it('emits dropdown-show event when dropdown is shown', () => {
createComponent();
dropdown().vm.$emit('show');
expect(wrapper.emitted('dropdown-show')).toHaveLength(1);
});
it('emits dropdown-hide event when dropdown is hidden', () => {
createComponent();
dropdown().vm.$emit('hide');
expect(wrapper.emitted('dropdown-hide')).toHaveLength(1);
}); });
describe('dropdown button', () => { describe('dropdown button', () => {
...@@ -56,6 +71,15 @@ describe('Filter Body component', () => { ...@@ -56,6 +71,15 @@ describe('Filter Body component', () => {
expect(searchBox().exists()).toBe(show); expect(searchBox().exists()).toBe(show);
}); });
it('is focused when the dropdown is opened', async () => {
createComponent({ showSearchBox: true }, { attachTo: document.body });
const spy = jest.spyOn(searchBox().vm, 'focusInput');
dropdown().vm.$emit('show');
await wrapper.vm.$nextTick();
expect(spy).toHaveBeenCalledTimes(1);
});
it('emits input event on component when search box input is changed', () => { it('emits input event on component when search box input is changed', () => {
const text = 'abc'; const text = 'abc';
createComponent({ showSearchBox: true }); createComponent({ showSearchBox: true });
...@@ -68,7 +92,7 @@ describe('Filter Body component', () => { ...@@ -68,7 +92,7 @@ describe('Filter Body component', () => {
describe('dropdown body', () => { describe('dropdown body', () => {
it('shows slot content', () => { it('shows slot content', () => {
const slotContent = 'some slot content'; const slotContent = 'some slot content';
createComponent({}, slotContent); createComponent({}, { slots: { default: slotContent } });
expect(wrapper.text()).toContain(slotContent); expect(wrapper.text()).toContain(slotContent);
}); });
......
...@@ -1330,7 +1330,7 @@ msgstr "" ...@@ -1330,7 +1330,7 @@ msgstr ""
msgid "A job artifact is an archive of files and directories saved by a job when it finishes." msgid "A job artifact is an archive of files and directories saved by a job when it finishes."
msgstr "" msgstr ""
msgid "A label list displays all issues with the selected label." msgid "A label list displays issues with the selected label."
msgstr "" msgstr ""
msgid "A limit of %{ci_project_subscriptions_limit} subscriptions to or from a project applies." msgid "A limit of %{ci_project_subscriptions_limit} subscriptions to or from a project applies."
...@@ -20298,7 +20298,7 @@ msgstr "" ...@@ -20298,7 +20298,7 @@ msgstr ""
msgid "New label" msgid "New label"
msgstr "" msgstr ""
msgid "New label list" msgid "New list"
msgstr "" msgstr ""
msgid "New merge request" msgid "New merge request"
...@@ -20520,9 +20520,6 @@ msgstr "" ...@@ -20520,9 +20520,6 @@ msgstr ""
msgid "No label" msgid "No label"
msgstr "" msgstr ""
msgid "No label selected"
msgstr ""
msgid "No labels with such name or description" msgid "No labels with such name or description"
msgstr "" msgstr ""
......
import { GlFormGroup, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import defaultState from '~/boards/stores/state';
import { mockLabelList } from '../mock_data';
Vue.use(Vuex);
describe('Board card layout', () => {
let wrapper;
const createStore = ({ actions = {}, getters = {}, state = {} } = {}) => {
return new Vuex.Store({
state: {
...defaultState,
...state,
},
actions,
getters,
});
};
const mountComponent = ({
loading = false,
formDescription = '',
searchLabel = '',
searchPlaceholder = '',
selectedId,
actions,
slots,
} = {}) => {
wrapper = extendedWrapper(
shallowMount(BoardAddNewColumnForm, {
stubs: {
GlFormGroup: true,
},
propsData: {
loading,
formDescription,
searchLabel,
searchPlaceholder,
selectedId,
},
slots,
store: createStore({
actions: {
setAddColumnFormVisibility: jest.fn(),
...actions,
},
}),
}),
);
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const formTitle = () => wrapper.findByTestId('board-add-column-form-title').text();
const findSearchInput = () => wrapper.find(GlSearchBoxByType);
const findSearchLabel = () => wrapper.find(GlFormGroup);
const cancelButton = () => wrapper.findByTestId('cancelAddNewColumn');
const submitButton = () => wrapper.findByTestId('addNewColumnButton');
it('shows form title & search input', () => {
mountComponent();
expect(formTitle()).toEqual(BoardAddNewColumnForm.i18n.newList);
expect(findSearchInput().exists()).toBe(true);
});
it('clicking cancel hides the form', () => {
const setAddColumnFormVisibility = jest.fn();
mountComponent({
actions: {
setAddColumnFormVisibility,
},
});
cancelButton().vm.$emit('click');
expect(setAddColumnFormVisibility).toHaveBeenCalledWith(expect.anything(), false);
});
it('sets placeholder and description from props', () => {
const props = {
formDescription: 'Some description of a list',
};
mountComponent(props);
expect(wrapper.html()).toHaveText(props.formDescription);
});
describe('items', () => {
const mountWithItems = (loading) =>
mountComponent({
loading,
slots: {
items: '<div class="item-slot">Some kind of list</div>',
},
});
it('hides items slot and shows skeleton while loading', () => {
mountWithItems(true);
expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
expect(wrapper.find('.item-slot').exists()).toBe(false);
});
it('shows items slot and hides skeleton while not loading', () => {
mountWithItems(false);
expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(false);
expect(wrapper.find('.item-slot').exists()).toBe(true);
});
});
describe('search box', () => {
it('sets label and placeholder text from props', () => {
const props = {
searchLabel: 'Some items',
searchPlaceholder: 'Search for an item',
};
mountComponent(props);
expect(findSearchLabel().attributes('label')).toEqual(props.searchLabel);
expect(findSearchInput().attributes('placeholder')).toEqual(props.searchPlaceholder);
});
it('emits filter event on input', () => {
mountComponent();
const searchText = 'some text';
findSearchInput().vm.$emit('input', searchText);
expect(wrapper.emitted('filter-items')).toEqual([[searchText]]);
});
});
describe('Add list button', () => {
it('is disabled if no item is selected', () => {
mountComponent();
expect(submitButton().props('disabled')).toBe(true);
});
it('emits add-list event on click', async () => {
mountComponent({
selectedId: mockLabelList.label.id,
});
await nextTick();
submitButton().vm.$emit('click');
expect(wrapper.emitted('add-list')).toEqual([[]]);
});
});
});
import { GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue'; import Vue, { nextTick } from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardAddNewColumn from '~/boards/components/board_add_new_column.vue'; import BoardAddNewColumn from '~/boards/components/board_add_new_column.vue';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import defaultState from '~/boards/stores/state'; import defaultState from '~/boards/stores/state';
import { mockLabelList } from '../mock_data'; import { mockLabelList } from '../mock_data';
...@@ -11,7 +11,6 @@ Vue.use(Vuex); ...@@ -11,7 +11,6 @@ Vue.use(Vuex);
describe('Board card layout', () => { describe('Board card layout', () => {
let wrapper; let wrapper;
let shouldUseGraphQL;
const createStore = ({ actions = {}, getters = {}, state = {} } = {}) => { const createStore = ({ actions = {}, getters = {}, state = {} } = {}) => {
return new Vuex.Store({ return new Vuex.Store({
...@@ -25,19 +24,16 @@ describe('Board card layout', () => { ...@@ -25,19 +24,16 @@ describe('Board card layout', () => {
}; };
const mountComponent = ({ const mountComponent = ({
selectedLabelId, selectedId,
labels = [], labels = [],
getListByLabelId = jest.fn(), getListByLabelId = jest.fn(),
actions = {}, actions = {},
} = {}) => { } = {}) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
shallowMount(BoardAddNewColumn, { shallowMount(BoardAddNewColumn, {
stubs: {
GlFormGroup: true,
},
data() { data() {
return { return {
selectedLabelId, selectedId,
}; };
}, },
store: createStore({ store: createStore({
...@@ -47,12 +43,13 @@ describe('Board card layout', () => { ...@@ -47,12 +43,13 @@ describe('Board card layout', () => {
...actions, ...actions,
}, },
getters: { getters: {
shouldUseGraphQL: () => shouldUseGraphQL, shouldUseGraphQL: () => true,
getListByLabelId: () => getListByLabelId, getListByLabelId: () => getListByLabelId,
}, },
state: { state: {
labels, labels,
labelsLoading: false, labelsLoading: false,
isEpicBoard: false,
}, },
}), }),
provide: { provide: {
...@@ -64,65 +61,32 @@ describe('Board card layout', () => { ...@@ -64,65 +61,32 @@ describe('Board card layout', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
});
const formTitle = () => wrapper.findByTestId('board-add-column-form-title').text();
const findSearchInput = () => wrapper.find(GlSearchBoxByType);
const cancelButton = () => wrapper.findByTestId('cancelAddNewColumn');
const submitButton = () => wrapper.findByTestId('addNewColumnButton');
beforeEach(() => {
shouldUseGraphQL = true;
});
it('shows form title & search input', () => {
mountComponent();
expect(formTitle()).toEqual(BoardAddNewColumn.i18n.newLabelList);
expect(findSearchInput().exists()).toBe(true);
});
it('clicking cancel hides the form', () => {
const setAddColumnFormVisibility = jest.fn();
mountComponent({
actions: {
setAddColumnFormVisibility,
},
});
cancelButton().vm.$emit('click');
expect(setAddColumnFormVisibility).toHaveBeenCalledWith(expect.anything(), false);
}); });
describe('Add list button', () => { describe('Add list button', () => {
it('is disabled if no item is selected', () => { it('calls addList', async () => {
mountComponent(); const getListByLabelId = jest.fn().mockReturnValue(null);
expect(submitButton().props('disabled')).toBe(true);
});
it('adds a new list on click', async () => {
const labelId = mockLabelList.label.id;
const highlightList = jest.fn(); const highlightList = jest.fn();
const createList = jest.fn(); const createList = jest.fn();
mountComponent({ mountComponent({
labels: [mockLabelList.label], labels: [mockLabelList.label],
selectedLabelId: labelId, selectedId: mockLabelList.label.id,
getListByLabelId,
actions: { actions: {
createList, createList,
highlightList, highlightList,
}, },
}); });
await nextTick(); wrapper.findComponent(BoardAddNewColumnForm).vm.$emit('add-list');
submitButton().vm.$emit('click'); await nextTick();
expect(highlightList).not.toHaveBeenCalled(); expect(highlightList).not.toHaveBeenCalled();
expect(createList).toHaveBeenCalledWith(expect.anything(), { labelId }); expect(createList).toHaveBeenCalledWith(expect.anything(), {
labelId: mockLabelList.label.id,
});
}); });
it('highlights existing list if trying to re-add', async () => { it('highlights existing list if trying to re-add', async () => {
...@@ -132,7 +96,7 @@ describe('Board card layout', () => { ...@@ -132,7 +96,7 @@ describe('Board card layout', () => {
mountComponent({ mountComponent({
labels: [mockLabelList.label], labels: [mockLabelList.label],
selectedLabelId: mockLabelList.label.id, selectedId: mockLabelList.label.id,
getListByLabelId, getListByLabelId,
actions: { actions: {
createList, createList,
...@@ -140,9 +104,9 @@ describe('Board card layout', () => { ...@@ -140,9 +104,9 @@ describe('Board card layout', () => {
}, },
}); });
await nextTick(); wrapper.findComponent(BoardAddNewColumnForm).vm.$emit('add-list');
submitButton().vm.$emit('click'); await nextTick();
expect(highlightList).toHaveBeenCalledWith(expect.anything(), mockLabelList.id); expect(highlightList).toHaveBeenCalledWith(expect.anything(), mockLabelList.id);
expect(createList).not.toHaveBeenCalled(); expect(createList).not.toHaveBeenCalled();
......
...@@ -293,7 +293,7 @@ describe('createIssueList', () => { ...@@ -293,7 +293,7 @@ describe('createIssueList', () => {
data: { data: {
boardListCreate: { boardListCreate: {
list: {}, list: {},
errors: [{ foo: 'bar' }], errors: ['foo'],
}, },
}, },
}), }),
...@@ -301,7 +301,7 @@ describe('createIssueList', () => { ...@@ -301,7 +301,7 @@ describe('createIssueList', () => {
await actions.createIssueList({ getters, state, commit, dispatch }, { backlog: true }); await actions.createIssueList({ getters, state, commit, dispatch }, { backlog: true });
expect(commit).toHaveBeenCalledWith(types.CREATE_LIST_FAILURE); expect(commit).toHaveBeenCalledWith(types.CREATE_LIST_FAILURE, 'foo');
}); });
it('highlights list and does not re-query if it already exists', async () => { it('highlights list and does not re-query if it already exists', async () => {
......
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