Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
16e3c34cac
commit
4dff02cf71
|
@ -0,0 +1,16 @@
|
||||||
|
#
|
||||||
|
# This list of browsers is a conservative first definition, based on
|
||||||
|
# https://docs.gitlab.com/ee/install/requirements.html#supported-web-browsers
|
||||||
|
# with the following reasoning:
|
||||||
|
#
|
||||||
|
# - Edge: Pick the last two major version before the Chrome switch
|
||||||
|
# - Rest: We should support the latest ESR of Firefox: 68, because it used quite a lot.
|
||||||
|
# For the rest, pick browser versions that have a similar age to Firefox 68.
|
||||||
|
#
|
||||||
|
# See also this follow-up epic:
|
||||||
|
# https://gitlab.com/groups/gitlab-org/-/epics/3957
|
||||||
|
#
|
||||||
|
chrome >= 73
|
||||||
|
edge >= 17
|
||||||
|
firefox >= 68
|
||||||
|
safari >= 12
|
|
@ -31,7 +31,6 @@ If applicable, any groups/projects that are happy to have this feature turned on
|
||||||
|
|
||||||
## Roll Out Steps
|
## Roll Out Steps
|
||||||
|
|
||||||
- [ ] Confirm that QA tests pass with the feature flag enabled (if you're unsure how, contact the relevant [stable counterpart in the Quality department](https://about.gitlab.com/handbook/engineering/quality/#individual-contributors))
|
|
||||||
- [ ] Enable on staging (`/chatops run feature set feature_name true --staging`)
|
- [ ] Enable on staging (`/chatops run feature set feature_name true --staging`)
|
||||||
- [ ] Test on staging
|
- [ ] Test on staging
|
||||||
- [ ] Ensure that documentation has been updated
|
- [ ] Ensure that documentation has been updated
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -284,6 +284,7 @@ gem 'gitlab_chronic_duration', '~> 0.10.6.2'
|
||||||
gem 'rack-proxy', '~> 0.6.0'
|
gem 'rack-proxy', '~> 0.6.0'
|
||||||
|
|
||||||
gem 'sassc-rails', '~> 2.1.0'
|
gem 'sassc-rails', '~> 2.1.0'
|
||||||
|
gem 'autoprefixer-rails', '10.2.0.0'
|
||||||
gem 'terser', '1.0.2'
|
gem 'terser', '1.0.2'
|
||||||
|
|
||||||
gem 'addressable', '~> 2.7'
|
gem 'addressable', '~> 2.7'
|
||||||
|
@ -336,6 +337,7 @@ end
|
||||||
group :development do
|
group :development do
|
||||||
gem 'brakeman', '~> 4.2', require: false
|
gem 'brakeman', '~> 4.2', require: false
|
||||||
gem 'danger', '~> 8.0.6', require: false
|
gem 'danger', '~> 8.0.6', require: false
|
||||||
|
gem 'lefthook', '~> 0.7', require: false
|
||||||
|
|
||||||
gem 'letter_opener_web', '~> 1.3.4'
|
gem 'letter_opener_web', '~> 1.3.4'
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,8 @@ GEM
|
||||||
attr_encrypted (3.1.0)
|
attr_encrypted (3.1.0)
|
||||||
encryptor (~> 3.0.0)
|
encryptor (~> 3.0.0)
|
||||||
attr_required (1.0.1)
|
attr_required (1.0.1)
|
||||||
|
autoprefixer-rails (10.2.0.0)
|
||||||
|
execjs
|
||||||
awesome_print (1.8.0)
|
awesome_print (1.8.0)
|
||||||
awrence (1.1.1)
|
awrence (1.1.1)
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.1.0)
|
||||||
|
@ -658,6 +660,7 @@ GEM
|
||||||
rest-client (~> 2.0)
|
rest-client (~> 2.0)
|
||||||
launchy (2.4.3)
|
launchy (2.4.3)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
|
lefthook (0.7.2)
|
||||||
letter_opener (1.7.0)
|
letter_opener (1.7.0)
|
||||||
launchy (~> 2.2)
|
launchy (~> 2.2)
|
||||||
letter_opener_web (1.3.4)
|
letter_opener_web (1.3.4)
|
||||||
|
@ -1285,6 +1288,7 @@ DEPENDENCIES
|
||||||
asciidoctor-plantuml (~> 0.0.12)
|
asciidoctor-plantuml (~> 0.0.12)
|
||||||
atlassian-jwt (~> 0.2.0)
|
atlassian-jwt (~> 0.2.0)
|
||||||
attr_encrypted (~> 3.1.0)
|
attr_encrypted (~> 3.1.0)
|
||||||
|
autoprefixer-rails (= 10.2.0.0)
|
||||||
awesome_print
|
awesome_print
|
||||||
aws-sdk-cloudformation (~> 1)
|
aws-sdk-cloudformation (~> 1)
|
||||||
aws-sdk-core (~> 3)
|
aws-sdk-core (~> 3)
|
||||||
|
@ -1410,6 +1414,7 @@ DEPENDENCIES
|
||||||
knapsack (~> 1.17)
|
knapsack (~> 1.17)
|
||||||
kramdown (~> 2.3.0)
|
kramdown (~> 2.3.0)
|
||||||
kubeclient (~> 4.9.1)
|
kubeclient (~> 4.9.1)
|
||||||
|
lefthook (~> 0.7)
|
||||||
letter_opener_web (~> 1.3.4)
|
letter_opener_web (~> 1.3.4)
|
||||||
license_finder (~> 6.0)
|
license_finder (~> 6.0)
|
||||||
licensee (~> 8.9)
|
licensee (~> 8.9)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import BoardCardLayout from './board_card_layout.vue';
|
import BoardCardLayout from './board_card_layout.vue';
|
||||||
|
import BoardCardLayoutDeprecated from './board_card_layout_deprecated.vue';
|
||||||
import eventHub from '../eventhub';
|
import eventHub from '../eventhub';
|
||||||
import sidebarEventHub from '~/sidebar/event_hub';
|
import sidebarEventHub from '~/sidebar/event_hub';
|
||||||
import boardsStore from '../stores/boards_store';
|
import boardsStore from '../stores/boards_store';
|
||||||
|
@ -7,7 +8,7 @@ import boardsStore from '../stores/boards_store';
|
||||||
export default {
|
export default {
|
||||||
name: 'BoardsIssueCard',
|
name: 'BoardsIssueCard',
|
||||||
components: {
|
components: {
|
||||||
BoardCardLayout,
|
BoardCardLayout: gon.features?.graphqlBoardLists ? BoardCardLayout : BoardCardLayoutDeprecated,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
list: {
|
list: {
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapGetters } from 'vuex';
|
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||||
import IssueCardInner from './issue_card_inner.vue';
|
import IssueCardInner from './issue_card_inner.vue';
|
||||||
import IssueCardInnerDeprecated from './issue_card_inner_deprecated.vue';
|
|
||||||
import boardsStore from '../stores/boards_store';
|
|
||||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
|
||||||
import { ISSUABLE } from '~/boards/constants';
|
import { ISSUABLE } from '~/boards/constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BoardCardLayout',
|
name: 'BoardCardLayout',
|
||||||
components: {
|
components: {
|
||||||
IssueCardInner: gon.features?.graphqlBoardLists ? IssueCardInner : IssueCardInnerDeprecated,
|
IssueCardInner,
|
||||||
},
|
},
|
||||||
mixins: [glFeatureFlagMixin()],
|
|
||||||
props: {
|
props: {
|
||||||
list: {
|
list: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -42,17 +38,17 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showDetail: false,
|
showDetail: false,
|
||||||
multiSelect: boardsStore.multiSelect,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapState(['selectedBoardItems']),
|
||||||
...mapGetters(['isSwimlanesOn']),
|
...mapGetters(['isSwimlanesOn']),
|
||||||
multiSelectVisible() {
|
multiSelectVisible() {
|
||||||
return this.multiSelect.list.findIndex((issue) => issue.id === this.issue.id) > -1;
|
return this.selectedBoardItems.findIndex((boardItem) => boardItem.id === this.issue.id) > -1;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['setActiveId']),
|
...mapActions(['setActiveId', 'toggleBoardItemMultiSelection']),
|
||||||
mouseDown() {
|
mouseDown() {
|
||||||
this.showDetail = true;
|
this.showDetail = true;
|
||||||
},
|
},
|
||||||
|
@ -63,16 +59,16 @@ export default {
|
||||||
// Don't do anything if this happened on a no trigger element
|
// Don't do anything if this happened on a no trigger element
|
||||||
if (e.target.classList.contains('js-no-trigger')) return;
|
if (e.target.classList.contains('js-no-trigger')) return;
|
||||||
|
|
||||||
if (this.glFeatures.graphqlBoardLists || this.isSwimlanesOn) {
|
|
||||||
this.setActiveId({ id: this.issue.id, sidebarType: ISSUABLE });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMultiSelect = e.ctrlKey || e.metaKey;
|
const isMultiSelect = e.ctrlKey || e.metaKey;
|
||||||
|
|
||||||
|
if (!isMultiSelect) {
|
||||||
|
this.setActiveId({ id: this.issue.id, sidebarType: ISSUABLE });
|
||||||
|
} else {
|
||||||
|
this.toggleBoardItemMultiSelection(this.issue);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.showDetail || isMultiSelect) {
|
if (this.showDetail || isMultiSelect) {
|
||||||
this.showDetail = false;
|
this.showDetail = false;
|
||||||
this.$emit('show', { event: e, isMultiSelect });
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
<script>
|
||||||
|
import { mapActions, mapGetters } from 'vuex';
|
||||||
|
import IssueCardInner from './issue_card_inner.vue';
|
||||||
|
import IssueCardInnerDeprecated from './issue_card_inner_deprecated.vue';
|
||||||
|
import boardsStore from '../stores/boards_store';
|
||||||
|
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
|
import { ISSUABLE } from '~/boards/constants';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'BoardCardLayout',
|
||||||
|
components: {
|
||||||
|
IssueCardInner: gon.features?.graphqlBoardLists ? IssueCardInner : IssueCardInnerDeprecated,
|
||||||
|
},
|
||||||
|
mixins: [glFeatureFlagMixin()],
|
||||||
|
props: {
|
||||||
|
list: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
issue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showDetail: false,
|
||||||
|
multiSelect: boardsStore.multiSelect,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(['isSwimlanesOn']),
|
||||||
|
multiSelectVisible() {
|
||||||
|
return this.multiSelect.list.findIndex((issue) => issue.id === this.issue.id) > -1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['setActiveId']),
|
||||||
|
mouseDown() {
|
||||||
|
this.showDetail = true;
|
||||||
|
},
|
||||||
|
mouseMove() {
|
||||||
|
this.showDetail = false;
|
||||||
|
},
|
||||||
|
showIssue(e) {
|
||||||
|
// Don't do anything if this happened on a no trigger element
|
||||||
|
if (e.target.classList.contains('js-no-trigger')) return;
|
||||||
|
|
||||||
|
if (this.glFeatures.graphqlBoardLists || this.isSwimlanesOn) {
|
||||||
|
this.setActiveId({ id: this.issue.id, sidebarType: ISSUABLE });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMultiSelect = e.ctrlKey || e.metaKey;
|
||||||
|
|
||||||
|
if (this.showDetail || isMultiSelect) {
|
||||||
|
this.showDetail = false;
|
||||||
|
this.$emit('show', { event: e, isMultiSelect });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<li
|
||||||
|
:class="{
|
||||||
|
'multi-select': multiSelectVisible,
|
||||||
|
'user-can-drag': !disabled && issue.id,
|
||||||
|
'is-disabled': disabled || !issue.id,
|
||||||
|
'is-active': isActive,
|
||||||
|
}"
|
||||||
|
:index="index"
|
||||||
|
:data-issue-id="issue.id"
|
||||||
|
:data-issue-iid="issue.iid"
|
||||||
|
:data-issue-path="issue.referencePath"
|
||||||
|
data-testid="board_card"
|
||||||
|
class="board-card gl-p-5 gl-rounded-base"
|
||||||
|
@mousedown="mouseDown"
|
||||||
|
@mousemove="mouseMove"
|
||||||
|
@mouseup="showIssue($event)"
|
||||||
|
>
|
||||||
|
<issue-card-inner :list="list" :issue="issue" :update-filters="true" />
|
||||||
|
</li>
|
||||||
|
</template>
|
|
@ -14,6 +14,10 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
readonly: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -28,12 +32,14 @@ export default {
|
||||||
</p>
|
</p>
|
||||||
<gl-form-checkbox
|
<gl-form-checkbox
|
||||||
:checked="!hideBacklogList"
|
:checked="!hideBacklogList"
|
||||||
|
:disabled="readonly"
|
||||||
data-testid="backlog-list-checkbox"
|
data-testid="backlog-list-checkbox"
|
||||||
@change="$emit('update:hideBacklogList', !hideBacklogList)"
|
@change="$emit('update:hideBacklogList', !hideBacklogList)"
|
||||||
>{{ __('Show the Open list') }}
|
>{{ __('Show the Open list') }}
|
||||||
</gl-form-checkbox>
|
</gl-form-checkbox>
|
||||||
<gl-form-checkbox
|
<gl-form-checkbox
|
||||||
:checked="!hideClosedList"
|
:checked="!hideClosedList"
|
||||||
|
:disabled="readonly"
|
||||||
data-testid="closed-list-checkbox"
|
data-testid="closed-list-checkbox"
|
||||||
@change="$emit('update:hideClosedList', !hideClosedList)"
|
@change="$emit('update:hideClosedList', !hideClosedList)"
|
||||||
>{{ __('Show the Closed list') }}
|
>{{ __('Show the Closed list') }}
|
||||||
|
|
|
@ -308,6 +308,7 @@ export default {
|
||||||
<board-configuration-options
|
<board-configuration-options
|
||||||
:hide-backlog-list.sync="board.hide_backlog_list"
|
:hide-backlog-list.sync="board.hide_backlog_list"
|
||||||
:hide-closed-list.sync="board.hide_closed_list"
|
:hide-closed-list.sync="board.hide_closed_list"
|
||||||
|
:readonly="readonly"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<board-scope
|
<board-scope
|
||||||
|
|
|
@ -8,9 +8,8 @@ import {
|
||||||
GlDropdownDivider,
|
GlDropdownDivider,
|
||||||
GlLoadingIcon,
|
GlLoadingIcon,
|
||||||
} from '@gitlab/ui';
|
} from '@gitlab/ui';
|
||||||
import { fetchPolicies } from '~/lib/graphql';
|
|
||||||
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
|
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
|
||||||
import groupMilestones from '../../graphql/group_milestones.query.graphql';
|
import projectMilestones from '../../graphql/project_milestones.query.graphql';
|
||||||
import createFlash from '~/flash';
|
import createFlash from '~/flash';
|
||||||
import { __, s__ } from '~/locale';
|
import { __, s__ } from '~/locale';
|
||||||
|
|
||||||
|
@ -34,22 +33,21 @@ export default {
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
milestones: {
|
milestones: {
|
||||||
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
|
query: projectMilestones,
|
||||||
query: groupMilestones,
|
|
||||||
debounce: 250,
|
debounce: 250,
|
||||||
skip() {
|
skip() {
|
||||||
return !this.edit;
|
return !this.edit;
|
||||||
},
|
},
|
||||||
variables() {
|
variables() {
|
||||||
return {
|
return {
|
||||||
fullPath: this.groupFullPath,
|
fullPath: this.projectPath,
|
||||||
searchTitle: this.searchTitle,
|
searchTitle: this.searchTitle,
|
||||||
state: 'active',
|
state: 'active',
|
||||||
includeDescendants: true,
|
includeAncestors: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
update(data) {
|
update(data) {
|
||||||
const edges = data?.group?.milestones?.edges ?? [];
|
const edges = data?.project?.milestones?.edges ?? [];
|
||||||
return edges.map((item) => item.node);
|
return edges.map((item) => item.node);
|
||||||
},
|
},
|
||||||
error() {
|
error() {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
query groupMilestones(
|
query groupMilestones(
|
||||||
$fullPath: ID!
|
$fullPath: ID!
|
||||||
$state: MilestoneStateEnum
|
$state: MilestoneStateEnum
|
||||||
$includeDescendants: Boolean
|
$includeAncestors: Boolean
|
||||||
$searchTitle: String
|
$searchTitle: String
|
||||||
) {
|
) {
|
||||||
group(fullPath: $fullPath) {
|
project(fullPath: $fullPath) {
|
||||||
milestones(state: $state, includeDescendants: $includeDescendants, searchTitle: $searchTitle) {
|
milestones(state: $state, includeAncestors: $includeAncestors, searchTitle: $searchTitle) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
|
@ -534,6 +534,17 @@ export default {
|
||||||
commit(types.SET_SELECTED_PROJECT, project);
|
commit(types.SET_SELECTED_PROJECT, project);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleBoardItemMultiSelection: ({ commit, state }, boardItem) => {
|
||||||
|
const { selectedBoardItems } = state;
|
||||||
|
const index = selectedBoardItems.indexOf(boardItem);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
commit(types.ADD_BOARD_ITEM_TO_SELECTION, boardItem);
|
||||||
|
} else {
|
||||||
|
commit(types.REMOVE_BOARD_ITEM_FROM_SELECTION, boardItem);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
fetchBacklog: () => {
|
fetchBacklog: () => {
|
||||||
notImplemented();
|
notImplemented();
|
||||||
},
|
},
|
||||||
|
|
|
@ -40,3 +40,5 @@ export const REQUEST_GROUP_PROJECTS = 'REQUEST_GROUP_PROJECTS';
|
||||||
export const RECEIVE_GROUP_PROJECTS_SUCCESS = 'RECEIVE_GROUP_PROJECTS_SUCCESS';
|
export const RECEIVE_GROUP_PROJECTS_SUCCESS = 'RECEIVE_GROUP_PROJECTS_SUCCESS';
|
||||||
export const RECEIVE_GROUP_PROJECTS_FAILURE = 'RECEIVE_GROUP_PROJECTS_FAILURE';
|
export const RECEIVE_GROUP_PROJECTS_FAILURE = 'RECEIVE_GROUP_PROJECTS_FAILURE';
|
||||||
export const SET_SELECTED_PROJECT = 'SET_SELECTED_PROJECT';
|
export const SET_SELECTED_PROJECT = 'SET_SELECTED_PROJECT';
|
||||||
|
export const ADD_BOARD_ITEM_TO_SELECTION = 'ADD_BOARD_ITEM_TO_SELECTION';
|
||||||
|
export const REMOVE_BOARD_ITEM_FROM_SELECTION = 'REMOVE_BOARD_ITEM_FROM_SELECTION';
|
||||||
|
|
|
@ -258,4 +258,16 @@ export default {
|
||||||
[mutationTypes.SET_SELECTED_PROJECT]: (state, project) => {
|
[mutationTypes.SET_SELECTED_PROJECT]: (state, project) => {
|
||||||
state.selectedProject = project;
|
state.selectedProject = project;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[mutationTypes.ADD_BOARD_ITEM_TO_SELECTION]: (state, boardItem) => {
|
||||||
|
state.selectedBoardItems = [...state.selectedBoardItems, boardItem];
|
||||||
|
},
|
||||||
|
|
||||||
|
[mutationTypes.REMOVE_BOARD_ITEM_FROM_SELECTION]: (state, boardItem) => {
|
||||||
|
Vue.set(
|
||||||
|
state,
|
||||||
|
'selectedBoardItems',
|
||||||
|
state.selectedBoardItems.filter((obj) => obj !== boardItem),
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,6 +15,7 @@ export default () => ({
|
||||||
filterParams: {},
|
filterParams: {},
|
||||||
boardConfig: {},
|
boardConfig: {},
|
||||||
labels: [],
|
labels: [],
|
||||||
|
selectedBoardItems: [],
|
||||||
groupProjects: [],
|
groupProjects: [],
|
||||||
groupProjectsFlags: {
|
groupProjectsFlags: {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
|
|
@ -39,13 +39,17 @@ export default {
|
||||||
this.$emit('toggle');
|
this.$emit('toggle');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ICON_CLASS: 'gl-mr-3 gl-cursor-pointer',
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<li :class="className" class="replies-toggle js-toggle-replies">
|
<li
|
||||||
|
:class="className"
|
||||||
|
class="replies-toggle js-toggle-replies gl-display-flex! gl-align-items-center gl-flex-wrap"
|
||||||
|
>
|
||||||
<template v-if="collapsed">
|
<template v-if="collapsed">
|
||||||
<gl-icon name="chevron-right" @click.native="toggle" />
|
<gl-icon :class="$options.ICON_CLASS" name="chevron-right" @click.native="toggle" />
|
||||||
<div>
|
<div>
|
||||||
<user-avatar-link
|
<user-avatar-link
|
||||||
v-for="author in uniqueAuthors"
|
v-for="author in uniqueAuthors"
|
||||||
|
@ -59,7 +63,7 @@ export default {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<gl-button
|
<gl-button
|
||||||
class="js-replies-text"
|
class="js-replies-text gl-mr-2"
|
||||||
category="tertiary"
|
category="tertiary"
|
||||||
variant="link"
|
variant="link"
|
||||||
data-qa-selector="expand_replies_button"
|
data-qa-selector="expand_replies_button"
|
||||||
|
@ -68,18 +72,19 @@ export default {
|
||||||
{{ replies.length }} {{ n__('reply', 'replies', replies.length) }}
|
{{ replies.length }} {{ n__('reply', 'replies', replies.length) }}
|
||||||
</gl-button>
|
</gl-button>
|
||||||
{{ __('Last reply by') }}
|
{{ __('Last reply by') }}
|
||||||
<a :href="lastReply.author.path" class="btn btn-link author-link">
|
<a :href="lastReply.author.path" class="btn btn-link author-link gl-mx-2">
|
||||||
{{ lastReply.author.name }}
|
{{ lastReply.author.name }}
|
||||||
</a>
|
</a>
|
||||||
<time-ago-tooltip :time="lastReply.created_at" tooltip-placement="bottom" />
|
<time-ago-tooltip :time="lastReply.created_at" tooltip-placement="bottom" />
|
||||||
</template>
|
</template>
|
||||||
<span
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="collapse-replies-btn js-collapse-replies"
|
class="collapse-replies-btn js-collapse-replies gl-display-flex align-items-center"
|
||||||
data-qa-selector="collapse_replies_button"
|
data-qa-selector="collapse_replies_button"
|
||||||
@click="toggle"
|
@click="toggle"
|
||||||
>
|
>
|
||||||
<gl-icon name="chevron-down" /> {{ s__('Notes|Collapse replies') }}
|
<gl-icon :class="$options.ICON_CLASS" name="chevron-down" />
|
||||||
</span>
|
<span class="gl-cursor-pointer">{{ s__('Notes|Collapse replies') }}</span>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import Search from './search';
|
|
||||||
import { initSearchApp } from '~/search';
|
import { initSearchApp } from '~/search';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
initSearchApp(); // Vue Bootstrap
|
initSearchApp();
|
||||||
return new Search(); // Legacy Search Methods
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
import $ from 'jquery';
|
|
||||||
import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result';
|
|
||||||
import Project from '~/pages/projects/project';
|
|
||||||
import { visitUrl } from '~/lib/utils/url_utility';
|
|
||||||
import refreshCounts from './refresh_counts';
|
|
||||||
|
|
||||||
export default class Search {
|
|
||||||
constructor() {
|
|
||||||
this.searchInput = '.js-search-input';
|
|
||||||
this.searchClear = '.js-search-clear';
|
|
||||||
|
|
||||||
setHighlightClass(); // Code Highlighting
|
|
||||||
this.eventListeners(); // Search Form Actions
|
|
||||||
refreshCounts(); // Other Scope Tab Counts
|
|
||||||
Project.initRefSwitcher(); // Code Search Branch Picker
|
|
||||||
}
|
|
||||||
|
|
||||||
eventListeners() {
|
|
||||||
$(document).off('keyup', this.searchInput).on('keyup', this.searchInput, this.searchKeyUp);
|
|
||||||
$(document)
|
|
||||||
.off('click', this.searchClear)
|
|
||||||
.on('click', this.searchClear, this.clearSearchField.bind(this));
|
|
||||||
|
|
||||||
$('a.js-search-clear').off('click', this.clearSearchFilter).on('click', this.clearSearchFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
static submitSearch() {
|
|
||||||
return $('.js-search-form').submit();
|
|
||||||
}
|
|
||||||
|
|
||||||
searchKeyUp() {
|
|
||||||
const $input = $(this);
|
|
||||||
if ($input.val() === '') {
|
|
||||||
$('.js-search-clear').addClass('hidden');
|
|
||||||
} else {
|
|
||||||
$('.js-search-clear').removeClass('hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clearSearchField() {
|
|
||||||
return $(this.searchInput).val('').trigger('keyup').focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to manually follow the link on the anchors
|
|
||||||
// that have this event bound, as their `click` default
|
|
||||||
// behavior is prevented by the toggle logic.
|
|
||||||
/* eslint-disable-next-line class-methods-use-this */
|
|
||||||
clearSearchFilter(ev) {
|
|
||||||
const $target = $(ev.currentTarget);
|
|
||||||
|
|
||||||
visitUrl($target.href);
|
|
||||||
ev.stopPropagation();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
export default () => {
|
export default (search = '') => {
|
||||||
const highlightLineClass = 'hll';
|
const highlightLineClass = 'hll';
|
||||||
const contentBody = document.getElementById('content-body');
|
const contentBody = document.getElementById('content-body');
|
||||||
const searchTerm = contentBody.querySelector('.js-search-input').value.toLowerCase();
|
const searchTerm = search.toLowerCase();
|
||||||
const blobs = contentBody.querySelectorAll('.blob-result');
|
const blobs = contentBody.querySelectorAll('.blob-result');
|
||||||
|
|
||||||
blobs.forEach((blob) => {
|
blobs.forEach((blob) => {
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result';
|
||||||
|
import Project from '~/pages/projects/project';
|
||||||
|
import refreshCounts from '~/pages/search/show/refresh_counts';
|
||||||
import { queryToObject } from '~/lib/utils/url_utility';
|
import { queryToObject } from '~/lib/utils/url_utility';
|
||||||
import createStore from './store';
|
import createStore from './store';
|
||||||
import { initTopbar } from './topbar';
|
import { initTopbar } from './topbar';
|
||||||
|
@ -7,8 +10,14 @@ export const initSearchApp = () => {
|
||||||
// Similar to url_utility.decodeUrlParameter
|
// Similar to url_utility.decodeUrlParameter
|
||||||
// Our query treats + as %20. This replaces the query + symbols with %20.
|
// Our query treats + as %20. This replaces the query + symbols with %20.
|
||||||
const sanitizedSearch = window.location.search.replace(/\+/g, '%20');
|
const sanitizedSearch = window.location.search.replace(/\+/g, '%20');
|
||||||
const store = createStore({ query: queryToObject(sanitizedSearch) });
|
const query = queryToObject(sanitizedSearch);
|
||||||
|
|
||||||
|
const store = createStore({ query });
|
||||||
|
|
||||||
initTopbar(store);
|
initTopbar(store);
|
||||||
initSidebar(store);
|
initSidebar(store);
|
||||||
|
|
||||||
|
setHighlightClass(query.search); // Code Highlighting
|
||||||
|
refreshCounts(); // Other Scope Tab Counts
|
||||||
|
Project.initRefSwitcher(); // Code Search Branch Picker
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
<script>
|
||||||
|
import { mapState, mapActions } from 'vuex';
|
||||||
|
import { GlForm, GlSearchBoxByType, GlButton } from '@gitlab/ui';
|
||||||
|
import GroupFilter from './group_filter.vue';
|
||||||
|
import ProjectFilter from './project_filter.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'GlobalSearchTopbar',
|
||||||
|
components: {
|
||||||
|
GlForm,
|
||||||
|
GlSearchBoxByType,
|
||||||
|
GroupFilter,
|
||||||
|
ProjectFilter,
|
||||||
|
GlButton,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
groupInitialData: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
projectInitialData: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['query']),
|
||||||
|
search: {
|
||||||
|
get() {
|
||||||
|
return this.query ? this.query.search : '';
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.setQuery({ key: 'search', value });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
showFilters() {
|
||||||
|
return !this.query.snippets || this.query.snippets === 'false';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['applyQuery', 'setQuery']),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<gl-form class="search-page-form" @submit.prevent="applyQuery">
|
||||||
|
<section class="gl-display-lg-flex gl-align-items-flex-end">
|
||||||
|
<div class="gl-flex-fill-1 gl-mb-4 gl-lg-mb-0 gl-lg-mr-2">
|
||||||
|
<label>{{ __('What are you searching for?') }}</label>
|
||||||
|
<gl-search-box-by-type
|
||||||
|
id="dashboard_search"
|
||||||
|
v-model="search"
|
||||||
|
name="search"
|
||||||
|
:placeholder="__(`Search for projects, issues, etc.`)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2">
|
||||||
|
<label class="gl-display-block">{{ __('Group') }}</label>
|
||||||
|
<group-filter :initial-data="groupInitialData" />
|
||||||
|
</div>
|
||||||
|
<div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2">
|
||||||
|
<label class="gl-display-block">{{ __('Project') }}</label>
|
||||||
|
<project-filter :initial-data="projectInitialData" />
|
||||||
|
</div>
|
||||||
|
<gl-button class="btn-search gl-lg-ml-2" variant="success" type="submit">{{
|
||||||
|
__('Search')
|
||||||
|
}}</gl-button>
|
||||||
|
</section>
|
||||||
|
</gl-form>
|
||||||
|
</template>
|
|
@ -37,6 +37,7 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<searchable-dropdown
|
<searchable-dropdown
|
||||||
|
data-testid="group-filter"
|
||||||
:header-text="$options.GROUP_DATA.headerText"
|
:header-text="$options.GROUP_DATA.headerText"
|
||||||
:selected-display-value="$options.GROUP_DATA.selectedDisplayValue"
|
:selected-display-value="$options.GROUP_DATA.selectedDisplayValue"
|
||||||
:items-display-value="$options.GROUP_DATA.itemsDisplayValue"
|
:items-display-value="$options.GROUP_DATA.itemsDisplayValue"
|
||||||
|
|
|
@ -40,6 +40,7 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<searchable-dropdown
|
<searchable-dropdown
|
||||||
|
data-testid="project-filter"
|
||||||
:header-text="$options.PROJECT_DATA.headerText"
|
:header-text="$options.PROJECT_DATA.headerText"
|
||||||
:selected-display-value="$options.PROJECT_DATA.selectedDisplayValue"
|
:selected-display-value="$options.PROJECT_DATA.selectedDisplayValue"
|
||||||
:items-display-value="$options.PROJECT_DATA.itemsDisplayValue"
|
:items-display-value="$options.PROJECT_DATA.itemsDisplayValue"
|
||||||
|
|
|
@ -101,7 +101,7 @@ export default {
|
||||||
@keydown.enter.stop="resetDropdown"
|
@keydown.enter.stop="resetDropdown"
|
||||||
@click.stop="resetDropdown"
|
@click.stop="resetDropdown"
|
||||||
>
|
>
|
||||||
<gl-icon name="clear" class="gl-text-gray-200! gl-hover-text-blue-800!" />
|
<gl-icon name="clear" />
|
||||||
</gl-button>
|
</gl-button>
|
||||||
<gl-icon name="chevron-down" />
|
<gl-icon name="chevron-down" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,44 +1,31 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Translate from '~/vue_shared/translate';
|
import Translate from '~/vue_shared/translate';
|
||||||
import GroupFilter from './components/group_filter.vue';
|
import GlobalSearchTopbar from './components/app.vue';
|
||||||
import ProjectFilter from './components/project_filter.vue';
|
|
||||||
|
|
||||||
Vue.use(Translate);
|
Vue.use(Translate);
|
||||||
|
|
||||||
const mountSearchableDropdown = (store, { id, component }) => {
|
export const initTopbar = (store) => {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById('js-search-topbar');
|
||||||
|
|
||||||
if (!el) {
|
if (!el) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { initialData } = el.dataset;
|
let { groupInitialData, projectInitialData } = el.dataset;
|
||||||
|
|
||||||
initialData = JSON.parse(initialData);
|
groupInitialData = JSON.parse(groupInitialData);
|
||||||
|
projectInitialData = JSON.parse(projectInitialData);
|
||||||
|
|
||||||
return new Vue({
|
return new Vue({
|
||||||
el,
|
el,
|
||||||
store,
|
store,
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
return createElement(component, {
|
return createElement(GlobalSearchTopbar, {
|
||||||
props: {
|
props: {
|
||||||
initialData,
|
groupInitialData,
|
||||||
|
projectInitialData,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchableDropdowns = [
|
|
||||||
{
|
|
||||||
id: 'js-search-group-dropdown',
|
|
||||||
component: GroupFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'js-search-project-dropdown',
|
|
||||||
component: ProjectFilter,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const initTopbar = (store) =>
|
|
||||||
searchableDropdowns.map((dropdown) => mountSearchableDropdown(store, dropdown));
|
|
||||||
|
|
|
@ -110,11 +110,6 @@ export default {
|
||||||
return this.referencedUsers.length >= referencedUsersThreshold;
|
return this.referencedUsers.length >= referencedUsersThreshold;
|
||||||
},
|
},
|
||||||
lineContent() {
|
lineContent() {
|
||||||
const [firstSuggestion] = this.suggestions;
|
|
||||||
if (firstSuggestion) {
|
|
||||||
return firstSuggestion.from_content;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.line) {
|
if (this.line) {
|
||||||
const { rich_text: richText, text } = this.line;
|
const { rich_text: richText, text } = this.line;
|
||||||
|
|
||||||
|
|
|
@ -91,29 +91,10 @@ $note-form-margin-left: 72px;
|
||||||
color: $blue-600;
|
color: $blue-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.expanded {
|
|
||||||
span {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
position: relative;
|
|
||||||
top: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
color: $gl-text-color-secondary;
|
color: $gl-text-color-secondary;
|
||||||
border-radius: 0 0 $border-radius-default $border-radius-default;
|
border-radius: 0 0 $border-radius-default $border-radius-default;
|
||||||
|
|
||||||
svg {
|
|
||||||
float: left;
|
|
||||||
position: relative;
|
|
||||||
top: $gl-padding-4;
|
|
||||||
margin-right: $gl-padding-8;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
margin: -2px 4px 0 0;
|
margin: -2px 4px 0 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
.col-sm-10
|
.col-sm-10
|
||||||
= f.check_box :trusted
|
= f.check_box :trusted
|
||||||
%span.form-text.text-muted
|
%span.form-text.text-muted
|
||||||
Trusted applications are automatically authorized on GitLab OAuth flow.
|
Trusted applications are automatically authorized on GitLab OAuth flow. It's highly recommended for the security of users that trusted applications have the confidential setting set to true.
|
||||||
|
|
||||||
= content_tag :div, class: 'form-group row' do
|
= content_tag :div, class: 'form-group row' do
|
||||||
.col-sm-2.col-form-label.pt-0
|
.col-sm-2.col-form-label.pt-0
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
- if params[:group_id].present?
|
|
||||||
= hidden_field_tag :group_id, params[:group_id]
|
|
||||||
- if params[:project_id].present?
|
|
||||||
= hidden_field_tag :project_id, params[:project_id]
|
|
||||||
- project_attributes = @project&.attributes&.slice('id', 'namespace_id', 'name')&.merge(name_with_namespace: @project&.name_with_namespace)
|
|
||||||
|
|
||||||
.dropdown.form-group.mb-lg-0.mx-lg-1.gl-p-0{ data: { testid: "group-filter" } }
|
|
||||||
%label.d-block{ for: "dashboard_search_group" }
|
|
||||||
= _("Group")
|
|
||||||
%input#js-search-group-dropdown.dropdown-menu-toggle{ value: "Loading...", data: { "initial-data": @group.to_json } }
|
|
||||||
.dropdown.form-group.mb-lg-0.mx-lg-1.gl-p-0{ data: { testid: "project-filter" } }
|
|
||||||
%label.d-block{ for: "dashboard_search_project" }
|
|
||||||
= _("Project")
|
|
||||||
%input#js-search-project-dropdown.dropdown-menu-toggle{ value: "Loading...", data: { "initial-data": project_attributes.to_json } }
|
|
|
@ -1,20 +0,0 @@
|
||||||
= form_tag search_path, method: :get, class: 'search-page-form js-search-form' do |f|
|
|
||||||
= hidden_field_tag :snippets, params[:snippets]
|
|
||||||
= hidden_field_tag :scope, params[:scope]
|
|
||||||
= hidden_field_tag :repository_ref, params[:repository_ref]
|
|
||||||
|
|
||||||
.d-lg-flex.align-items-end
|
|
||||||
.search-field-holder.form-group.mr-lg-1.mb-lg-0
|
|
||||||
%label{ for: "dashboard_search" }
|
|
||||||
= _("What are you searching for?")
|
|
||||||
.gl-search-box-by-type
|
|
||||||
= search_field_tag :search, params[:search], placeholder: _("Search for projects, issues, etc."), class: "gl-form-input form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false
|
|
||||||
= sprite_icon('search', css_class: 'gl-search-box-by-type-search-icon gl-icon')
|
|
||||||
%button.search-clear.js-search-clear{ class: [("hidden" if params[:search].blank?), "has-tooltip"], type: "button", tabindex: "-1", title: _('Clear') }
|
|
||||||
= sprite_icon('clear')
|
|
||||||
%span.sr-only
|
|
||||||
= _("Clear search")
|
|
||||||
- unless params[:snippets].eql? 'true'
|
|
||||||
= render 'filter'
|
|
||||||
.d-flex-center.flex-column.flex-lg-row
|
|
||||||
= button_tag _("Search"), class: "gl-button btn btn-success btn-search mt-lg-0 ml-lg-1 align-self-end"
|
|
|
@ -1,6 +1,11 @@
|
||||||
- @hide_top_links = true
|
- @hide_top_links = true
|
||||||
- page_title @search_term
|
- page_title @search_term
|
||||||
- @hide_breadcrumbs = true
|
- @hide_breadcrumbs = true
|
||||||
|
- if params[:group_id].present?
|
||||||
|
= hidden_field_tag :group_id, params[:group_id]
|
||||||
|
- if params[:project_id].present?
|
||||||
|
= hidden_field_tag :project_id, params[:project_id]
|
||||||
|
- project_attributes = @project&.attributes&.slice('id', 'namespace_id', 'name')&.merge(name_with_namespace: @project&.name_with_namespace)
|
||||||
|
|
||||||
- if @search_results
|
- if @search_results
|
||||||
- page_description(_("%{count} %{scope} for term '%{term}'") % { count: @search_results.formatted_count(@scope), scope: @scope, term: @search_term })
|
- page_description(_("%{count} %{scope} for term '%{term}'") % { count: @search_results.formatted_count(@scope), scope: @scope, term: @search_term })
|
||||||
|
@ -11,7 +16,7 @@
|
||||||
= render_if_exists 'search/form_elasticsearch', attrs: { class: 'mb-2 mb-sm-0 align-self-center' }
|
= render_if_exists 'search/form_elasticsearch', attrs: { class: 'mb-2 mb-sm-0 align-self-center' }
|
||||||
|
|
||||||
.gl-mt-3
|
.gl-mt-3
|
||||||
= render 'search/form'
|
#js-search-topbar{ data: { "group-initial-data": @group.to_json, "project-initial-data": project_attributes.to_json } }
|
||||||
- if @search_term
|
- if @search_term
|
||||||
= render 'search/category'
|
= render 'search/category'
|
||||||
= render 'search/results'
|
= render 'search/results'
|
||||||
|
|
|
@ -12,5 +12,5 @@
|
||||||
|
|
||||||
.col-md-4.col-lg-5.text-right-md.gl-mt-2
|
.col-md-4.col-lg-5.text-right-md.gl-mt-2
|
||||||
%span>= render 'shared/web_hooks/test_button', hook: hook, button_class: 'btn-sm gl-mr-3'
|
%span>= render 'shared/web_hooks/test_button', hook: hook, button_class: 'btn-sm gl-mr-3'
|
||||||
%span>= link_to _('Edit'), edit_hook_path(hook), class: 'btn btn-sm gl-mr-3'
|
%span>= link_to _('Edit'), edit_hook_path(hook), class: 'gl-button btn btn-sm gl-mr-3'
|
||||||
= link_to _('Delete'), destroy_hook_path(hook), data: { confirm: _('Are you sure?') }, method: :delete, class: 'btn btn-sm'
|
= link_to _('Delete'), destroy_hook_path(hook), data: { confirm: _('Are you sure?') }, method: :delete, class: 'gl-button btn btn-sm'
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
- triggers = hook.class.triggers
|
- triggers = hook.class.triggers
|
||||||
|
|
||||||
.hook-test-button.dropdown.inline>
|
.hook-test-button.dropdown.inline>
|
||||||
%button.btn{ 'data-toggle' => 'dropdown', class: button_class }
|
%button.btn.gl-button{ 'data-toggle' => 'dropdown', class: button_class }
|
||||||
= _('Test')
|
= _('Test')
|
||||||
= sprite_icon('chevron-down')
|
= sprite_icon('chevron-down')
|
||||||
%ul.dropdown-menu.dropdown-menu-right{ role: 'menu' }
|
%ul.dropdown-menu.dropdown-menu-right{ role: 'menu' }
|
||||||
|
|
|
@ -9,24 +9,6 @@ let presets = [
|
||||||
useBuiltIns: 'usage',
|
useBuiltIns: 'usage',
|
||||||
corejs: { version: 3, proposals: true },
|
corejs: { version: 3, proposals: true },
|
||||||
modules: false,
|
modules: false,
|
||||||
/**
|
|
||||||
* This list of browsers is a conservative first definition, based on
|
|
||||||
* https://docs.gitlab.com/ee/install/requirements.html#supported-web-browsers
|
|
||||||
* with the following reasoning:
|
|
||||||
*
|
|
||||||
* - Edge: Pick the last two major version before the Chrome switch
|
|
||||||
* - Rest: We should support the latest ESR of Firefox: 68, because it used quite a lot.
|
|
||||||
* For the rest, pick browser versions that have a similar age to Firefox 68.
|
|
||||||
*
|
|
||||||
* See also this follow-up epic:
|
|
||||||
* https://gitlab.com/groups/gitlab-org/-/epics/3957
|
|
||||||
*/
|
|
||||||
targets: {
|
|
||||||
chrome: '73',
|
|
||||||
edge: '17',
|
|
||||||
firefox: '68',
|
|
||||||
safari: '12',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Global Search - UX Cleanup of Search Bar
|
||||||
|
merge_request: 51409
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Scope milestones on swimlane boards to project and its ancestors
|
||||||
|
merge_request: 52199
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Disable board configuration options for users without edit permission
|
||||||
|
merge_request: 52077
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Update buttons in _hook.html.haml to use GitLab UI
|
||||||
|
merge_request: 51065
|
||||||
|
author: nuwe1
|
||||||
|
type: other
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Adds GitLabUI button styles in _test_button.html.haml
|
||||||
|
merge_request: 51070
|
||||||
|
author: nuwe1
|
||||||
|
type: other
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fixed sdiff suggestions not working when replying to comments
|
||||||
|
merge_request: 52100
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix alignment of chevron-down icon in toggle replies
|
||||||
|
merge_request: 51872
|
||||||
|
author: Yogi (@yo)
|
||||||
|
type: fixed
|
|
@ -0,0 +1,32 @@
|
||||||
|
# This is a template for a "Whats New" release.
|
||||||
|
# A release typically contains multiple entries of features that we'd like to highlight.
|
||||||
|
#
|
||||||
|
# Below is an example of what a single entry should look like, it's required attributes,
|
||||||
|
# and what types we expect those attribute values to be. All attributes are required.
|
||||||
|
#
|
||||||
|
# For more information please refer to the handbook documentation here:
|
||||||
|
# https://about.gitlab.com/handbook/marketing/blog/release-posts/index.html#create-mr-for-whats-new-entries
|
||||||
|
#
|
||||||
|
# Please delete this line and above before submitting your Merge Request.
|
||||||
|
|
||||||
|
- title: # Match the release post entry
|
||||||
|
body: | # Do not modify this line, instead modify the lines below.
|
||||||
|
<!-- START OF BODY COMMENT
|
||||||
|
|
||||||
|
This area supports markdown.
|
||||||
|
|
||||||
|
It is recommended to copy and paste the release post entry content here.
|
||||||
|
|
||||||
|
You can shorten it if needed, but remember that the entry itself has already been reviewed by Tech Writing so don't feel like you need to reword anything.
|
||||||
|
|
||||||
|
Delete this entire comment and replace it with your markdown content.
|
||||||
|
|
||||||
|
END OF BODY COMMENT -->
|
||||||
|
stage: # String value of the stage that the feature was created in. e.g., Growth
|
||||||
|
self-managed: # Boolean value (true or false)
|
||||||
|
gitlab-com: # Boolean value (true or false)
|
||||||
|
packages: # Array of strings. The Array brackets are required here. e.g., [Core, Starter, Premium, Ultimate]
|
||||||
|
url: # This is the documentation URL, but can be a URL to a video if there is one
|
||||||
|
image_url: # This should be a full URL, generally taken from the release post content. If a video, use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
|
||||||
|
published_at: # YYYY-MM-DD
|
||||||
|
release: # XX.Y
|
|
@ -189,6 +189,7 @@ heatmaps
|
||||||
Helm
|
Helm
|
||||||
Heroku
|
Heroku
|
||||||
Herokuish
|
Herokuish
|
||||||
|
Hexo
|
||||||
HipChat
|
HipChat
|
||||||
hostname
|
hostname
|
||||||
hostnames
|
hostnames
|
||||||
|
@ -204,6 +205,8 @@ inclusivity
|
||||||
Ingress
|
Ingress
|
||||||
initializer
|
initializer
|
||||||
initializers
|
initializers
|
||||||
|
innersource
|
||||||
|
innersourcing
|
||||||
interdependencies
|
interdependencies
|
||||||
interdependency
|
interdependency
|
||||||
interruptible
|
interruptible
|
||||||
|
@ -432,6 +435,7 @@ sbt
|
||||||
scatterplot
|
scatterplot
|
||||||
scatterplots
|
scatterplots
|
||||||
Schemastore
|
Schemastore
|
||||||
|
scrollable
|
||||||
Sendmail
|
Sendmail
|
||||||
Sentry
|
Sentry
|
||||||
serializer
|
serializer
|
||||||
|
@ -556,6 +560,7 @@ unpublish
|
||||||
unpublished
|
unpublished
|
||||||
unpublishes
|
unpublishes
|
||||||
unpublishing
|
unpublishing
|
||||||
|
unpushed
|
||||||
unreferenced
|
unreferenced
|
||||||
unregister
|
unregister
|
||||||
unregistered
|
unregistered
|
||||||
|
|
|
@ -84,9 +84,10 @@ To enable merge trains for your project:
|
||||||
1. If you are on a self-managed GitLab instance, ensure the [feature flag](#merge-trains-feature-flag) is set correctly.
|
1. If you are on a self-managed GitLab instance, ensure the [feature flag](#merge-trains-feature-flag) is set correctly.
|
||||||
1. [Configure your CI/CD configuration file](../../index.md#configuring-pipelines-for-merge-requests)
|
1. [Configure your CI/CD configuration file](../../index.md#configuring-pipelines-for-merge-requests)
|
||||||
so that the pipeline or individual jobs run for merge requests.
|
so that the pipeline or individual jobs run for merge requests.
|
||||||
1. Visit your project's **Settings > General** and expand **Merge requests**
|
1. Visit your project's **Settings > General** and expand **Merge requests**.
|
||||||
1. Check **Enable merged results pipelines.** (if not enabled)
|
1. In the **Merge method** section, verify that **Merge commit** is selected.
|
||||||
1. Check **Enable merge trains.**
|
You cannot use **Merge commit with semi-linear history** or **Fast-forward merge** with merge trains.
|
||||||
|
1. In the **Merge options** section, select **Enable merged results pipelines.** (if not already selected) and **Enable merge trains.**
|
||||||
1. Click **Save changes**
|
1. Click **Save changes**
|
||||||
|
|
||||||
In GitLab 13.5 and earlier, there is only one checkbox, named
|
In GitLab 13.5 and earlier, there is only one checkbox, named
|
||||||
|
|
|
@ -15,52 +15,83 @@ settings automatically by default. If your editor/IDE does not automatically sup
|
||||||
we suggest investigating to see if a plugin exists. For instance here is the
|
we suggest investigating to see if a plugin exists. For instance here is the
|
||||||
[plugin for vim](https://github.com/editorconfig/editorconfig-vim).
|
[plugin for vim](https://github.com/editorconfig/editorconfig-vim).
|
||||||
|
|
||||||
## Pre-push static analysis
|
## Pre-push static analysis with Lefthook
|
||||||
|
|
||||||
We strongly recommend installing [Lefthook](https://github.com/Arkweid/lefthook) to automatically check
|
[Lefthook](https://github.com/Arkweid/lefthook) is a Git hooks manager that allows
|
||||||
for static analysis offenses before pushing your changes.
|
custom logic to be executed prior to Git committing or pushing. GitLab comes with
|
||||||
|
Lefthook configuration (`lefthook.yml`), but it must be installed.
|
||||||
|
|
||||||
To install `lefthook`, run the following in your GitLab source directory:
|
We have a `lefthook.yml` checked in but it is ignored until Lefthook is installed.
|
||||||
|
|
||||||
|
### Uninstall Overcommit
|
||||||
|
|
||||||
|
We were using Overcommit prior to Lefthook, so you may want to uninstall it first with `overcommit --uninstall`.
|
||||||
|
|
||||||
|
### Install Lefthook
|
||||||
|
|
||||||
|
1. Install the `lefthook` Ruby gem:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
bundle install
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Install Lefthook managed Git hooks:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
bundle exec lefthook install
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Test Lefthook is working by running the Lefthook `prepare-commit-msg` Git hook:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
bundle exec lefthook run prepare-commit-msg
|
||||||
|
```
|
||||||
|
|
||||||
|
This should return a fully qualified path command with no other output.
|
||||||
|
|
||||||
|
### Lefthook configuration
|
||||||
|
|
||||||
|
The current Lefthook configuration can be found in [`lefthook.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lefthook.yml).
|
||||||
|
|
||||||
|
Before you push your changes, Lefthook automatically runs the following checks:
|
||||||
|
|
||||||
|
- Danger: Runs a subset of checks that `danger-review` runs on your merge requests.
|
||||||
|
- ES lint: Run `yarn eslint` checks (with the [`.eslintrc.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.eslintrc.yml) config) on the modified `*.{js,vue}` files. Tags: `frontend`, `style`.
|
||||||
|
- HAML lint: Run `bundle exec haml-lint` checks (with the [`.haml-lint.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.haml-lint.yml) config) on the modified `*.html.haml` files. Tags: `view`, `haml`, `style`.
|
||||||
|
- Markdown lint: Run `yarn markdownlint` checks on the modified `*.md` files. Tags: `documentation`, `style`.
|
||||||
|
- SCSS lint: Run `bundle exec scss-lint` checks (with the [`.scss-lint.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.scss-lint.yml) config) on the modified `*.scss{,.css}` files. Tags: `stylesheet`, `css`, `style`.
|
||||||
|
- RuboCop: Run `bundle exec rubocop` checks (with the [`.rubocop.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.rubocop.yml) config) on the modified `*.rb` files. Tags: `backend`, `style`.
|
||||||
|
- Vale: Run `vale` checks (with the [`.vale.ini`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.vale.ini) config) on the modified `*.md` files. Tags: `documentation`, `style`.
|
||||||
|
|
||||||
|
In addition to the default configuration, you can define a [local configuration](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#local-config).
|
||||||
|
|
||||||
|
### Disable Lefthook temporarily
|
||||||
|
|
||||||
|
To disable Lefthook temporarily, you can set the `LEFTHOOK` environment variable to `0`. For instance:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# 1. Make sure to uninstall Overcommit first
|
LEFTHOOK=0 git push ...
|
||||||
overcommit --uninstall
|
|
||||||
|
|
||||||
# If using rbenv, at this point you may need to do: rbenv rehash
|
|
||||||
|
|
||||||
# 2. Install lefthook...
|
|
||||||
|
|
||||||
## With Homebrew (macOS)
|
|
||||||
brew install Arkweid/lefthook/lefthook
|
|
||||||
|
|
||||||
## Or with Go
|
|
||||||
go get github.com/Arkweid/lefthook
|
|
||||||
|
|
||||||
## Or with Rubygems
|
|
||||||
gem install lefthook
|
|
||||||
|
|
||||||
### You may need to run the following if you're using rbenv
|
|
||||||
rbenv rehash
|
|
||||||
|
|
||||||
# 3. Install the Git hooks
|
|
||||||
lefthook install -f
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Before you push your changes, Lefthook then automatically run Danger checks, and other checks
|
### Run Lefthook hooks manually
|
||||||
for changed files. This saves you time as you don't have to wait for the same errors to be detected
|
|
||||||
by CI/CD.
|
|
||||||
|
|
||||||
Lefthook relies on a pre-push hook to prevent commits that violate its ruleset.
|
To run the `pre-push` Git hook, run:
|
||||||
To override this behavior, pass the environment variable `LEFTHOOK=0`. That is,
|
|
||||||
`LEFTHOOK=0 git push`.
|
|
||||||
|
|
||||||
You can also:
|
```shell
|
||||||
|
bundle exec lefthook run pre-push
|
||||||
|
```
|
||||||
|
|
||||||
- Define [local configuration](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#local-config).
|
For more information, check out [Lefthook documentation](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#run-githook-group-directly).
|
||||||
- Skip [checks per tag on the fly](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#skip-some-tags-on-the-fly).
|
|
||||||
For example, `LEFTHOOK_EXCLUDE=frontend git push origin`.
|
### Skip Lefthook checks per tag
|
||||||
- Run [hooks manually](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#run-githook-group-directly).
|
|
||||||
For example, `lefthook run pre-push`.
|
To skip some checks based on tags when pushing, you can set the `LEFTHOOK_EXCLUDE` environment variable. For instance:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
LEFTHOOK_EXCLUDE=frontend,documentation git push ...
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information, check out [Lefthook documentation](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#skip-some-tags-on-the-fly).
|
||||||
|
|
||||||
## Ruby, Rails, RSpec
|
## Ruby, Rails, RSpec
|
||||||
|
|
||||||
|
|
|
@ -286,7 +286,7 @@ Configuration for `lefthook` is available in the [`lefthook.yml`](https://gitlab
|
||||||
file for the [`gitlab`](https://gitlab.com/gitlab-org/gitlab) project.
|
file for the [`gitlab`](https://gitlab.com/gitlab-org/gitlab) project.
|
||||||
|
|
||||||
To set up `lefthook` for documentation linting, see
|
To set up `lefthook` for documentation linting, see
|
||||||
[Pre-push static analysis](../contributing/style_guides.md#pre-push-static-analysis).
|
[Pre-push static analysis](../contributing/style_guides.md#pre-push-static-analysis-with-lefthook).
|
||||||
|
|
||||||
### Show subset of Vale alerts
|
### Show subset of Vale alerts
|
||||||
|
|
||||||
|
|
|
@ -324,7 +324,7 @@ To delete a custom value stream:
|
||||||
This chart visually depicts the total number of days it takes for cycles to be completed. (Totals are being replaced with averages in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/262070).)
|
This chart visually depicts the total number of days it takes for cycles to be completed. (Totals are being replaced with averages in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/262070).)
|
||||||
|
|
||||||
This chart uses the global page filters for displaying data based on the selected
|
This chart uses the global page filters for displaying data based on the selected
|
||||||
group, projects, and timeframe. In addition, specific stages can be selected
|
group, projects, and time frame. In addition, specific stages can be selected
|
||||||
from within the chart itself.
|
from within the chart itself.
|
||||||
|
|
||||||
The chart data is limited to the last 500 items.
|
The chart data is limited to the last 500 items.
|
||||||
|
@ -345,7 +345,7 @@ Feature.disable(:cycle_analytics_scatterplot_enabled)
|
||||||
This chart shows a cumulative count of issues and merge requests per day.
|
This chart shows a cumulative count of issues and merge requests per day.
|
||||||
|
|
||||||
This chart uses the global page filters for displaying data based on the selected
|
This chart uses the global page filters for displaying data based on the selected
|
||||||
group, projects, and timeframe. The chart defaults to showing counts for issues but can be
|
group, projects, and time frame. The chart defaults to showing counts for issues but can be
|
||||||
toggled to show data for merge requests and further refined for specific group-level labels.
|
toggled to show data for merge requests and further refined for specific group-level labels.
|
||||||
|
|
||||||
By default the top group-level labels (max. 10) are pre-selected, with the ability to
|
By default the top group-level labels (max. 10) are pre-selected, with the ability to
|
||||||
|
|
|
@ -29,7 +29,7 @@ You can use [GitLab CI/CD](../../../ci/README.md) to build packages.
|
||||||
For Maven, NuGet, NPM, Conan, and PyPI packages, and Composer dependencies, you can
|
For Maven, NuGet, NPM, Conan, and PyPI packages, and Composer dependencies, you can
|
||||||
authenticate with GitLab by using the `CI_JOB_TOKEN`.
|
authenticate with GitLab by using the `CI_JOB_TOKEN`.
|
||||||
|
|
||||||
CI/CD templates, which you can use to get started, are in [this repo](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates).
|
CI/CD templates, which you can use to get started, are in [this repository](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates).
|
||||||
|
|
||||||
Learn more about using CI/CD to build:
|
Learn more about using CI/CD to build:
|
||||||
|
|
||||||
|
|
|
@ -240,7 +240,7 @@ following desktop browsers:
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
For Firefox 47-66, you can enable the FIDO U2F API in
|
For Firefox 47-66, you can enable the FIDO U2F API in
|
||||||
[about:config](https://support.mozilla.org/en-US/kb/about-config-editor-firefox).
|
[`about:config`](https://support.mozilla.org/en-US/kb/about-config-editor-firefox).
|
||||||
Search for `security.webauth.u2f` and double click on it to toggle to `true`.
|
Search for `security.webauth.u2f` and double click on it to toggle to `true`.
|
||||||
|
|
||||||
To set up 2FA with a U2F device:
|
To set up 2FA with a U2F device:
|
||||||
|
|
|
@ -43,7 +43,7 @@ Google Kubernetes Engine integration. All you have to do is [follow this link](h
|
||||||
## Creating a new project from a template
|
## Creating a new project from a template
|
||||||
|
|
||||||
We use a GitLab project templates to get started. As the name suggests,
|
We use a GitLab project templates to get started. As the name suggests,
|
||||||
those projects provide a barebones application built on some well-known frameworks.
|
those projects provide a bare-bones application built on some well-known frameworks.
|
||||||
|
|
||||||
1. In GitLab, click the plus icon (**+**) at the top of the navigation bar and select
|
1. In GitLab, click the plus icon (**+**) at the top of the navigation bar and select
|
||||||
**New project**.
|
**New project**.
|
||||||
|
|
|
@ -563,7 +563,7 @@ another list. This makes it faster to reorder many issues at once.
|
||||||
|
|
||||||
To select and move multiple cards:
|
To select and move multiple cards:
|
||||||
|
|
||||||
1. Select each card with <kbd>Ctrl</kbd>+`Click` on Windows or Linux, or <kbd>Cmd</kbd>+`Click` on MacOS.
|
1. Select each card with <kbd>Control</kbd>+`Click` on Windows or Linux, or <kbd>Command</kbd>+`Click` on MacOS.
|
||||||
1. Drag one of the selected cards to another position or list and all selected cards are moved.
|
1. Drag one of the selected cards to another position or list and all selected cards are moved.
|
||||||
|
|
||||||
![Multi-select Issue Cards](img/issue_boards_multi_select_v12_4.png)
|
![Multi-select Issue Cards](img/issue_boards_multi_select_v12_4.png)
|
||||||
|
|
|
@ -23,7 +23,7 @@ of the merge request.
|
||||||
|
|
||||||
## Enabling commit edits from upstream members
|
## Enabling commit edits from upstream members
|
||||||
|
|
||||||
From [GitLab 13.7 onwards](https://gitlab.com/gitlab-org/gitlab/-/issues/23308),
|
In [GitLab 13.7 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/23308),
|
||||||
this setting is enabled by default. It can be changed by users with Developer
|
this setting is enabled by default. It can be changed by users with Developer
|
||||||
permissions to the source project. Once enabled, upstream members will also be
|
permissions to the source project. Once enabled, upstream members will also be
|
||||||
able to retry the pipelines and jobs of the merge request:
|
able to retry the pipelines and jobs of the merge request:
|
||||||
|
|
|
@ -75,7 +75,7 @@ GitLab 11.4 or earlier, you can view the deprecated job definitions in the
|
||||||
- Using shared runners, the job should be configured For the [Docker-in-Docker workflow](../../../ci/docker/using_docker_build.md#use-the-docker-executor-with-the-docker-image-docker-in-docker).
|
- Using shared runners, the job should be configured For the [Docker-in-Docker workflow](../../../ci/docker/using_docker_build.md#use-the-docker-executor-with-the-docker-image-docker-in-docker).
|
||||||
- Using private runners, there is an [alternative configuration](#set-up-a-private-runner-for-code-quality-without-docker-in-docker) recommended for running CodeQuality analysis more efficiently.
|
- Using private runners, there is an [alternative configuration](#set-up-a-private-runner-for-code-quality-without-docker-in-docker) recommended for running CodeQuality analysis more efficiently.
|
||||||
|
|
||||||
In either configuration, the runner mmust have enough disk space to handle generated Code Quality files. For example on the [GitLab project](https://gitlab.com/gitlab-org/gitlab) the files are approximately 7 GB.
|
In either configuration, the runner must have enough disk space to handle generated Code Quality files. For example on the [GitLab project](https://gitlab.com/gitlab-org/gitlab) the files are approximately 7 GB.
|
||||||
|
|
||||||
Once you set up GitLab Runner, include the Code Quality template in your CI configuration:
|
Once you set up GitLab Runner, include the Code Quality template in your CI configuration:
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ From there, when reviewing merge requests' **Changes** tab, you will see only on
|
||||||
|
|
||||||
![File-by-file diff navigation](img/file_by_file_v13_2.png)
|
![File-by-file diff navigation](img/file_by_file_v13_2.png)
|
||||||
|
|
||||||
From [GitLab 13.7](https://gitlab.com/gitlab-org/gitlab/-/issues/233898) onwards, if you want to change
|
In [GitLab 13.7](https://gitlab.com/gitlab-org/gitlab/-/issues/233898) and later, if you want to change
|
||||||
this behavior, you can do so from your **User preferences** (as explained above) or directly in a
|
this behavior, you can do so from your **User preferences** (as explained above) or directly in a
|
||||||
merge request:
|
merge request:
|
||||||
|
|
||||||
|
|
|
@ -52,5 +52,5 @@ You can take some **optional** further steps:
|
||||||
|
|
||||||
![Change repository's path](../img/change_path_v12_10.png)
|
![Change repository's path](../img/change_path_v12_10.png)
|
||||||
|
|
||||||
- Now go to your SSG's configuration file and change the [base URL](../getting_started_part_one.md#urls-and-baseurls)
|
- Now go to your SSG's configuration file and change the [base URL](../getting_started_part_one.md#urls-and-base-urls)
|
||||||
from `"project-name"` to `""`. The project name setting varies by SSG and may not be in the configuration file.
|
from `"project-name"` to `""`. The project name setting varies by SSG and may not be in the configuration file.
|
||||||
|
|
|
@ -4,7 +4,7 @@ group: Release
|
||||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||||
---
|
---
|
||||||
|
|
||||||
# GitLab Pages domain names, URLs, and baseurls
|
# GitLab Pages domain names, URLs, and base URLs
|
||||||
|
|
||||||
On this document, learn how to name your project for GitLab Pages
|
On this document, learn how to name your project for GitLab Pages
|
||||||
according to your intended website's URL.
|
according to your intended website's URL.
|
||||||
|
@ -78,7 +78,7 @@ To understand Pages domains clearly, read the examples below.
|
||||||
- On your GitLab instance, replace `gitlab.io` above with your
|
- On your GitLab instance, replace `gitlab.io` above with your
|
||||||
Pages server domain. Ask your sysadmin for this information.
|
Pages server domain. Ask your sysadmin for this information.
|
||||||
|
|
||||||
## URLs and baseurls
|
## URLs and base URLs
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
The `baseurl` option might be called named differently in some static site generators.
|
The `baseurl` option might be called named differently in some static site generators.
|
||||||
|
|
|
@ -10,7 +10,7 @@ description: "The static site editor enables users to edit content on static web
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28758) in GitLab 12.10.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28758) in GitLab 12.10.
|
||||||
> - WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214559) in GitLab 13.0.
|
> - WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214559) in GitLab 13.0.
|
||||||
> - Non-Markdown content blocks uneditable on the WYSIWYG mode [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216836) in GitLab 13.3.
|
> - Non-Markdown content blocks not editable on the WYSIWYG mode [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216836) in GitLab 13.3.
|
||||||
> - Formatting Markdown [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49052) in GitLab 13.7.
|
> - Formatting Markdown [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49052) in GitLab 13.7.
|
||||||
|
|
||||||
Static Site Editor (SSE) enables users to edit content on static websites without
|
Static Site Editor (SSE) enables users to edit content on static websites without
|
||||||
|
|
|
@ -26,7 +26,7 @@ and from merge requests.
|
||||||
|
|
||||||
The file finder allows you to quickly open files in the current branch by
|
The file finder allows you to quickly open files in the current branch by
|
||||||
searching for fragments of the file path. The file finder is launched using the keyboard shortcut
|
searching for fragments of the file path. The file finder is launched using the keyboard shortcut
|
||||||
<kbd>Cmd</kbd>+<kbd>p</kbd>, <kbd>Ctrl</kbd>+<kbd>p</kbd>, or <kbd>t</kbd>
|
<kbd>Command</kbd>+<kbd>p</kbd>, <kbd>Control</kbd>+<kbd>p</kbd>, or <kbd>t</kbd>
|
||||||
(when editor is not in focus). Type the filename or file path fragments to
|
(when editor is not in focus). Type the filename or file path fragments to
|
||||||
start seeing results.
|
start seeing results.
|
||||||
|
|
||||||
|
|
|
@ -42,10 +42,10 @@ for example comments, replies, issue descriptions, and merge request description
|
||||||
| Keyboard Shortcut | Description |
|
| Keyboard Shortcut | Description |
|
||||||
| ---------------------------------------------------------------------- | ----------- |
|
| ---------------------------------------------------------------------- | ----------- |
|
||||||
| <kbd>↑</kbd> | Edit your last comment. You must be in a blank text field below a thread, and you must already have at least one comment in the thread. |
|
| <kbd>↑</kbd> | Edit your last comment. You must be in a blank text field below a thread, and you must already have at least one comment in the thread. |
|
||||||
| <kbd>⌘</kbd> (Mac) / <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>p</kbd> | Toggle Markdown preview, when editing text in a text field that has **Write** and **Preview** tabs at the top. |
|
| <kbd>⌘</kbd> (Mac) / <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>p</kbd> | Toggle Markdown preview, when editing text in a text field that has **Write** and **Preview** tabs at the top. |
|
||||||
| <kbd>⌘</kbd> (Mac) / <kbd>Ctrl</kbd> + <kbd>b</kbd> | Bold the selected text (surround it with `**`). |
|
| <kbd>⌘</kbd> (Mac) / <kbd>Control</kbd> + <kbd>b</kbd> | Bold the selected text (surround it with `**`). |
|
||||||
| <kbd>⌘</kbd> (Mac) / <kbd>Ctrl</kbd> + <kbd>i</kbd> | Italicize the selected text (surround it with `_`). |
|
| <kbd>⌘</kbd> (Mac) / <kbd>Control</kbd> + <kbd>i</kbd> | Italicize the selected text (surround it with `_`). |
|
||||||
| <kbd>⌘</kbd> (Mac) / <kbd>Ctrl</kbd> + <kbd>k</kbd> | Add a link (surround the selected text with `[]()`). |
|
| <kbd>⌘</kbd> (Mac) / <kbd>Control</kbd> + <kbd>k</kbd> | Add a link (surround the selected text with `[]()`). |
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
The shortcuts for editing in text fields are always enabled, even when
|
The shortcuts for editing in text fields are always enabled, even when
|
||||||
|
@ -104,7 +104,7 @@ These shortcuts are available when browsing the files in a project (navigate to
|
||||||
| <kbd>↑</kbd> | Move selection up. |
|
| <kbd>↑</kbd> | Move selection up. |
|
||||||
| <kbd>↓</kbd> | Move selection down. |
|
| <kbd>↓</kbd> | Move selection down. |
|
||||||
| <kbd>enter</kbd> | Open selection. |
|
| <kbd>enter</kbd> | Open selection. |
|
||||||
| <kbd>esc</kbd> | Go back to file list screen (only while searching for files, **Repository > Files** then click on **Find File**). |
|
| <kbd>Escape</kbd> | Go back to file list screen (only while searching for files, **Repository > Files** then click on **Find File**). |
|
||||||
| <kbd>y</kbd> | Go to file permalink (only while viewing a file). |
|
| <kbd>y</kbd> | Go to file permalink (only while viewing a file). |
|
||||||
|
|
||||||
### Web IDE
|
### Web IDE
|
||||||
|
@ -113,8 +113,8 @@ These shortcuts are available when editing a file with the [Web IDE](project/web
|
||||||
|
|
||||||
| Keyboard Shortcut | Description |
|
| Keyboard Shortcut | Description |
|
||||||
| ------------------------------------------------------- | ----------- |
|
| ------------------------------------------------------- | ----------- |
|
||||||
| <kbd>⌘</kbd> (Mac) / <kbd>Ctrl</kbd> + <kbd>p</kbd> | Search for, and then open another file for editing. |
|
| <kbd>⌘</kbd> (Mac) / <kbd>Control</kbd> + <kbd>p</kbd> | Search for, and then open another file for editing. |
|
||||||
| <kbd>⌘</kbd> (Mac) / <kbd>Ctrl</kbd> + <kbd>Enter</kbd> | Commit (when editing the commit message). |
|
| <kbd>⌘</kbd> (Mac) / <kbd>Control</kbd> + <kbd>Enter</kbd> | Commit (when editing the commit message). |
|
||||||
|
|
||||||
### Repository Graph
|
### Repository Graph
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ These shortcuts are available when using a [filtered search input](search/index.
|
||||||
| Keyboard Shortcut | Description |
|
| Keyboard Shortcut | Description |
|
||||||
| ----------------------------------------------------- | ----------- |
|
| ----------------------------------------------------- | ----------- |
|
||||||
| <kbd>⌘</kbd> (Mac) + <kbd>⌫</kbd> | Clear entire search filter. |
|
| <kbd>⌘</kbd> (Mac) + <kbd>⌫</kbd> | Clear entire search filter. |
|
||||||
| <kbd>⌥</kbd> (Mac) / <kbd>Ctrl</kbd> + <kbd>⌫</kbd> | Clear one token at a time. |
|
| <kbd>⌥</kbd> (Mac) / <kbd>Control</kbd> + <kbd>⌫</kbd> | Clear one token at a time. |
|
||||||
|
|
||||||
## Epics **(ULTIMATE)**
|
## Epics **(ULTIMATE)**
|
||||||
|
|
||||||
|
|
|
@ -8291,12 +8291,21 @@ msgstr ""
|
||||||
msgid "CreateValueStreamForm|'%{name}' Value Stream created"
|
msgid "CreateValueStreamForm|'%{name}' Value Stream created"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "CreateValueStreamForm|Add another stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "CreateValueStreamForm|Add stage"
|
msgid "CreateValueStreamForm|Add stage"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "CreateValueStreamForm|All default stages are currently visible"
|
msgid "CreateValueStreamForm|All default stages are currently visible"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "CreateValueStreamForm|Create from default template"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "CreateValueStreamForm|Create from no template"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "CreateValueStreamForm|Default stages"
|
msgid "CreateValueStreamForm|Default stages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -8312,16 +8321,13 @@ msgstr ""
|
||||||
msgid "CreateValueStreamForm|End event: "
|
msgid "CreateValueStreamForm|End event: "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "CreateValueStreamForm|Enter a name for the stage"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "CreateValueStreamForm|Enter stage name"
|
msgid "CreateValueStreamForm|Enter stage name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "CreateValueStreamForm|Maximum length %{maxLength} characters"
|
msgid "CreateValueStreamForm|Enter value stream name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "CreateValueStreamForm|Name"
|
msgid "CreateValueStreamForm|Maximum length %{maxLength} characters"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "CreateValueStreamForm|Name is required"
|
msgid "CreateValueStreamForm|Name is required"
|
||||||
|
@ -8333,6 +8339,9 @@ msgstr ""
|
||||||
msgid "CreateValueStreamForm|Please select a start event first"
|
msgid "CreateValueStreamForm|Please select a start event first"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "CreateValueStreamForm|Please select an end event"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "CreateValueStreamForm|Recover hidden stage"
|
msgid "CreateValueStreamForm|Recover hidden stage"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -8354,6 +8363,9 @@ msgstr ""
|
||||||
msgid "CreateValueStreamForm|Stage name already exists"
|
msgid "CreateValueStreamForm|Stage name already exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "CreateValueStreamForm|Stage name is required"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "CreateValueStreamForm|Start event"
|
msgid "CreateValueStreamForm|Start event"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -8369,6 +8381,9 @@ msgstr ""
|
||||||
msgid "CreateValueStreamForm|Update stage"
|
msgid "CreateValueStreamForm|Update stage"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "CreateValueStreamForm|Value Stream name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Created"
|
msgid "Created"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,23 @@ RSpec.describe 'User comments on a diff', :js do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'allows suggestions in replies' do
|
||||||
|
click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']"))
|
||||||
|
|
||||||
|
page.within('.js-discussion-note-form') do
|
||||||
|
fill_in('note_note', with: "```suggestion\n# change to a comment\n```")
|
||||||
|
click_button('Add comment now')
|
||||||
|
end
|
||||||
|
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
click_button 'Reply...'
|
||||||
|
|
||||||
|
find('.js-suggestion-btn').click
|
||||||
|
|
||||||
|
expect(find('.js-vue-issue-note-form').value).to include("url = https://github.com/gitlabhq/gitlab-shell.git")
|
||||||
|
end
|
||||||
|
|
||||||
it 'suggestion is appliable' do
|
it 'suggestion is appliable' do
|
||||||
click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']"))
|
click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']"))
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe 'User searches for commits' do
|
RSpec.describe 'User searches for commits', :js do
|
||||||
let(:project) { create(:project, :repository) }
|
let(:project) { create(:project, :repository) }
|
||||||
let(:sha) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
|
let(:sha) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
|
@ -41,7 +41,7 @@ RSpec.describe 'User searches for commits' do
|
||||||
submit_search('See merge request')
|
submit_search('See merge request')
|
||||||
select_search_scope('Commits')
|
select_search_scope('Commits')
|
||||||
|
|
||||||
expect(page).to have_selector('.commit-row-description', count: 9)
|
expect(page).to have_selector('.commit-row-description', visible: false, count: 9)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe 'User searches for projects' do
|
RSpec.describe 'User searches for projects', :js do
|
||||||
let!(:project) { create(:project, :public, name: 'Shop') }
|
let!(:project) { create(:project, :public, name: 'Shop') }
|
||||||
|
|
||||||
context 'when signed out' do
|
context 'when signed out' do
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
/* global List */
|
||||||
|
/* global ListLabel */
|
||||||
|
|
||||||
|
import Vuex from 'vuex';
|
||||||
|
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||||
|
|
||||||
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
|
import axios from '~/lib/utils/axios_utils';
|
||||||
|
|
||||||
|
import '~/boards/models/label';
|
||||||
|
import '~/boards/models/assignee';
|
||||||
|
import '~/boards/models/list';
|
||||||
|
import boardsVuexStore from '~/boards/stores';
|
||||||
|
import boardsStore from '~/boards/stores/boards_store';
|
||||||
|
import BoardCardLayout from '~/boards/components/board_card_layout_deprecated.vue';
|
||||||
|
import issueCardInner from '~/boards/components/issue_card_inner.vue';
|
||||||
|
import { listObj, boardsMockInterceptor, setMockEndpoints } from '../mock_data';
|
||||||
|
|
||||||
|
import { ISSUABLE } from '~/boards/constants';
|
||||||
|
|
||||||
|
describe('Board card layout', () => {
|
||||||
|
let wrapper;
|
||||||
|
let mock;
|
||||||
|
let list;
|
||||||
|
let store;
|
||||||
|
|
||||||
|
const localVue = createLocalVue();
|
||||||
|
localVue.use(Vuex);
|
||||||
|
|
||||||
|
const createStore = ({ getters = {}, actions = {} } = {}) => {
|
||||||
|
store = new Vuex.Store({
|
||||||
|
...boardsVuexStore,
|
||||||
|
actions,
|
||||||
|
getters,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// this particular mount component needs to be used after the root beforeEach because it depends on list being initialized
|
||||||
|
const mountComponent = ({ propsData = {}, provide = {} } = {}) => {
|
||||||
|
wrapper = shallowMount(BoardCardLayout, {
|
||||||
|
localVue,
|
||||||
|
stubs: {
|
||||||
|
issueCardInner,
|
||||||
|
},
|
||||||
|
store,
|
||||||
|
propsData: {
|
||||||
|
list,
|
||||||
|
issue: list.issues[0],
|
||||||
|
disabled: false,
|
||||||
|
index: 0,
|
||||||
|
...propsData,
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
groupId: null,
|
||||||
|
rootPath: '/',
|
||||||
|
scopedLabelsAvailable: false,
|
||||||
|
...provide,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupData = () => {
|
||||||
|
list = new List(listObj);
|
||||||
|
boardsStore.create();
|
||||||
|
boardsStore.detail.issue = {};
|
||||||
|
const label1 = new ListLabel({
|
||||||
|
id: 3,
|
||||||
|
title: 'testing 123',
|
||||||
|
color: '#000cff',
|
||||||
|
text_color: 'white',
|
||||||
|
description: 'test',
|
||||||
|
});
|
||||||
|
return waitForPromises().then(() => {
|
||||||
|
list.issues[0].labels.push(label1);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mock = new MockAdapter(axios);
|
||||||
|
mock.onAny().reply(boardsMockInterceptor);
|
||||||
|
setMockEndpoints();
|
||||||
|
return setupData();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
wrapper = null;
|
||||||
|
list = null;
|
||||||
|
mock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('mouse events', () => {
|
||||||
|
it('sets showDetail to true on mousedown', async () => {
|
||||||
|
createStore();
|
||||||
|
mountComponent();
|
||||||
|
|
||||||
|
wrapper.trigger('mousedown');
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.vm.showDetail).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets showDetail to false on mousemove', async () => {
|
||||||
|
createStore();
|
||||||
|
mountComponent();
|
||||||
|
wrapper.trigger('mousedown');
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
expect(wrapper.vm.showDetail).toBe(true);
|
||||||
|
wrapper.trigger('mousemove');
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
expect(wrapper.vm.showDetail).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls 'setActiveId' when 'graphqlBoardLists' feature flag is turned on", async () => {
|
||||||
|
const setActiveId = jest.fn();
|
||||||
|
createStore({
|
||||||
|
actions: {
|
||||||
|
setActiveId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mountComponent({
|
||||||
|
provide: {
|
||||||
|
glFeatures: { graphqlBoardLists: true },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.trigger('mouseup');
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(setActiveId).toHaveBeenCalledTimes(1);
|
||||||
|
expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
|
||||||
|
id: list.issues[0].id,
|
||||||
|
sidebarType: ISSUABLE,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls 'setActiveId' when epic swimlanes is active", async () => {
|
||||||
|
const setActiveId = jest.fn();
|
||||||
|
const isSwimlanesOn = () => true;
|
||||||
|
createStore({
|
||||||
|
getters: { isSwimlanesOn },
|
||||||
|
actions: {
|
||||||
|
setActiveId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mountComponent();
|
||||||
|
|
||||||
|
wrapper.trigger('mouseup');
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(setActiveId).toHaveBeenCalledTimes(1);
|
||||||
|
expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
|
||||||
|
id: list.issues[0].id,
|
||||||
|
sidebarType: ISSUABLE,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,28 +1,15 @@
|
||||||
/* global List */
|
|
||||||
/* global ListLabel */
|
|
||||||
|
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||||
|
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
import defaultState from '~/boards/stores/state';
|
||||||
import waitForPromises from 'helpers/wait_for_promises';
|
|
||||||
import axios from '~/lib/utils/axios_utils';
|
|
||||||
|
|
||||||
import '~/boards/models/label';
|
|
||||||
import '~/boards/models/assignee';
|
|
||||||
import '~/boards/models/list';
|
|
||||||
import boardsVuexStore from '~/boards/stores';
|
|
||||||
import boardsStore from '~/boards/stores/boards_store';
|
|
||||||
import BoardCardLayout from '~/boards/components/board_card_layout.vue';
|
import BoardCardLayout from '~/boards/components/board_card_layout.vue';
|
||||||
import issueCardInner from '~/boards/components/issue_card_inner.vue';
|
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
|
||||||
import { listObj, boardsMockInterceptor, setMockEndpoints } from '../mock_data';
|
import { mockLabelList, mockIssue } from '../mock_data';
|
||||||
|
|
||||||
import { ISSUABLE } from '~/boards/constants';
|
import { ISSUABLE } from '~/boards/constants';
|
||||||
|
|
||||||
describe('Board card layout', () => {
|
describe('Board card layout', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let mock;
|
|
||||||
let list;
|
|
||||||
let store;
|
let store;
|
||||||
|
|
||||||
const localVue = createLocalVue();
|
const localVue = createLocalVue();
|
||||||
|
@ -30,7 +17,7 @@ describe('Board card layout', () => {
|
||||||
|
|
||||||
const createStore = ({ getters = {}, actions = {} } = {}) => {
|
const createStore = ({ getters = {}, actions = {} } = {}) => {
|
||||||
store = new Vuex.Store({
|
store = new Vuex.Store({
|
||||||
...boardsVuexStore,
|
state: defaultState,
|
||||||
actions,
|
actions,
|
||||||
getters,
|
getters,
|
||||||
});
|
});
|
||||||
|
@ -41,12 +28,12 @@ describe('Board card layout', () => {
|
||||||
wrapper = shallowMount(BoardCardLayout, {
|
wrapper = shallowMount(BoardCardLayout, {
|
||||||
localVue,
|
localVue,
|
||||||
stubs: {
|
stubs: {
|
||||||
issueCardInner,
|
IssueCardInner,
|
||||||
},
|
},
|
||||||
store,
|
store,
|
||||||
propsData: {
|
propsData: {
|
||||||
list,
|
list: mockLabelList,
|
||||||
issue: list.issues[0],
|
issue: mockIssue,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
index: 0,
|
index: 0,
|
||||||
...propsData,
|
...propsData,
|
||||||
|
@ -60,34 +47,9 @@ describe('Board card layout', () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupData = () => {
|
|
||||||
list = new List(listObj);
|
|
||||||
boardsStore.create();
|
|
||||||
boardsStore.detail.issue = {};
|
|
||||||
const label1 = new ListLabel({
|
|
||||||
id: 3,
|
|
||||||
title: 'testing 123',
|
|
||||||
color: '#000cff',
|
|
||||||
text_color: 'white',
|
|
||||||
description: 'test',
|
|
||||||
});
|
|
||||||
return waitForPromises().then(() => {
|
|
||||||
list.issues[0].labels.push(label1);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mock = new MockAdapter(axios);
|
|
||||||
mock.onAny().reply(boardsMockInterceptor);
|
|
||||||
setMockEndpoints();
|
|
||||||
return setupData();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
wrapper = null;
|
wrapper = null;
|
||||||
list = null;
|
|
||||||
mock.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('mouse events', () => {
|
describe('mouse events', () => {
|
||||||
|
@ -112,25 +74,21 @@ describe('Board card layout', () => {
|
||||||
expect(wrapper.vm.showDetail).toBe(false);
|
expect(wrapper.vm.showDetail).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls 'setActiveId' when 'graphqlBoardLists' feature flag is turned on", async () => {
|
it("calls 'setActiveId'", async () => {
|
||||||
const setActiveId = jest.fn();
|
const setActiveId = jest.fn();
|
||||||
createStore({
|
createStore({
|
||||||
actions: {
|
actions: {
|
||||||
setActiveId,
|
setActiveId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
mountComponent({
|
mountComponent();
|
||||||
provide: {
|
|
||||||
glFeatures: { graphqlBoardLists: true },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
wrapper.trigger('mouseup');
|
wrapper.trigger('mouseup');
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
expect(setActiveId).toHaveBeenCalledTimes(1);
|
expect(setActiveId).toHaveBeenCalledTimes(1);
|
||||||
expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
|
expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
|
||||||
id: list.issues[0].id,
|
id: mockIssue.id,
|
||||||
sidebarType: ISSUABLE,
|
sidebarType: ISSUABLE,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -151,7 +109,7 @@ describe('Board card layout', () => {
|
||||||
|
|
||||||
expect(setActiveId).toHaveBeenCalledTimes(1);
|
expect(setActiveId).toHaveBeenCalledTimes(1);
|
||||||
expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
|
expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
|
||||||
id: list.issues[0].id,
|
id: mockIssue.id,
|
||||||
sidebarType: ISSUABLE,
|
sidebarType: ISSUABLE,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ describe('BoardConfigurationOptions', () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
hideBacklogList: false,
|
hideBacklogList: false,
|
||||||
hideClosedList: false,
|
hideClosedList: false,
|
||||||
|
readonly: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const createComponent = (props = {}) => {
|
const createComponent = (props = {}) => {
|
||||||
|
@ -61,4 +62,18 @@ describe('BoardConfigurationOptions', () => {
|
||||||
|
|
||||||
expect(wrapper.emitted('update:hideClosedList')).toEqual([[true]]);
|
expect(wrapper.emitted('update:hideClosedList')).toEqual([[true]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders checkboxes disabled when user does not have edit rights', () => {
|
||||||
|
createComponent({ readonly: true });
|
||||||
|
|
||||||
|
expect(closedListCheckbox().attributes('disabled')).toBe('true');
|
||||||
|
expect(backlogListCheckbox().attributes('disabled')).toBe('true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders checkboxes enabled when user has edit rights', () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
expect(closedListCheckbox().attributes('disabled')).toBeUndefined();
|
||||||
|
expect(backlogListCheckbox().attributes('disabled')).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,7 +20,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
|
||||||
wrapper = null;
|
wrapper = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const createWrapper = ({ milestone = null } = {}) => {
|
const createWrapper = ({ milestone = null, loading = false } = {}) => {
|
||||||
store = createStore();
|
store = createStore();
|
||||||
store.state.issues = { [TEST_ISSUE.id]: { ...TEST_ISSUE, milestone } };
|
store.state.issues = { [TEST_ISSUE.id]: { ...TEST_ISSUE, milestone } };
|
||||||
store.state.activeId = TEST_ISSUE.id;
|
store.state.activeId = TEST_ISSUE.id;
|
||||||
|
@ -38,7 +38,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
|
||||||
},
|
},
|
||||||
mocks: {
|
mocks: {
|
||||||
$apollo: {
|
$apollo: {
|
||||||
loading: false,
|
loading,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -63,12 +63,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows loader while Apollo is loading', async () => {
|
it('shows loader while Apollo is loading', async () => {
|
||||||
createWrapper({ milestone: TEST_MILESTONE });
|
createWrapper({ milestone: TEST_MILESTONE, loading: true });
|
||||||
|
|
||||||
expect(findLoader().exists()).toBe(false);
|
|
||||||
|
|
||||||
wrapper.vm.$apollo.loading = true;
|
|
||||||
await wrapper.vm.$nextTick();
|
|
||||||
|
|
||||||
expect(findLoader().exists()).toBe(true);
|
expect(findLoader().exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -76,8 +71,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
|
||||||
it('shows message when error or no milestones found', async () => {
|
it('shows message when error or no milestones found', async () => {
|
||||||
createWrapper();
|
createWrapper();
|
||||||
|
|
||||||
wrapper.setData({ milestones: [] });
|
await wrapper.setData({ milestones: [] });
|
||||||
await wrapper.vm.$nextTick();
|
|
||||||
|
|
||||||
expect(findNoMilestonesFoundItem().text()).toBe('No milestones found');
|
expect(findNoMilestonesFoundItem().text()).toBe('No milestones found');
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,7 +41,7 @@ describe('Issue model', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(issue.labels.length).toBe(1);
|
expect(issue.labels.length).toBe(1);
|
||||||
expect(issue.labels[0].color).toBe('red');
|
expect(issue.labels[0].color).toBe('#F0AD4E');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds other label with same title', () => {
|
it('adds other label with same title', () => {
|
||||||
|
|
|
@ -137,7 +137,7 @@ export const rawIssue = {
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'test',
|
title: 'test',
|
||||||
color: 'red',
|
color: '#F0AD4E',
|
||||||
description: 'testing',
|
description: 'testing',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -165,7 +165,7 @@ export const mockIssue = {
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'test',
|
title: 'test',
|
||||||
color: 'red',
|
color: '#F0AD4E',
|
||||||
description: 'testing',
|
description: 'testing',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1222,6 +1222,40 @@ describe('setSelectedProject', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('toggleBoardItemMultiSelection', () => {
|
||||||
|
const boardItem = mockIssue;
|
||||||
|
|
||||||
|
it('should commit mutation ADD_BOARD_ITEM_TO_SELECTION if item is not on selection state', () => {
|
||||||
|
testAction(
|
||||||
|
actions.toggleBoardItemMultiSelection,
|
||||||
|
boardItem,
|
||||||
|
{ selectedBoardItems: [] },
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: types.ADD_BOARD_ITEM_TO_SELECTION,
|
||||||
|
payload: boardItem,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should commit mutation REMOVE_BOARD_ITEM_FROM_SELECTION if item is on selection state', () => {
|
||||||
|
testAction(
|
||||||
|
actions.toggleBoardItemMultiSelection,
|
||||||
|
boardItem,
|
||||||
|
{ selectedBoardItems: [mockIssue] },
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: types.REMOVE_BOARD_ITEM_FROM_SELECTION,
|
||||||
|
payload: boardItem,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('fetchBacklog', () => {
|
describe('fetchBacklog', () => {
|
||||||
expectNotImplemented(actions.fetchBacklog);
|
expectNotImplemented(actions.fetchBacklog);
|
||||||
});
|
});
|
||||||
|
|
|
@ -594,4 +594,27 @@ describe('Board Store Mutations', () => {
|
||||||
expect(state.selectedProject).toEqual(mockGroupProjects[0]);
|
expect(state.selectedProject).toEqual(mockGroupProjects[0]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ADD_BOARD_ITEM_TO_SELECTION', () => {
|
||||||
|
it('Should add boardItem to selectedBoardItems state', () => {
|
||||||
|
expect(state.selectedBoardItems).toEqual([]);
|
||||||
|
|
||||||
|
mutations[types.ADD_BOARD_ITEM_TO_SELECTION](state, mockIssue);
|
||||||
|
|
||||||
|
expect(state.selectedBoardItems).toEqual([mockIssue]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('REMOVE_BOARD_ITEM_FROM_SELECTION', () => {
|
||||||
|
it('Should remove boardItem to selectedBoardItems state', () => {
|
||||||
|
state = {
|
||||||
|
...state,
|
||||||
|
selectedBoardItems: [mockIssue],
|
||||||
|
};
|
||||||
|
|
||||||
|
mutations[types.REMOVE_BOARD_ITEM_FROM_SELECTION](state, mockIssue);
|
||||||
|
|
||||||
|
expect(state.selectedBoardItems).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import setHighlightClass from '~/search/highlight_blob_search_result';
|
import setHighlightClass from '~/search/highlight_blob_search_result';
|
||||||
|
|
||||||
const fixture = 'search/blob_search_result.html';
|
const fixture = 'search/blob_search_result.html';
|
||||||
|
const searchKeyword = 'Send'; // spec/frontend/fixtures/search.rb#79
|
||||||
|
|
||||||
describe('search/highlight_blob_search_result', () => {
|
describe('search/highlight_blob_search_result', () => {
|
||||||
preloadFixtures(fixture);
|
preloadFixtures(fixture);
|
||||||
|
@ -8,7 +9,7 @@ describe('search/highlight_blob_search_result', () => {
|
||||||
beforeEach(() => loadFixtures(fixture));
|
beforeEach(() => loadFixtures(fixture));
|
||||||
|
|
||||||
it('highlights lines with search term occurrence', () => {
|
it('highlights lines with search term occurrence', () => {
|
||||||
setHighlightClass();
|
setHighlightClass(searchKeyword);
|
||||||
|
|
||||||
expect(document.querySelectorAll('.blob-result .hll').length).toBe(4);
|
expect(document.querySelectorAll('.blob-result .hll').length).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result';
|
||||||
import { initSearchApp } from '~/search';
|
import { initSearchApp } from '~/search';
|
||||||
import createStore from '~/search/store';
|
import createStore from '~/search/store';
|
||||||
|
|
||||||
jest.mock('~/search/store');
|
jest.mock('~/search/store');
|
||||||
jest.mock('~/search/topbar');
|
jest.mock('~/search/topbar');
|
||||||
jest.mock('~/search/sidebar');
|
jest.mock('~/search/sidebar');
|
||||||
|
jest.mock('ee_else_ce/search/highlight_blob_search_result');
|
||||||
|
|
||||||
describe('initSearchApp', () => {
|
describe('initSearchApp', () => {
|
||||||
let defaultLocation;
|
let defaultLocation;
|
||||||
|
@ -42,6 +44,7 @@ describe('initSearchApp', () => {
|
||||||
|
|
||||||
it(`decodes ${search} to ${decodedSearch}`, () => {
|
it(`decodes ${search} to ${decodedSearch}`, () => {
|
||||||
expect(createStore).toHaveBeenCalledWith({ query: { search: decodedSearch } });
|
expect(createStore).toHaveBeenCalledWith({ query: { search: decodedSearch } });
|
||||||
|
expect(setHighlightClass).toHaveBeenCalledWith(decodedSearch);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
import Vuex from 'vuex';
|
||||||
|
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||||
|
import { GlForm, GlSearchBoxByType, GlButton } from '@gitlab/ui';
|
||||||
|
import { MOCK_QUERY } from 'jest/search/mock_data';
|
||||||
|
import GlobalSearchTopbar from '~/search/topbar/components/app.vue';
|
||||||
|
import GroupFilter from '~/search/topbar/components/group_filter.vue';
|
||||||
|
import ProjectFilter from '~/search/topbar/components/project_filter.vue';
|
||||||
|
|
||||||
|
const localVue = createLocalVue();
|
||||||
|
localVue.use(Vuex);
|
||||||
|
|
||||||
|
describe('GlobalSearchTopbar', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
const actionSpies = {
|
||||||
|
applyQuery: jest.fn(),
|
||||||
|
setQuery: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const createComponent = (initialState) => {
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
state: {
|
||||||
|
query: MOCK_QUERY,
|
||||||
|
...initialState,
|
||||||
|
},
|
||||||
|
actions: actionSpies,
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper = shallowMount(GlobalSearchTopbar, {
|
||||||
|
localVue,
|
||||||
|
store,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
wrapper = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const findTopbarForm = () => wrapper.find(GlForm);
|
||||||
|
const findGlSearchBox = () => wrapper.find(GlSearchBoxByType);
|
||||||
|
const findGroupFilter = () => wrapper.find(GroupFilter);
|
||||||
|
const findProjectFilter = () => wrapper.find(ProjectFilter);
|
||||||
|
const findSearchButton = () => wrapper.find(GlButton);
|
||||||
|
|
||||||
|
describe('template', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders Topbar Form always', () => {
|
||||||
|
expect(findTopbarForm().exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Search box', () => {
|
||||||
|
it('renders always', () => {
|
||||||
|
expect(findGlSearchBox().exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onSearch', () => {
|
||||||
|
const testSearch = 'test search';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
findGlSearchBox().vm.$emit('input', testSearch);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setQuery when input event is fired from GlSearchBoxByType', () => {
|
||||||
|
expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), {
|
||||||
|
key: 'search',
|
||||||
|
value: testSearch,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each`
|
||||||
|
snippets | showFilters
|
||||||
|
${null} | ${true}
|
||||||
|
${{ query: { snippets: '' } }} | ${true}
|
||||||
|
${{ query: { snippets: false } }} | ${true}
|
||||||
|
${{ query: { snippets: true } }} | ${false}
|
||||||
|
${{ query: { snippets: 'false' } }} | ${true}
|
||||||
|
${{ query: { snippets: 'true' } }} | ${false}
|
||||||
|
`('topbar filters', ({ snippets, showFilters }) => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent(snippets);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`does${showFilters ? '' : ' not'} render when snippets is ${JSON.stringify(
|
||||||
|
snippets,
|
||||||
|
)}`, () => {
|
||||||
|
expect(findGroupFilter().exists()).toBe(showFilters);
|
||||||
|
expect(findProjectFilter().exists()).toBe(showFilters);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders SearchButton always', () => {
|
||||||
|
expect(findSearchButton().exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('actions', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clicking SearchButton calls applyQuery', () => {
|
||||||
|
findTopbarForm().vm.$emit('submit', { preventDefault: () => {} });
|
||||||
|
|
||||||
|
expect(actionSpies.applyQuery).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,23 +0,0 @@
|
||||||
import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result';
|
|
||||||
import Search from '~/pages/search/show/search';
|
|
||||||
|
|
||||||
jest.mock('~/api');
|
|
||||||
jest.mock('ee_else_ce/search/highlight_blob_search_result');
|
|
||||||
|
|
||||||
describe('Search', () => {
|
|
||||||
const fixturePath = 'search/show.html';
|
|
||||||
|
|
||||||
preloadFixtures(fixturePath);
|
|
||||||
|
|
||||||
describe('constructor side effects', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
jest.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('highlights lines with search terms in blob search results', () => {
|
|
||||||
new Search(); // eslint-disable-line no-new
|
|
||||||
|
|
||||||
expect(setHighlightClass).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -221,7 +221,6 @@ RSpec.configure do |config|
|
||||||
# of older sidebar.
|
# of older sidebar.
|
||||||
# See https://gitlab.com/groups/gitlab-org/-/epics/1863
|
# See https://gitlab.com/groups/gitlab-org/-/epics/1863
|
||||||
stub_feature_flags(vue_issuable_sidebar: false)
|
stub_feature_flags(vue_issuable_sidebar: false)
|
||||||
stub_feature_flags(vue_issuable_epic_sidebar: false)
|
|
||||||
|
|
||||||
# Merge request widget GraphQL requests are disabled in the tests
|
# Merge request widget GraphQL requests are disabled in the tests
|
||||||
# for now whilst we migrate as much as we can over the GraphQL
|
# for now whilst we migrate as much as we can over the GraphQL
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'search/_filter' do
|
|
||||||
context 'when the search page is opened' do
|
|
||||||
it 'displays the correct elements' do
|
|
||||||
render
|
|
||||||
|
|
||||||
expect(rendered).to have_selector('label[for="dashboard_search_group"]')
|
|
||||||
expect(rendered).to have_selector('input#js-search-group-dropdown')
|
|
||||||
|
|
||||||
expect(rendered).to have_selector('label[for="dashboard_search_project"]')
|
|
||||||
expect(rendered).to have_selector('input#js-search-project-dropdown')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,14 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'search/_form' do
|
|
||||||
context 'when the search page is opened' do
|
|
||||||
it 'displays the correct elements' do
|
|
||||||
render
|
|
||||||
|
|
||||||
expect(rendered).to have_selector('.search-field-holder.form-group')
|
|
||||||
expect(rendered).to have_selector('label[for="dashboard_search"]')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
56
yarn.lock
56
yarn.lock
|
@ -2486,14 +2486,15 @@ browserify-zlib@^0.2.0:
|
||||||
pako "~1.0.5"
|
pako "~1.0.5"
|
||||||
|
|
||||||
browserslist@^4.12.0, browserslist@^4.6.3, browserslist@^4.8.3:
|
browserslist@^4.12.0, browserslist@^4.6.3, browserslist@^4.8.3:
|
||||||
version "4.12.0"
|
version "4.16.1"
|
||||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d"
|
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.1.tgz#bf757a2da376b3447b800a16f0f1c96358138766"
|
||||||
integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==
|
integrity sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite "^1.0.30001043"
|
caniuse-lite "^1.0.30001173"
|
||||||
electron-to-chromium "^1.3.413"
|
colorette "^1.2.1"
|
||||||
node-releases "^1.1.53"
|
electron-to-chromium "^1.3.634"
|
||||||
pkg-up "^2.0.0"
|
escalade "^3.1.1"
|
||||||
|
node-releases "^1.1.69"
|
||||||
|
|
||||||
bs-logger@0.x:
|
bs-logger@0.x:
|
||||||
version "0.2.6"
|
version "0.2.6"
|
||||||
|
@ -2717,10 +2718,10 @@ camelcase@^6.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
|
||||||
integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
|
integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30001043:
|
caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30001173:
|
||||||
version "1.0.30001081"
|
version "1.0.30001178"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001081.tgz#40615a3c416a047c5a4d45673e5257bf128eb3b5"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001178.tgz#3ad813b2b2c7d585b0be0a2440e1e233c6eabdbc"
|
||||||
integrity sha512-iZdh3lu09jsUtLE6Bp8NAbJskco4Y3UDtkR3GTCJGsbMowBU5IWDFF79sV2ws7lSqTzWyKazxam2thasHymENQ==
|
integrity sha512-VtdZLC0vsXykKni8Uztx45xynytOi71Ufx9T8kHptSw9AL4dpqailUJJHavttuzUe1KYuBYtChiWv+BAb7mPmQ==
|
||||||
|
|
||||||
capture-exit@^2.0.0:
|
capture-exit@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
@ -3050,6 +3051,11 @@ color-name@~1.1.4:
|
||||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
|
||||||
|
colorette@^1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
|
||||||
|
integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
|
||||||
|
|
||||||
colors@^1.1.0:
|
colors@^1.1.0:
|
||||||
version "1.3.3"
|
version "1.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
|
resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
|
||||||
|
@ -4283,10 +4289,10 @@ ejs@^2.6.1:
|
||||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
|
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
|
||||||
integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
|
integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
|
||||||
|
|
||||||
electron-to-chromium@^1.3.413:
|
electron-to-chromium@^1.3.634:
|
||||||
version "1.3.466"
|
version "1.3.642"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.466.tgz#89f716db3afc4bb482ea2aaaa16c4808f89f762a"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.642.tgz#8b884f50296c2ae2a9997f024d0e3e57facc2b94"
|
||||||
integrity sha512-eieqkoM2hCkZZRhETKyCouMziDV3l4XEKHRLuzcHG+HV+P7PeODU/z9HAmBgMQkzvHg2DoyQhfIDmmeguLZT/Q==
|
integrity sha512-cev+jOrz/Zm1i+Yh334Hed6lQVOkkemk2wRozfMF4MtTR7pxf3r3L5Rbd7uX1zMcEqVJ7alJBnJL7+JffkC6FQ==
|
||||||
|
|
||||||
elliptic@^6.0.0:
|
elliptic@^6.0.0:
|
||||||
version "6.4.0"
|
version "6.4.0"
|
||||||
|
@ -4480,6 +4486,11 @@ es6-promisify@^5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
es6-promise "^4.0.3"
|
es6-promise "^4.0.3"
|
||||||
|
|
||||||
|
escalade@^3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||||
|
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
|
||||||
|
|
||||||
escape-goat@^2.0.0:
|
escape-goat@^2.0.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
|
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
|
||||||
|
@ -8636,10 +8647,10 @@ node-notifier@^8.0.0:
|
||||||
uuid "^8.3.0"
|
uuid "^8.3.0"
|
||||||
which "^2.0.2"
|
which "^2.0.2"
|
||||||
|
|
||||||
node-releases@^1.1.53:
|
node-releases@^1.1.69:
|
||||||
version "1.1.58"
|
version "1.1.70"
|
||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.58.tgz#8ee20eef30fa60e52755fcc0942def5a734fe935"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.70.tgz#66e0ed0273aa65666d7fe78febe7634875426a08"
|
||||||
integrity sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==
|
integrity sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==
|
||||||
|
|
||||||
node-sass@^4.14.1:
|
node-sass@^4.14.1:
|
||||||
version "4.14.1"
|
version "4.14.1"
|
||||||
|
@ -9362,13 +9373,6 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
find-up "^4.0.0"
|
find-up "^4.0.0"
|
||||||
|
|
||||||
pkg-up@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f"
|
|
||||||
integrity sha1-yBmscoBZpGHKscOImivjxJoATX8=
|
|
||||||
dependencies:
|
|
||||||
find-up "^2.1.0"
|
|
||||||
|
|
||||||
pngjs@^3.0.0:
|
pngjs@^3.0.0:
|
||||||
version "3.3.3"
|
version "3.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b"
|
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b"
|
||||||
|
|
Loading…
Reference in New Issue