diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-12 12:11:05 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-12 12:11:05 +0000 |
commit | 2fe5ea34a5f63661a050404d3b5fbe3056a39765 (patch) | |
tree | 630d5b896d2933d38a206abf4e2baf7e1b773027 | |
parent | dcb517514405d6f550cc077686889dbb34c37c75 (diff) | |
download | gitlab-ce-2fe5ea34a5f63661a050404d3b5fbe3056a39765.tar.gz |
Add latest changes from gitlab-org/gitlab@master
124 files changed, 812 insertions, 1224 deletions
diff --git a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js index 177ea4c2e7d..c6d32fb8547 100644 --- a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js +++ b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js @@ -14,18 +14,6 @@ export default CodeBlockLowlight.extend({ }; }, }, - /* `params` is the name of the attribute that - prosemirror-markdown uses to extract the language - of a codeblock. - https://github.com/ProseMirror/prosemirror-markdown/blob/master/src/to_markdown.js#L62 - */ - params: { - parseHTML: (element) => { - return { - params: extractLanguage(element), - }; - }, - }, class: { default: 'code highlight js-syntax-highlight', }, diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js index 702344f5514..5c59fb7710a 100644 --- a/app/assets/javascripts/content_editor/services/markdown_serializer.js +++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js @@ -51,7 +51,13 @@ const defaultSerializerConfig = { nodes: { [Blockquote.name]: defaultMarkdownSerializer.nodes.blockquote, [BulletList.name]: defaultMarkdownSerializer.nodes.bullet_list, - [CodeBlockHighlight.name]: defaultMarkdownSerializer.nodes.code_block, + [CodeBlockHighlight.name]: (state, node) => { + state.write(`\`\`\`${node.attrs.language || ''}\n`); + state.text(node.textContent, false); + state.ensureNewLine(); + state.write('```'); + state.closeBlock(node); + }, [Emoji.name]: (state, node) => { const { name } = node.attrs; diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel.vue b/app/assets/javascripts/monitoring/components/dashboard_panel.vue index b786d015f3b..446c6b52602 100644 --- a/app/assets/javascripts/monitoring/components/dashboard_panel.vue +++ b/app/assets/javascripts/monitoring/components/dashboard_panel.vue @@ -404,21 +404,16 @@ export default { --> <gl-dropdown v-gl-tooltip + icon="ellipsis_v" + :text="__('More actions')" + :text-sr-only="true" toggle-class="gl-px-3!" no-caret data-qa-selector="prometheus_widgets_dropdown" right :title="__('More actions')" > - <template #button-content> - <gl-icon class="gl-mr-0!" name="ellipsis_v" /> - </template> - <gl-dropdown-item - v-if="expandBtnAvailable" - ref="expandBtn" - :href="clipboardText" - @click.prevent="onExpand" - > + <gl-dropdown-item v-if="expandBtnAvailable" ref="expandBtn" @click.prevent="onExpand"> {{ s__('Metrics|Expand panel') }} </gl-dropdown-item> <gl-dropdown-item diff --git a/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue b/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue index b7832ca679c..55179947756 100644 --- a/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue +++ b/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue @@ -55,12 +55,13 @@ export default { }, getUpdateVariables(dropdownLabels) { const currentLabelIds = this.selectedLabels.map((label) => label.id); - const userAddedLabelIds = dropdownLabels - .filter((label) => label.set) - .map((label) => label.id); - const userRemovedLabelIds = dropdownLabels - .filter((label) => !label.set) - .map((label) => label.id); + const dropdownLabelIds = dropdownLabels.map((label) => label.id); + const userAddedLabelIds = this.glFeatures.labelsWidget + ? difference(dropdownLabelIds, currentLabelIds) + : dropdownLabels.filter((label) => label.set).map((label) => label.id); + const userRemovedLabelIds = this.glFeatures.labelsWidget + ? difference(currentLabelIds, dropdownLabelIds) + : dropdownLabels.filter((label) => !label.set).map((label) => label.id); const labelIds = difference(union(currentLabelIds, userAddedLabelIds), userRemovedLabelIds); @@ -155,7 +156,7 @@ export default { :labels-manage-path="labelsManagePath" :labels-select-in-progress="isLabelsSelectInProgress" :selected-labels="selectedLabels" - :variant="$options.sidebar" + :variant="$options.variant" data-qa-selector="labels_block" @onDropdownClose="handleDropdownClose" @onLabelRemove="handleLabelRemove" diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js index 8837f3601e4..031472a7d20 100644 --- a/app/assets/javascripts/sidebar/mount_sidebar.js +++ b/app/assets/javascripts/sidebar/mount_sidebar.js @@ -24,6 +24,7 @@ import SidebarDropdownWidget from '~/sidebar/components/sidebar_dropdown_widget. import SidebarTodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue'; import { apolloProvider } from '~/sidebar/graphql'; import trackShowInviteMemberLink from '~/sidebar/track_invite_members'; +import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants'; import Translate from '../vue_shared/translate'; import SidebarAssignees from './components/assignees/sidebar_assignees.vue'; import CopyEmailToClipboard from './components/copy_email_to_clipboard.vue'; @@ -256,6 +257,7 @@ export function mountSidebarLabels() { allowLabelEdit: parseBoolean(el.dataset.canEdit), allowScopedLabels: parseBoolean(el.dataset.allowScopedLabels), initiallySelectedLabels: JSON.parse(el.dataset.selectedLabels), + variant: DropdownVariant.Sidebar, }, render: (createElement) => createElement(SidebarLabels), }); diff --git a/app/assets/javascripts/tracking/constants.js b/app/assets/javascripts/tracking/constants.js index dfca633dc24..598111e4086 100644 --- a/app/assets/javascripts/tracking/constants.js +++ b/app/assets/javascripts/tracking/constants.js @@ -18,3 +18,9 @@ export const DEFAULT_SNOWPLOW_OPTIONS = { fields: { allow: [] }, }, }; + +export const ACTION_ATTR_SELECTOR = '[data-track-action]'; +export const LOAD_ACTION_ATTR_SELECTOR = '[data-track-action="render"]'; + +export const DEPRECATED_EVENT_ATTR_SELECTOR = '[data-track-event]'; +export const DEPRECATED_LOAD_EVENT_ATTR_SELECTOR = '[data-track-event="render"]'; diff --git a/app/assets/javascripts/tracking/tracking.js b/app/assets/javascripts/tracking/tracking.js index 7ae3a8cd834..a1f745bc172 100644 --- a/app/assets/javascripts/tracking/tracking.js +++ b/app/assets/javascripts/tracking/tracking.js @@ -1,3 +1,4 @@ +import { LOAD_ACTION_ATTR_SELECTOR, DEPRECATED_LOAD_EVENT_ATTR_SELECTOR } from './constants'; import { dispatchSnowplowEvent } from './dispatch_snowplow_event'; import getStandardContext from './get_standard_context'; import { getEventHandlers, createEventPayload, renameKey, addExperimentContext } from './utils'; @@ -98,7 +99,7 @@ export default class Tracking { } const loadEvents = parent.querySelectorAll( - '[data-track-action="render"], [data-track-event="render"]', + `${LOAD_ACTION_ATTR_SELECTOR}, ${DEPRECATED_LOAD_EVENT_ATTR_SELECTOR}`, ); loadEvents.forEach((element) => { diff --git a/app/assets/javascripts/tracking/utils.js b/app/assets/javascripts/tracking/utils.js index 3647280c41a..1189b2168ad 100644 --- a/app/assets/javascripts/tracking/utils.js +++ b/app/assets/javascripts/tracking/utils.js @@ -1,6 +1,12 @@ import { omitBy, isUndefined } from 'lodash'; import { TRACKING_CONTEXT_SCHEMA } from '~/experimentation/constants'; import { getExperimentData } from '~/experimentation/utils'; +import { + ACTION_ATTR_SELECTOR, + LOAD_ACTION_ATTR_SELECTOR, + DEPRECATED_EVENT_ATTR_SELECTOR, + DEPRECATED_LOAD_EVENT_ATTR_SELECTOR, +} from './constants'; export const addExperimentContext = (opts) => { const { experiment, ...options } = opts; @@ -65,7 +71,9 @@ export const createEventPayload = (el, { suffix = '' } = {}) => { }; export const eventHandler = (e, func, opts = {}) => { - const el = e.target.closest('[data-track-event], [data-track-action]'); + const actionSelector = `${ACTION_ATTR_SELECTOR}:not(${LOAD_ACTION_ATTR_SELECTOR})`; + const deprecatedEventSelector = `${DEPRECATED_EVENT_ATTR_SELECTOR}:not(${DEPRECATED_LOAD_EVENT_ATTR_SELECTOR})`; + const el = e.target.closest(`${actionSelector}, ${deprecatedEventSelector}`); if (!el) { return; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue index 1f0704f7308..6694e349b6e 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue @@ -21,9 +21,29 @@ export default { type: String, required: true, }, + selectedLabels: { + type: Array, + required: true, + }, + allowMultiselect: { + type: Boolean, + required: true, + }, + labelsListTitle: { + type: String, + required: true, + }, + footerCreateLabelTitle: { + type: String, + required: true, + }, + footerManageLabelTitle: { + type: String, + required: true, + }, }, computed: { - ...mapState(['showDropdownContentsCreateView', 'labelsListTitle']), + ...mapState(['showDropdownContentsCreateView']), ...mapGetters(['isDropdownVariantSidebar', 'isDropdownVariantEmbedded']), dropdownContentsView() { if (this.showDropdownContentsCreateView) { @@ -75,6 +95,16 @@ export default { @click="toggleDropdownContents" /> </div> - <component :is="dropdownContentsView" @hideCreateView="toggleDropdownContentsCreateView" /> + <component + :is="dropdownContentsView" + :selected-labels="selectedLabels" + :allow-multiselect="allowMultiselect" + :labels-list-title="labelsListTitle" + :footer-create-label-title="footerCreateLabelTitle" + :footer-manage-label-title="footerManageLabelTitle" + @hideCreateView="toggleDropdownContentsCreateView" + @closeDropdown="$emit('closeDropdown', $event)" + @toggleDropdownContentsCreateView="toggleDropdownContentsCreateView" + /> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue index bff34743344..ffa37424c2c 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue @@ -1,38 +1,91 @@ <script> -import { GlIntersectionObserver, GlLoadingIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui'; +import { GlLoadingIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; -import { mapState, mapGetters, mapActions } from 'vuex'; - +import { debounce } from 'lodash'; +import createFlash from '~/flash'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes'; - +import { __ } from '~/locale'; +import { DropdownVariant } from './constants'; +import projectLabelsQuery from './graphql/project_labels.query.graphql'; import LabelItem from './label_item.vue'; export default { components: { - GlIntersectionObserver, GlLoadingIcon, GlSearchBoxByType, GlLink, LabelItem, }, + inject: ['projectPath', 'allowLabelCreate', 'labelsManagePath', 'variant'], + props: { + selectedLabels: { + type: Array, + required: true, + }, + allowMultiselect: { + type: Boolean, + required: true, + }, + labelsListTitle: { + type: String, + required: true, + }, + footerCreateLabelTitle: { + type: String, + required: true, + }, + footerManageLabelTitle: { + type: String, + required: true, + }, + }, data() { return { searchKey: '', + labels: [], currentHighlightItem: -1, + localSelectedLabels: [...this.selectedLabels], }; }, + apollo: { + labels: { + query: projectLabelsQuery, + variables() { + return { + fullPath: this.projectPath, + searchTerm: this.searchKey, + }; + }, + skip() { + return this.searchKey.length === 1; + }, + update: (data) => data.workspace?.labels?.nodes || [], + async result() { + if (this.$refs.searchInput) { + await this.$nextTick(); + this.$refs.searchInput.focusInput(); + } + }, + error() { + createFlash({ message: __('Error fetching labels.') }); + }, + }, + }, computed: { - ...mapState([ - 'allowLabelCreate', - 'allowMultiselect', - 'labelsManagePath', - 'labels', - 'labelsFetchInProgress', - 'labelsListTitle', - 'footerCreateLabelTitle', - 'footerManageLabelTitle', - ]), - ...mapGetters(['selectedLabelsList', 'isDropdownVariantSidebar', 'isDropdownVariantEmbedded']), + isDropdownVariantSidebar() { + return this.variant === DropdownVariant.Sidebar; + }, + isDropdownVariantEmbedded() { + return this.variant === DropdownVariant.Embedded; + }, + labelsFetchInProgress() { + return this.$apollo.queries.labels.loading; + }, + localSelectedLabelsIds() { + return this.localSelectedLabels.map((label) => label.id); + }, visibleLabels() { if (this.searchKey) { return fuzzaldrinPlus.filter(this.labels, this.searchKey, { @@ -55,17 +108,16 @@ export default { } }, }, + created() { + this.debouncedSearchKeyUpdate = debounce(this.setSearchKey, DEFAULT_DEBOUNCE_AND_THROTTLE_MS); + }, + beforeDestroy() { + this.$emit('closeDropdown', this.localSelectedLabels); + this.debouncedSearchKeyUpdate.cancel(); + }, methods: { - ...mapActions([ - 'toggleDropdownContents', - 'toggleDropdownContentsCreateView', - 'fetchLabels', - 'receiveLabelsSuccess', - 'updateSelectedLabels', - 'toggleDropdownContents', - ]), isLabelSelected(label) { - return this.selectedLabelsList.includes(label.id); + return this.localSelectedLabelsIds.includes(getIdFromGraphQLId(label.id)); }, /** * This method scrolls item from dropdown into @@ -86,23 +138,17 @@ export default { } } }, - handleComponentAppear() { - // We can avoid putting `catch` block here - // as failure is handled within actions.js already. - return this.fetchLabels().then(() => { - this.$refs.searchInput.focusInput(); - }); - }, - /** - * We want to remove loaded labels to ensure component - * fetches fresh set of labels every time when shown. - */ - handleComponentDisappear() { - this.receiveLabelsSuccess([]); - }, - handleCreateLabelClick() { - this.receiveLabelsSuccess([]); - this.toggleDropdownContentsCreateView(); + updateSelectedLabels(label) { + if (this.isLabelSelected(label)) { + this.localSelectedLabels = this.localSelectedLabels.filter( + ({ id }) => id !== getIdFromGraphQLId(label.id), + ); + } else { + this.localSelectedLabels.push({ + ...label, + id: getIdFromGraphQLId(label.id), + }); + } }, /** * This method enables keyboard navigation support for @@ -117,10 +163,10 @@ export default { ) { this.currentHighlightItem += 1; } else if (e.keyCode === ENTER_KEY_CODE && this.currentHighlightItem > -1) { - this.updateSelectedLabels([this.visibleLabels[this.currentHighlightItem]]); + this.updateSelectedLabels(this.visibleLabels[this.currentHighlightItem]); this.searchKey = ''; } else if (e.keyCode === ESC_KEY_CODE) { - this.toggleDropdownContents(); + this.$emit('closeDropdown', this.localSelectedLabels); } if (e.keyCode !== ESC_KEY_CODE) { @@ -132,68 +178,82 @@ export default { } }, handleLabelClick(label) { - this.updateSelectedLabels([label]); - if (!this.allowMultiselect) this.toggleDropdownContents(); + this.updateSelectedLabels(label); + if (!this.allowMultiselect) { + this.$emit('closeDropdown', this.localSelectedLabels); + } + }, + setSearchKey(value) { + this.searchKey = value; }, }, }; </script> <template> - <gl-intersection-observer @appear="handleComponentAppear" @disappear="handleComponentDisappear"> - <div class="labels-select-contents-list js-labels-list" @keydown="handleKeyDown"> - <div class="dropdown-input" @click.stop="() => {}"> - <gl-search-box-by-type - ref="searchInput" - v-model="searchKey" - :disabled="labelsFetchInProgress" - data-qa-selector="dropdown_input_field" - /> - </div> - <div ref="labelsListContainer" class="dropdown-content" data-testid="dropdown-content"> - <gl-loading-icon - v-if="labelsFetchInProgress" - class="labels-fetch-loading gl-align-items-center w-100 h-100" - size="md" + <div + class="labels-select-contents-list js-labels-list" + data-testid="dropdown-wrapper" + @keydown="handleKeyDown" + > + <div class="dropdown-input" @click.stop="() => {}"> + <gl-search-box-by-type + ref="searchInput" + :value="searchKey" + :disabled="labelsFetchInProgress" + data-qa-selector="dropdown_input_field" + data-testid="dropdown-input-field" + @input="debouncedSearchKeyUpdate" + /> + </div> + <div ref="labelsListContainer" class="dropdown-content" data-testid="dropdown-content"> + <gl-loading-icon + v-if="labelsFetchInProgress" + class="labels-fetch-loading gl-align-items-center gl-w-full gl-h-full" + size="md" + /> + <ul v-else class="list-unstyled gl-mb-0 gl-word-break-word" data-testid="labels-list"> + <label-item + v-for="(label, index) in visibleLabels" + :key="label.id" + :label="label" + :is-label-set="isLabelSelected(label)" + :highlight="index === currentHighlightItem" + @clickLabel="handleLabelClick(label)" /> - <ul v-else class="list-unstyled gl-mb-0 gl-word-break-word"> - <label-item - v-for="(label, index) in visibleLabels" - :key="label.id" - :label="label" - :is-label-set="label.set" - :highlight="index === currentHighlightItem" - @clickLabel="handleLabelClick(label)" - /> - <li v-show="showNoMatchingResultsMessage" class="gl-p-3 gl-text-center"> - {{ __('No matching results') }} - </li> - </ul> - </div> - <div - v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded" - class="dropdown-footer" - data-testid="dropdown-footer" - > - <ul class="list-unstyled"> - <li v-if="allowLabelCreate"> - <gl-link - class="gl-display-flex w-100 flex-row text-break-word label-item" - @click="handleCreateLabelClick" - > - {{ footerCreateLabelTitle }} - </gl-link> - </li> - <li> - <gl-link - :href="labelsManagePath" - class="gl-display-flex flex-row text-break-word label-item" - > - {{ footerManageLabelTitle }} - </gl-link> - </li> - </ul> - </div> + <li + v-show="showNoMatchingResultsMessage" + class="gl-p-3 gl-text-center" + data-testid="no-results" + > + {{ __('No matching results') }} + </li> + </ul> + </div> + <div + v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded" + class="dropdown-footer" + data-testid="dropdown-footer" + > + <ul class="list-unstyled"> + <li v-if="allowLabelCreate"> + <gl-link + class="gl-display-flex gl-flex-direction-row gl-w-full gl-overflow-break-word label-item" + data-testid="create-label-button" + @click="$emit('toggleDropdownContentsCreateView')" + > + {{ footerCreateLabelTitle }} + </gl-link> + </li> + <li> + <gl-link + :href="labelsManagePath" + class="gl-display-flex gl-flex-direction-row gl-w-full gl-overflow-break-word label-item" + > + {{ footerManageLabelTitle }} + </gl-link> + </li> + </ul> </div> - </gl-intersection-observer> + </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql new file mode 100644 index 00000000000..dc39220487d --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql @@ -0,0 +1,12 @@ +query projectLabels($fullPath: ID!, $searchTerm: String) { + workspace: project(fullPath: $fullPath) { + labels(searchTerm: $searchTerm, includeAncestorGroups: true) { + nodes { + id + title + color + description + } + } + } +} diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue index 6cc14def14b..0499dfe468f 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue @@ -197,23 +197,6 @@ export default { methods: { ...mapActions(['setInitialState', 'toggleDropdownContents']), /** - * This method differentiates between - * dispatched actions and calls necessary method. - */ - handleVuexActionDispatch(action, state) { - if ( - action.type === 'toggleDropdownContents' && - !state.showDropdownButton && - !state.showDropdownContents - ) { - let filterFn = (label) => label.touched; - if (this.isDropdownVariantEmbedded) { - filterFn = (label) => label.set; - } - this.handleDropdownClose(state.labels.filter(filterFn)); - } - }, - /** * This method stores a mousedown event's target. * Required by the click listener because the click * event itself has no reference to this element. @@ -276,6 +259,9 @@ export default { handleDropdownClose(labels) { // Only emit label updates if there are any labels to update // on UI. + if (this.showDropdownContents) { + this.toggleDropdownContents(); + } if (labels.length) this.$emit('updateSelectedLabels', labels); this.$emit('onDropdownClose'); }, @@ -332,8 +318,14 @@ export default { <dropdown-contents v-if="dropdownButtonVisible && showDropdownContents" ref="dropdownContents" + :allow-multiselect="allowMultiselect" + :labels-list-title="labelsListTitle" + :footer-create-label-title="footerCreateLabelTitle" + :footer-manage-label-title="footerManageLabelTitle" :render-on-top="!contentIsOnViewport" :labels-create-title="labelsCreateTitle" + :selected-labels="selectedLabels" + @closeDropdown="handleDropdownClose" /> </template> <template v-if="isDropdownVariantStandalone || isDropdownVariantEmbedded"> @@ -341,7 +333,13 @@ export default { <dropdown-contents v-if="dropdownButtonVisible && showDropdownContents" ref="dropdownContents" + :allow-multiselect="allowMultiselect" + :labels-list-title="labelsListTitle" + :footer-create-label-title="footerCreateLabelTitle" + :footer-manage-label-title="footerManageLabelTitle" :render-on-top="!contentIsOnViewport" + :selected-labels="selectedLabels" + @closeDropdown="handleDropdownClose" /> </template> </div> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js index 935f020f559..b3d4a204a81 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js @@ -1,6 +1,3 @@ -import createFlash from '~/flash'; -import axios from '~/lib/utils/axios_utils'; -import { __ } from '~/locale'; import * as types from './mutation_types'; export const setInitialState = ({ commit }, props) => commit(types.SET_INITIAL_STATE, props); @@ -11,24 +8,5 @@ export const toggleDropdownContents = ({ commit }) => commit(types.TOGGLE_DROPDO export const toggleDropdownContentsCreateView = ({ commit }) => commit(types.TOGGLE_DROPDOWN_CONTENTS_CREATE_VIEW); -export const requestLabels = ({ commit }) => commit(types.REQUEST_LABELS); -export const receiveLabelsSuccess = ({ commit }, labels) => - commit(types.RECEIVE_SET_LABELS_SUCCESS, labels); -export const receiveLabelsFailure = ({ commit }) => { - commit(types.RECEIVE_SET_LABELS_FAILURE); - createFlash({ - message: __('Error fetching labels.'), - }); -}; -export const fetchLabels = ({ state, dispatch }) => { - dispatch('requestLabels'); - return axios - .get(state.labelsFetchPath) - .then(({ data }) => { - dispatch('receiveLabelsSuccess', data); - }) - .catch(() => dispatch('receiveLabelsFailure')); -}; - export const updateSelectedLabels = ({ commit }, labels) => commit(types.UPDATE_SELECTED_LABELS, { labels }); diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutation_types.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutation_types.js index b8da7a90b36..bd71c3b85f1 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutation_types.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutation_types.js @@ -1,13 +1,5 @@ export const SET_INITIAL_STATE = 'SET_INITIAL_STATE'; -export const REQUEST_LABELS = 'REQUEST_LABELS'; -export const RECEIVE_LABELS_SUCCESS = 'RECEIVE_LABELS_SUCCESS'; -export const RECEIVE_LABELS_FAILURE = 'RECEIVE_LABELS_FAILURE'; - -export const REQUEST_SET_LABELS = 'REQUEST_SET_LABELS'; -export const RECEIVE_SET_LABELS_SUCCESS = 'RECEIVE_SET_LABELS_SUCCESS'; -export const RECEIVE_SET_LABELS_FAILURE = 'RECEIVE_SET_LABELS_FAILURE'; - export const TOGGLE_DROPDOWN_BUTTON = 'TOGGLE_DROPDOWN_VISIBILITY'; export const TOGGLE_DROPDOWN_CONTENTS = 'TOGGLE_DROPDOWN_CONTENTS'; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js index 1c03d95f37b..45ec4d7ae04 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js @@ -26,27 +26,6 @@ export default { [types.TOGGLE_DROPDOWN_CONTENTS_CREATE_VIEW](state) { state.showDropdownContentsCreateView = !state.showDropdownContentsCreateView; }, - - [types.REQUEST_LABELS](state) { - state.labelsFetchInProgress = true; - }, - [types.RECEIVE_SET_LABELS_SUCCESS](state, labels) { - // Iterate over every label and add a `set` prop - // to determine whether it is already a part of - // selectedLabels array. - const selectedLabelIds = state.selectedLabels.map((label) => label.id); - state.labelsFetchInProgress = false; - state.labels = labels.reduce((allLabels, label) => { - allLabels.push({ - ...label, - set: selectedLabelIds.includes(label.id), - }); - return allLabels; - }, []); - }, - [types.RECEIVE_SET_LABELS_FAILURE](state) { - state.labelsFetchInProgress = false; - }, [types.UPDATE_SELECTED_LABELS](state, { labels }) { // Find the label to update from all the labels // and change `set` prop value to represent their current state. diff --git a/app/helpers/analytics/navbar_helper.rb b/app/helpers/analytics/navbar_helper.rb deleted file mode 100644 index 091571ff15a..00000000000 --- a/app/helpers/analytics/navbar_helper.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -module Analytics - module NavbarHelper - class NavbarSubItem - attr_reader :title, :path, :link, :link_to_options - - def initialize(title:, path:, link:, link_to_options: {}) - @title = title - @path = path - @link = link - @link_to_options = link_to_options.merge(title: title) - end - end - - def group_analytics_navbar_links(group, current_user) - [] - end - - private - - def navbar_sub_item(args) - NavbarSubItem.new(**args) - end - end -end - -Analytics::NavbarHelper.prepend_mod_with('Analytics::NavbarHelper') diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb index 4937f2e862b..7f41f0907d5 100644 --- a/app/models/remote_mirror.rb +++ b/app/models/remote_mirror.rb @@ -22,8 +22,6 @@ class RemoteMirror < ApplicationRecord validates :url, presence: true, public_url: { schemes: %w(ssh git http https), allow_blank: true, enforce_user: true } - before_save :set_new_remote_name, if: :mirror_url_changed? - after_save :set_override_remote_mirror_available, unless: -> { Gitlab::CurrentSettings.current_application_settings.mirror_available } after_update :reset_fields, if: :saved_change_to_mirror_url? @@ -85,10 +83,6 @@ class RemoteMirror < ApplicationRecord end end - def remote_name - super || fallback_remote_name - end - def update_failed? update_status == 'failed' end @@ -100,7 +94,6 @@ class RemoteMirror < ApplicationRecord def update_repository Gitlab::Git::RemoteMirror.new( project.repository.raw, - remote_name, remote_url, **options_for_update ).update @@ -268,12 +261,6 @@ class RemoteMirror < ApplicationRecord super end - def fallback_remote_name - return unless id - - "remote_mirror_#{id}" - end - def recently_scheduled? return false unless self.last_update_started_at @@ -296,10 +283,6 @@ class RemoteMirror < ApplicationRecord project.update(remote_mirror_available_overridden: enabled) end - def set_new_remote_name - self.remote_name = "remote_mirror_#{SecureRandom.hex}" - end - def mirror_url_changed? url_changed? || attribute_changed?(:credentials) end diff --git a/app/services/git/branch_hooks_service.rb b/app/services/git/branch_hooks_service.rb index a49b981c680..9738615c804 100644 --- a/app/services/git/branch_hooks_service.rb +++ b/app/services/git/branch_hooks_service.rb @@ -95,7 +95,7 @@ module Git end def track_ci_config_change_event - return unless Gitlab::CurrentSettings.usage_ping_enabled? + return unless ::ServicePing::ServicePingSettings.enabled? return unless default_branch? commits_changing_ci_config.each do |commit| diff --git a/app/services/service_ping/service_ping_settings.rb b/app/services/service_ping/service_ping_settings.rb index 2b6535d89d1..6964210b1db 100644 --- a/app/services/service_ping/service_ping_settings.rb +++ b/app/services/service_ping/service_ping_settings.rb @@ -5,12 +5,10 @@ module ServicePing extend self def product_intelligence_enabled? - pings_enabled? && !User.single_user&.requires_usage_stats_consent? + enabled? && !User.single_user&.requires_usage_stats_consent? end - private - - def pings_enabled? + def enabled? ::Gitlab::CurrentSettings.usage_ping_enabled? end end diff --git a/app/views/layouts/nav/sidebar/_analytics_links.html.haml b/app/views/layouts/nav/sidebar/_analytics_links.html.haml deleted file mode 100644 index 92a7b97203f..00000000000 --- a/app/views/layouts/nav/sidebar/_analytics_links.html.haml +++ /dev/null @@ -1,22 +0,0 @@ -- navbar_links = links.sort_by(&:title) -- all_paths = navbar_links.map(&:path) -- analytics_link = navbar_links.find { |link| link.title == _('Value stream') } || navbar_links.first - -- if navbar_links.any? - = nav_link(path: all_paths) do - = link_to analytics_link.link, {class: 'shortcuts-analytics has-sub-items', data: { qa_selector: 'analytics_anchor' } } do - .nav-icon-container - = sprite_icon('chart') - %span.nav-item-name{ data: { qa_selector: 'analytics_link' } } - = _('Analytics') - - %ul.sidebar-sub-level-items{ data: { qa_selector: 'analytics_sidebar_submenu' } } - = nav_link(path: analytics_link.path, html_options: { class: "fly-out-top-item" } ) do - = link_to analytics_link.link do - %strong.fly-out-top-item-name - = _('Analytics') - %li.divider.fly-out-top-item - - navbar_links.each do |menu_item| - = nav_link(path: menu_item.path) do - = link_to(menu_item.link, menu_item.link_to_options) do - %span= menu_item.title diff --git a/app/views/layouts/nav/sidebar/_group_menus.html.haml b/app/views/layouts/nav/sidebar/_group_menus.html.haml index 2eea076f8db..8a6183f8efe 100644 --- a/app/views/layouts/nav/sidebar/_group_menus.html.haml +++ b/app/views/layouts/nav/sidebar/_group_menus.html.haml @@ -1,5 +1,3 @@ -= render 'layouts/nav/sidebar/analytics_links', links: group_analytics_navbar_links(@group, current_user) - - if group_sidebar_link?(:wiki) = render 'layouts/nav/sidebar/wiki_link', wiki_url: @group.wiki.web_url diff --git a/config/feature_flags/development/agent_kubeconfig_ci_variable.yml b/config/feature_flags/development/agent_kubeconfig_ci_variable.yml deleted file mode 100644 index 78a10a094c6..00000000000 --- a/config/feature_flags/development/agent_kubeconfig_ci_variable.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: agent_kubeconfig_ci_variable -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67089 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337164 -milestone: '14.2' -type: development -group: group::configure -default_enabled: false diff --git a/config/metrics/license/20210201124932_recorded_at.yml b/config/metrics/license/20210201124932_recorded_at.yml index 7a40498abea..d9fabfaad19 100644 --- a/config/metrics/license/20210201124932_recorded_at.yml +++ b/config/metrics/license/20210201124932_recorded_at.yml @@ -19,3 +19,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/license/20210201124933_uuid.yml b/config/metrics/license/20210201124933_uuid.yml index aab2b3a641c..594fd1b6d7f 100644 --- a/config/metrics/license/20210201124933_uuid.yml +++ b/config/metrics/license/20210201124933_uuid.yml @@ -20,3 +20,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/license/20210204124827_hostname.yml b/config/metrics/license/20210204124827_hostname.yml index 27669c3f6c5..1bc9375b69b 100644 --- a/config/metrics/license/20210204124827_hostname.yml +++ b/config/metrics/license/20210204124827_hostname.yml @@ -18,3 +18,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/license/20210204124829_active_user_count.yml b/config/metrics/license/20210204124829_active_user_count.yml index 2fa311f119d..b585c027a9f 100644 --- a/config/metrics/license/20210204124829_active_user_count.yml +++ b/config/metrics/license/20210204124829_active_user_count.yml @@ -18,3 +18,4 @@ tier: - premium - ultimate introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/557 +performance_indicator_type: [] diff --git a/config/metrics/license/20210204124928_version.yml b/config/metrics/license/20210204124928_version.yml index 8323f5ea760..5233c0cabc6 100644 --- a/config/metrics/license/20210204124928_version.yml +++ b/config/metrics/license/20210204124928_version.yml @@ -15,3 +15,4 @@ distribution: tier: - free skip_validation: true +performance_indicator_type: [] diff --git a/config/metrics/license/20210204124936_pages_version.yml b/config/metrics/license/20210204124936_pages_version.yml index d589255399c..4f8e8439b6a 100644 --- a/config/metrics/license/20210204124936_pages_version.yml +++ b/config/metrics/license/20210204124936_pages_version.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/license/20210204124938_recording_ce_finished_at.yml b/config/metrics/license/20210204124938_recording_ce_finished_at.yml index 532a7861e46..318c9fae50e 100644 --- a/config/metrics/license/20210204124938_recording_ce_finished_at.yml +++ b/config/metrics/license/20210204124938_recording_ce_finished_at.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/license/20210216175601_version.yml b/config/metrics/license/20210216175601_version.yml index 3b26ac31a3f..ffcf8d97aa1 100644 --- a/config/metrics/license/20210216175601_version.yml +++ b/config/metrics/license/20210216175601_version.yml @@ -18,3 +18,4 @@ tier: - premium - ultimate skip_validation: true +performance_indicator_type: [] diff --git a/config/metrics/license/20210216175602_installation_type.yml b/config/metrics/license/20210216175602_installation_type.yml index 669f983a505..9767609ffcd 100644 --- a/config/metrics/license/20210216175602_installation_type.yml +++ b/config/metrics/license/20210216175602_installation_type.yml @@ -17,4 +17,4 @@ tier: - free - premium - ultimate - +performance_indicator_type: [] diff --git a/config/metrics/license/20210216181053_version.yml b/config/metrics/license/20210216181053_version.yml index a95dcd5c13f..b18d7360560 100644 --- a/config/metrics/license/20210216181053_version.yml +++ b/config/metrics/license/20210216181053_version.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/license/20210216183237_version.yml b/config/metrics/license/20210216183237_version.yml index 33dc3c38a9c..bd1b6158329 100644 --- a/config/metrics/license/20210216183237_version.yml +++ b/config/metrics/license/20210216183237_version.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210201124935_database_adapter.yml b/config/metrics/settings/20210201124935_database_adapter.yml index 0a26d0afb68..ca16bec6a08 100644 --- a/config/metrics/settings/20210201124935_database_adapter.yml +++ b/config/metrics/settings/20210201124935_database_adapter.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124856_instance_auto_devops_enabled.yml b/config/metrics/settings/20210204124856_instance_auto_devops_enabled.yml index f1426cd220d..45361c5b358 100644 --- a/config/metrics/settings/20210204124856_instance_auto_devops_enabled.yml +++ b/config/metrics/settings/20210204124856_instance_auto_devops_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124858_container_registry_enabled.yml b/config/metrics/settings/20210204124858_container_registry_enabled.yml index 4f5d2fde9e7..4c640a1de2b 100644 --- a/config/metrics/settings/20210204124858_container_registry_enabled.yml +++ b/config/metrics/settings/20210204124858_container_registry_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124900_dependency_proxy_enabled.yml b/config/metrics/settings/20210204124900_dependency_proxy_enabled.yml index a567dd3bf51..911b9732a56 100644 --- a/config/metrics/settings/20210204124900_dependency_proxy_enabled.yml +++ b/config/metrics/settings/20210204124900_dependency_proxy_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124902_gitlab_shared_runners_enabled.yml b/config/metrics/settings/20210204124902_gitlab_shared_runners_enabled.yml index d1737989b80..ae75667134d 100644 --- a/config/metrics/settings/20210204124902_gitlab_shared_runners_enabled.yml +++ b/config/metrics/settings/20210204124902_gitlab_shared_runners_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124904_gravatar_enabled.yml b/config/metrics/settings/20210204124904_gravatar_enabled.yml index aa7d697acfd..dfbf70bad87 100644 --- a/config/metrics/settings/20210204124904_gravatar_enabled.yml +++ b/config/metrics/settings/20210204124904_gravatar_enabled.yml @@ -17,4 +17,4 @@ tier: - free - premium - ultimate - +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124906_ldap_enabled.yml b/config/metrics/settings/20210204124906_ldap_enabled.yml index d1dcdaec106..a6e4a4fb80a 100644 --- a/config/metrics/settings/20210204124906_ldap_enabled.yml +++ b/config/metrics/settings/20210204124906_ldap_enabled.yml @@ -15,3 +15,4 @@ distribution: tier: - free skip_validation: true +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124908_mattermost_enabled.yml b/config/metrics/settings/20210204124908_mattermost_enabled.yml index ed72e557ad7..9da0e2c604f 100644 --- a/config/metrics/settings/20210204124908_mattermost_enabled.yml +++ b/config/metrics/settings/20210204124908_mattermost_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124910_omniauth_enabled.yml b/config/metrics/settings/20210204124910_omniauth_enabled.yml index 2943913bcbb..a9a6bdc0bda 100644 --- a/config/metrics/settings/20210204124910_omniauth_enabled.yml +++ b/config/metrics/settings/20210204124910_omniauth_enabled.yml @@ -15,3 +15,4 @@ distribution: tier: - free skip_validation: true +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124912_prometheus_enabled.yml b/config/metrics/settings/20210204124912_prometheus_enabled.yml index bf33f8e4454..8a8a44056ce 100644 --- a/config/metrics/settings/20210204124912_prometheus_enabled.yml +++ b/config/metrics/settings/20210204124912_prometheus_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124914_prometheus_metrics_enabled.yml b/config/metrics/settings/20210204124914_prometheus_metrics_enabled.yml index df4a4f1edaa..0aff4171cfc 100644 --- a/config/metrics/settings/20210204124914_prometheus_metrics_enabled.yml +++ b/config/metrics/settings/20210204124914_prometheus_metrics_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124916_reply_by_email_enabled.yml b/config/metrics/settings/20210204124916_reply_by_email_enabled.yml index 35f834511b2..9edc06cdd18 100644 --- a/config/metrics/settings/20210204124916_reply_by_email_enabled.yml +++ b/config/metrics/settings/20210204124916_reply_by_email_enabled.yml @@ -11,9 +11,10 @@ status: data_available time_frame: none data_source: system distribution: - - ce - - ee +- ce +- ee tier: - - free - - premium - - ultimate +- free +- premium +- ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124918_signup_enabled.yml b/config/metrics/settings/20210204124918_signup_enabled.yml index 40b16f33b0b..bc2f2097d41 100644 --- a/config/metrics/settings/20210204124918_signup_enabled.yml +++ b/config/metrics/settings/20210204124918_signup_enabled.yml @@ -18,3 +18,4 @@ tier: - premium - ultimate skip_validation: true +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124920_web_ide_clientside_preview_enabled.yml b/config/metrics/settings/20210204124920_web_ide_clientside_preview_enabled.yml index 869f7c89483..758c7ee62bf 100644 --- a/config/metrics/settings/20210204124920_web_ide_clientside_preview_enabled.yml +++ b/config/metrics/settings/20210204124920_web_ide_clientside_preview_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124922_grafana_link_enabled.yml b/config/metrics/settings/20210204124922_grafana_link_enabled.yml index 9660614bd46..4680f4b152c 100644 --- a/config/metrics/settings/20210204124922_grafana_link_enabled.yml +++ b/config/metrics/settings/20210204124922_grafana_link_enabled.yml @@ -14,3 +14,4 @@ distribution: - ce tier: - free +performance_indicator_type: [] diff --git a/config/metrics/settings/20210204124934_pages_enabled.yml b/config/metrics/settings/20210204124934_pages_enabled.yml index 14a2ea4bc76..907dd4b454c 100644 --- a/config/metrics/settings/20210204124934_pages_enabled.yml +++ b/config/metrics/settings/20210204124934_pages_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216174829_smtp_server.yml b/config/metrics/settings/20210216174829_smtp_server.yml index 1b264834821..46e3396eef6 100644 --- a/config/metrics/settings/20210216174829_smtp_server.yml +++ b/config/metrics/settings/20210216174829_smtp_server.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216175459_ingress_modsecurity_enabled.yml b/config/metrics/settings/20210216175459_ingress_modsecurity_enabled.yml index 603064a301d..fa6ccaf67b2 100644 --- a/config/metrics/settings/20210216175459_ingress_modsecurity_enabled.yml +++ b/config/metrics/settings/20210216175459_ingress_modsecurity_enabled.yml @@ -18,3 +18,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216175604_edition.yml b/config/metrics/settings/20210216175604_edition.yml index 0571097c5be..03f013e9c98 100644 --- a/config/metrics/settings/20210216175604_edition.yml +++ b/config/metrics/settings/20210216175604_edition.yml @@ -17,4 +17,4 @@ tier: - free - premium - ultimate - +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216175606_ldap_encrypted_secrets_enabled.yml b/config/metrics/settings/20210216175606_ldap_encrypted_secrets_enabled.yml index 2cd0949c9f5..9d156e327ea 100644 --- a/config/metrics/settings/20210216175606_ldap_encrypted_secrets_enabled.yml +++ b/config/metrics/settings/20210216175606_ldap_encrypted_secrets_enabled.yml @@ -17,4 +17,4 @@ tier: - free - premium - ultimate - +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216175609_version.yml b/config/metrics/settings/20210216175609_version.yml index 468a8998637..31f6dcece3d 100644 --- a/config/metrics/settings/20210216175609_version.yml +++ b/config/metrics/settings/20210216175609_version.yml @@ -17,4 +17,4 @@ tier: - free - premium - ultimate - +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180314_gitpod_enabled.yml b/config/metrics/settings/20210216180314_gitpod_enabled.yml index 71ceb75ab84..a42042a8200 100644 --- a/config/metrics/settings/20210216180314_gitpod_enabled.yml +++ b/config/metrics/settings/20210216180314_gitpod_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180836_enabled.yml b/config/metrics/settings/20210216180836_enabled.yml index 9a52788d2e3..01a7731d6f9 100644 --- a/config/metrics/settings/20210216180836_enabled.yml +++ b/config/metrics/settings/20210216180836_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180838_enabled.yml b/config/metrics/settings/20210216180838_enabled.yml index 1e8c0bf8d30..7f62d345288 100644 --- a/config/metrics/settings/20210216180838_enabled.yml +++ b/config/metrics/settings/20210216180838_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180840_direct_upload.yml b/config/metrics/settings/20210216180840_direct_upload.yml index a0859d7d5a6..9932494a421 100644 --- a/config/metrics/settings/20210216180840_direct_upload.yml +++ b/config/metrics/settings/20210216180840_direct_upload.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180841_background_upload.yml b/config/metrics/settings/20210216180841_background_upload.yml index 9386a1a8301..fb0e02b108a 100644 --- a/config/metrics/settings/20210216180841_background_upload.yml +++ b/config/metrics/settings/20210216180841_background_upload.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180843_provider.yml b/config/metrics/settings/20210216180843_provider.yml index 2c9e074f9ef..702f5eab679 100644 --- a/config/metrics/settings/20210216180843_provider.yml +++ b/config/metrics/settings/20210216180843_provider.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180845_enabled.yml b/config/metrics/settings/20210216180845_enabled.yml index a9c33827861..ab92282eafd 100644 --- a/config/metrics/settings/20210216180845_enabled.yml +++ b/config/metrics/settings/20210216180845_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180847_enabled.yml b/config/metrics/settings/20210216180847_enabled.yml index ca404dc8b66..c59efae2129 100644 --- a/config/metrics/settings/20210216180847_enabled.yml +++ b/config/metrics/settings/20210216180847_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180849_direct_upload.yml b/config/metrics/settings/20210216180849_direct_upload.yml index 37e77067336..17eb30c1441 100644 --- a/config/metrics/settings/20210216180849_direct_upload.yml +++ b/config/metrics/settings/20210216180849_direct_upload.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180851_background_upload.yml b/config/metrics/settings/20210216180851_background_upload.yml index 5c231d544bf..dc51d8ae733 100644 --- a/config/metrics/settings/20210216180851_background_upload.yml +++ b/config/metrics/settings/20210216180851_background_upload.yml @@ -18,3 +18,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180852_provider.yml b/config/metrics/settings/20210216180852_provider.yml index 8675e023caf..44bc38ea5fd 100644 --- a/config/metrics/settings/20210216180852_provider.yml +++ b/config/metrics/settings/20210216180852_provider.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180854_enabled.yml b/config/metrics/settings/20210216180854_enabled.yml index 51aebd0f029..275c26a1478 100644 --- a/config/metrics/settings/20210216180854_enabled.yml +++ b/config/metrics/settings/20210216180854_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180856_enabled.yml b/config/metrics/settings/20210216180856_enabled.yml index 842a6042ae6..da972595ff9 100644 --- a/config/metrics/settings/20210216180856_enabled.yml +++ b/config/metrics/settings/20210216180856_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180858_direct_upload.yml b/config/metrics/settings/20210216180858_direct_upload.yml index c2d680102e6..120537b8af0 100644 --- a/config/metrics/settings/20210216180858_direct_upload.yml +++ b/config/metrics/settings/20210216180858_direct_upload.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180900_background_upload.yml b/config/metrics/settings/20210216180900_background_upload.yml index 9d4b16ba441..5c885d29061 100644 --- a/config/metrics/settings/20210216180900_background_upload.yml +++ b/config/metrics/settings/20210216180900_background_upload.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180902_provider.yml b/config/metrics/settings/20210216180902_provider.yml index 1ec1949ef11..97dd62b8eca 100644 --- a/config/metrics/settings/20210216180902_provider.yml +++ b/config/metrics/settings/20210216180902_provider.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180903_enabled.yml b/config/metrics/settings/20210216180903_enabled.yml index 7ebcbd04837..55cd0967931 100644 --- a/config/metrics/settings/20210216180903_enabled.yml +++ b/config/metrics/settings/20210216180903_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180905_enabled.yml b/config/metrics/settings/20210216180905_enabled.yml index f967861ae0b..438a4ea5ff9 100644 --- a/config/metrics/settings/20210216180905_enabled.yml +++ b/config/metrics/settings/20210216180905_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180907_direct_upload.yml b/config/metrics/settings/20210216180907_direct_upload.yml index ca8cfa003c3..14d6ff17106 100644 --- a/config/metrics/settings/20210216180907_direct_upload.yml +++ b/config/metrics/settings/20210216180907_direct_upload.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180909_background_upload.yml b/config/metrics/settings/20210216180909_background_upload.yml index 783baa1f30d..738148fd0eb 100644 --- a/config/metrics/settings/20210216180909_background_upload.yml +++ b/config/metrics/settings/20210216180909_background_upload.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180911_provider.yml b/config/metrics/settings/20210216180911_provider.yml index 4066f83afdb..60cc2a56573 100644 --- a/config/metrics/settings/20210216180911_provider.yml +++ b/config/metrics/settings/20210216180911_provider.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180913_enabled.yml b/config/metrics/settings/20210216180913_enabled.yml index e19001746d8..38a5a3d61ca 100644 --- a/config/metrics/settings/20210216180913_enabled.yml +++ b/config/metrics/settings/20210216180913_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180915_enabled.yml b/config/metrics/settings/20210216180915_enabled.yml index 0cb84815e26..6aef961d6db 100644 --- a/config/metrics/settings/20210216180915_enabled.yml +++ b/config/metrics/settings/20210216180915_enabled.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180916_direct_upload.yml b/config/metrics/settings/20210216180916_direct_upload.yml index 41adb02d6c6..29a8c3559f4 100644 --- a/config/metrics/settings/20210216180916_direct_upload.yml +++ b/config/metrics/settings/20210216180916_direct_upload.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180918_background_upload.yml b/config/metrics/settings/20210216180918_background_upload.yml index 9c5ded4f956..b9820da5e42 100644 --- a/config/metrics/settings/20210216180918_background_upload.yml +++ b/config/metrics/settings/20210216180918_background_upload.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216180920_provider.yml b/config/metrics/settings/20210216180920_provider.yml index da5d1dc0c1b..bc24892c115 100644 --- a/config/metrics/settings/20210216180920_provider.yml +++ b/config/metrics/settings/20210216180920_provider.yml @@ -17,3 +17,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216183241_filesystems.yml b/config/metrics/settings/20210216183241_filesystems.yml index 242f2818ac6..0f00292b2cc 100644 --- a/config/metrics/settings/20210216183241_filesystems.yml +++ b/config/metrics/settings/20210216183241_filesystems.yml @@ -15,3 +15,4 @@ distribution: tier: - free skip_validation: true +performance_indicator_type: [] diff --git a/config/metrics/settings/20210216183248_pg_system_id.yml b/config/metrics/settings/20210216183248_pg_system_id.yml index 19b920ea7e3..e605a2df4e7 100644 --- a/config/metrics/settings/20210216183248_pg_system_id.yml +++ b/config/metrics/settings/20210216183248_pg_system_id.yml @@ -5,7 +5,7 @@ description: TBD product_section: enablement product_stage: enablement product_group: group::distribution -product_category: +product_category: value_type: number status: data_available time_frame: all @@ -15,3 +15,4 @@ distribution: tier: - free skip_validation: true +performance_indicator_type: [] diff --git a/config/metrics/settings/20210225045628_operating_system.yml b/config/metrics/settings/20210225045628_operating_system.yml index 6f1cd9be724..cc75a6dff68 100644 --- a/config/metrics/settings/20210225045628_operating_system.yml +++ b/config/metrics/settings/20210225045628_operating_system.yml @@ -19,3 +19,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210321224827_gitaly_apdex.yml b/config/metrics/settings/20210321224827_gitaly_apdex.yml index f760abae6dc..509371b6a24 100644 --- a/config/metrics/settings/20210321224827_gitaly_apdex.yml +++ b/config/metrics/settings/20210321224827_gitaly_apdex.yml @@ -19,3 +19,4 @@ tier: - free - premium - ultimate +performance_indicator_type: [] diff --git a/config/metrics/settings/20210323120839_topology.yml b/config/metrics/settings/20210323120839_topology.yml index c998b37d039..be761c7f555 100644 --- a/config/metrics/settings/20210323120839_topology.yml +++ b/config/metrics/settings/20210323120839_topology.yml @@ -5,7 +5,7 @@ description: Topology data product_section: enablement product_stage: enablement product_group: group::memory -product_category: +product_category: value_type: object status: data_available milestone: "13.11" @@ -20,3 +20,4 @@ tier: - premium - ultimate value_json_schema: 'config/metrics/objects_schemas/topology_schema.json' +performance_indicator_type: [] diff --git a/config/metrics/settings/20210702140138_collected_data_categories.yml b/config/metrics/settings/20210702140138_collected_data_categories.yml index 697be4e8dd1..74697132091 100644 --- a/config/metrics/settings/20210702140138_collected_data_categories.yml +++ b/config/metrics/settings/20210702140138_collected_data_categories.yml @@ -22,3 +22,4 @@ tier: - premium - ultimate value_json_schema: 'config/metrics/objects_schemas/collected_data_categories_schema.json' +performance_indicator_type: [] diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 90c059e646d..f145e91dae7 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -426,6 +426,8 @@ In `include` sections in your `.gitlab-ci.yml` file, you can use: - Project [predefined variables](../variables/predefined_variables.md). - [Custom instance, group, and project variables](../variables/index.md#custom-cicd-variables) in GitLab 14.2 and later. +- `$CI_COMMIT_REF_NAME` [predefined variable](../variables/predefined_variables.md) in GitLab 14.2 + and later. ```yaml include: diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 4d2007f548f..f542b11e864 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -578,11 +578,12 @@ context is fresh in memory, and improves contributors' experience significantly. To ensure swift feedback to ready-to-review code, we maintain a `Review-response` Service-level Objective (SLO). The SLO is defined as: -> - review-response SLO = (time when first review response is provided) - (time MR is assigned to reviewer) < 2 business days +> Review-response SLO = (time when first review is provided) - (time MR is assigned to reviewer) < 2 business days If you don't think you can review a merge request in the `Review-response` SLO -time frame, let the author know as soon as possible and try to help them find -another reviewer or maintainer who is able to, so that they can be unblocked +time frame, let the author know as soon as possible in the comments +(no later than 36 hours after first receiving the review request) +and try to help them find another reviewer or maintainer who is able to, so that they can be unblocked and get on with their work quickly. Remove yourself as a reviewer. If you think you are at capacity and are unable to accept any more reviews until diff --git a/doc/development/service_ping/index.md b/doc/development/service_ping/index.md index bb02dcedda8..8ae023f5255 100644 --- a/doc/development/service_ping/index.md +++ b/doc/development/service_ping/index.md @@ -220,178 +220,6 @@ We also collect metrics specific to [Geo](../../administration/geo/index.md) sec ] ``` -## Service Ping Metric Life cycle - -### 1. New metrics addition - -Please follow the [Implementing Service Ping](#implementing-service-ping) guide. - -### 2. Existing metric change - -Because we do not control when customers update their self-managed instances of GitLab, -we **STRONGLY DISCOURAGE** changes to the logic used to calculate any metric. -Any such changes lead to inconsistent reports from multiple GitLab instances. -If there is a problem with an existing metric, it's best to deprecate the existing metric, -and use it, side by side, with the desired new metric. - -Example: -Consider following change. Before GitLab 12.6, the `example_metric` was implemented as: - -```ruby -{ - ... - example_metric: distinct_count(Project, :creator_id) -} -``` - -For GitLab 12.6, the metric was changed to filter out archived projects: - -```ruby -{ - ... - example_metric: distinct_count(Project.non_archived, :creator_id) -} -``` - -In this scenario all instances running up to GitLab 12.5 continue to report `example_metric`, -including all archived projects, while all instances running GitLab 12.6 and higher filters -out such projects. As Service Ping data is collected from all reporting instances, the -resulting dataset includes mixed data, which distorts any following business analysis. - -The correct approach is to add a new metric for GitLab 12.6 release with updated logic: - -```ruby -{ - ... - example_metric_without_archived: distinct_count(Project.non_archived, :creator_id) -} -``` - -and update existing business analysis artefacts to use `example_metric_without_archived` instead of `example_metric` - -### 3. Deprecate a metric - -If a metric is obsolete and you no longer use it, you can mark it as deprecated. - -For an example of the metric deprecation process take a look at this [example merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59899) - -To deprecate a metric: - -1. Check the following YAML files and verify the metric is not used in an aggregate: - - [`config/metrics/aggregates/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/aggregates/) - - [`ee/config/metrics/aggregates/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/aggregates/) - -1. Create an issue in the [GitLab Data Team - project](https://gitlab.com/gitlab-data/analytics/-/issues). Ask for - confirmation that the metric is not used by other teams, or in any of the SiSense - dashboards. - -1. Verify the metric is not used to calculate the conversational index. The - conversational index is a measure that reports back to self-managed instances - to inform administrators of the progress of DevOps adoption for the instance. - - You can check - [`CalculateConvIndexService`](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/app/services/calculate_conv_index_service.rb) - to view the metrics that are used. The metrics are represented - as the keys that are passed as a field argument into the `get_value` method. - -1. Document the deprecation in the metric's YAML definition. Set - the `status:` attribute to `deprecated`, for example: - - ```yaml - --- - key_path: analytics_unique_visits.analytics_unique_visits_for_any_target_monthly - description: Visits to any of the pages listed above per month - product_section: dev - product_stage: manage - product_group: group::analytics - product_category: - value_type: number - status: deprecated - time_frame: 28d - data_source: - distribution: - - ce - tier: - - free - ``` - -1. Replace the metric's instrumentation with a fixed value. This avoids wasting - resources to calculate the deprecated metric. In - [`lib/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data.rb) - or - [`ee/lib/ee/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/ee/gitlab/usage_data.rb), - replace the code that calculates the metric's value with a fixed value that - indicates it's deprecated: - - ```ruby - module Gitlab - class UsageData - DEPRECATED_VALUE = -1000 - - def analytics_unique_visits_data - results['analytics_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics) } - results['analytics_unique_visits_for_any_target_monthly'] = DEPRECATED_VALUE - - { analytics_unique_visits: results } - end - # ... - end - end - ``` - -### 4. Remove a metric - -Only deprecated metrics can be removed from Service Ping. - -For an example of the metric removal process take a look at this [example issue](https://gitlab.com/gitlab-org/gitlab/-/issues/297029) - -To remove a deprecated metric: - -1. Verify that removing the metric from the Service Ping payload does not cause - errors in [Version App](https://gitlab.com/gitlab-services/version-gitlab-com) - when the updated payload is collected and processed. Version App collects - and persists all Service Ping reports. To do that you can modify - [fixtures](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/spec/support/usage_data_helpers.rb#L540) - used to test - [`UsageDataController#create`](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/3760ef28/spec/controllers/usage_data_controller_spec.rb#L75) - endpoint, and assure that test suite does not fail when metric that you wish to remove is not included into test payload. - -1. Create an issue in the - [GitLab Data Team project](https://gitlab.com/gitlab-data/analytics/-/issues). - Ask for confirmation that the metric is not referred to in any SiSense dashboards and - can be safely removed from Service Ping. Use this - [example issue](https://gitlab.com/gitlab-data/analytics/-/issues/7539) for guidance. - This step can be skipped if verification done during [deprecation process](#3-deprecate-a-metric) - reported that metric is not required by any data transformation in Snowflake data warehouse nor it is - used by any of SiSense dashboards. - -1. After you verify the metric can be safely removed, - update the attributes of the metric's YAML definition: - - - Set the `status:` to `removed`. - - Set `milestone_removed:` to the number of the - milestone in which the metric was removed. - - Do not remove the metric's YAML definition altogether. Some self-managed - instances might not immediately update to the latest version of GitLab, and - therefore continue to report the removed metric. The Product Intelligence team - requires a record of all removed metrics in order to identify and filter them. - - For example please take a look at this [merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60149/diffs#b01f429a54843feb22265100c0e4fec1b7da1240_10_10). - -1. After you verify the metric can be safely removed, - remove the metric's instrumentation from - [`lib/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data.rb) - or - [`ee/lib/ee/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/ee/gitlab/usage_data.rb). - - For example please take a look at this [merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60149/diffs#6335dc533bd21df26db9de90a02dd66278c2390d_167_167). - -1. Remove any other records related to the metric: - - The feature flag YAML file at [`config/feature_flags/*/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/config/feature_flags). - - The entry in the known events YAML file at [`lib/gitlab/usage_data_counters/known_events/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/usage_data_counters/known_events). - ## Implementing Service Ping Service Ping consists of two kinds of data, counters and observations. Counters track how often a certain event diff --git a/doc/development/service_ping/metrics_dictionary.md b/doc/development/service_ping/metrics_dictionary.md index b969286e053..d3eeaedd91d 100644 --- a/doc/development/service_ping/metrics_dictionary.md +++ b/doc/development/service_ping/metrics_dictionary.md @@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Metrics Dictionary Guide -This guide describes Metrics Dictionary and how it's implemented +This guide describes the [Metrics Dictionary](https://gitlab-org.gitlab.io/growth/product-intelligence/metric-dictionary) and how it's implemented. ## Metrics Definition and validation diff --git a/doc/development/service_ping/metrics_lifecycle.md b/doc/development/service_ping/metrics_lifecycle.md new file mode 100644 index 00000000000..87e5c796c97 --- /dev/null +++ b/doc/development/service_ping/metrics_lifecycle.md @@ -0,0 +1,179 @@ +--- +stage: Growth +group: Product Intelligence +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 +--- + +# Service Ping metric lifecycle + +The following guidelines explain the steps to follow at each stage of a metric's lifecycle. + +## Add a new metric + +Please follow the [Implementing Service Ping](index.md#implementing-service-ping) guide. + +## Change an existing metric + +Because we do not control when customers update their self-managed instances of GitLab, +we **STRONGLY DISCOURAGE** changes to the logic used to calculate any metric. +Any such changes lead to inconsistent reports from multiple GitLab instances. +If there is a problem with an existing metric, it's best to deprecate the existing metric, +and use it, side by side, with the desired new metric. + +Example: +Consider following change. Before GitLab 12.6, the `example_metric` was implemented as: + +```ruby +{ + ... + example_metric: distinct_count(Project, :creator_id) +} +``` + +For GitLab 12.6, the metric was changed to filter out archived projects: + +```ruby +{ + ... + example_metric: distinct_count(Project.non_archived, :creator_id) +} +``` + +In this scenario all instances running up to GitLab 12.5 continue to report `example_metric`, +including all archived projects, while all instances running GitLab 12.6 and higher filters +out such projects. As Service Ping data is collected from all reporting instances, the +resulting dataset includes mixed data, which distorts any following business analysis. + +The correct approach is to add a new metric for GitLab 12.6 release with updated logic: + +```ruby +{ + ... + example_metric_without_archived: distinct_count(Project.non_archived, :creator_id) +} +``` + +and update existing business analysis artefacts to use `example_metric_without_archived` instead of `example_metric` + +## Deprecate a metric + +If a metric is obsolete and you no longer use it, you can mark it as deprecated. + +For an example of the metric deprecation process take a look at this [example merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59899) + +To deprecate a metric: + +1. Check the following YAML files and verify the metric is not used in an aggregate: + - [`config/metrics/aggregates/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/aggregates/) + - [`ee/config/metrics/aggregates/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/aggregates/) + +1. Create an issue in the [GitLab Data Team + project](https://gitlab.com/gitlab-data/analytics/-/issues). Ask for + confirmation that the metric is not used by other teams, or in any of the SiSense + dashboards. + +1. Verify the metric is not used to calculate the conversational index. The + conversational index is a measure that reports back to self-managed instances + to inform administrators of the progress of DevOps adoption for the instance. + + You can check + [`CalculateConvIndexService`](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/app/services/calculate_conv_index_service.rb) + to view the metrics that are used. The metrics are represented + as the keys that are passed as a field argument into the `get_value` method. + +1. Document the deprecation in the metric's YAML definition. Set + the `status:` attribute to `deprecated`, for example: + + ```yaml + --- + key_path: analytics_unique_visits.analytics_unique_visits_for_any_target_monthly + description: Visits to any of the pages listed above per month + product_section: dev + product_stage: manage + product_group: group::analytics + product_category: + value_type: number + status: deprecated + time_frame: 28d + data_source: + distribution: + - ce + tier: + - free + ``` + +1. Replace the metric's instrumentation with a fixed value. This avoids wasting + resources to calculate the deprecated metric. In + [`lib/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data.rb) + or + [`ee/lib/ee/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/ee/gitlab/usage_data.rb), + replace the code that calculates the metric's value with a fixed value that + indicates it's deprecated: + + ```ruby + module Gitlab + class UsageData + DEPRECATED_VALUE = -1000 + + def analytics_unique_visits_data + results['analytics_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics) } + results['analytics_unique_visits_for_any_target_monthly'] = DEPRECATED_VALUE + + { analytics_unique_visits: results } + end + # ... + end + end + ``` + +## Remove a metric + +Only deprecated metrics can be removed from Service Ping. + +For an example of the metric removal process take a look at this [example issue](https://gitlab.com/gitlab-org/gitlab/-/issues/297029) + +To remove a deprecated metric: + +1. Verify that removing the metric from the Service Ping payload does not cause + errors in [Version App](https://gitlab.com/gitlab-services/version-gitlab-com) + when the updated payload is collected and processed. Version App collects + and persists all Service Ping reports. To do that you can modify + [fixtures](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/spec/support/usage_data_helpers.rb#L540) + used to test + [`UsageDataController#create`](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/3760ef28/spec/controllers/usage_data_controller_spec.rb#L75) + endpoint, and assure that test suite does not fail when metric that you wish to remove is not included into test payload. + +1. Create an issue in the + [GitLab Data Team project](https://gitlab.com/gitlab-data/analytics/-/issues). + Ask for confirmation that the metric is not referred to in any SiSense dashboards and + can be safely removed from Service Ping. Use this + [example issue](https://gitlab.com/gitlab-data/analytics/-/issues/7539) for guidance. + This step can be skipped if verification done during [deprecation process](#deprecate-a-metric) + reported that metric is not required by any data transformation in Snowflake data warehouse nor it is + used by any of SiSense dashboards. + +1. After you verify the metric can be safely removed, + update the attributes of the metric's YAML definition: + + - Set the `status:` to `removed`. + - Set `milestone_removed:` to the number of the + milestone in which the metric was removed. + + Do not remove the metric's YAML definition altogether. Some self-managed + instances might not immediately update to the latest version of GitLab, and + therefore continue to report the removed metric. The Product Intelligence team + requires a record of all removed metrics in order to identify and filter them. + + For example please take a look at this [merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60149/diffs#b01f429a54843feb22265100c0e4fec1b7da1240_10_10). + +1. After you verify the metric can be safely removed, + remove the metric's instrumentation from + [`lib/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data.rb) + or + [`ee/lib/ee/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/ee/gitlab/usage_data.rb). + + For example please take a look at this [merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60149/diffs#6335dc533bd21df26db9de90a02dd66278c2390d_167_167). + +1. Remove any other records related to the metric: + - The feature flag YAML file at [`config/feature_flags/*/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/config/feature_flags). + - The entry in the known events YAML file at [`lib/gitlab/usage_data_counters/known_events/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/usage_data_counters/known_events). diff --git a/doc/user/clusters/agent/ci_cd_tunnel.md b/doc/user/clusters/agent/ci_cd_tunnel.md index 1f794bac37f..09123fdd472 100644 --- a/doc/user/clusters/agent/ci_cd_tunnel.md +++ b/doc/user/clusters/agent/ci_cd_tunnel.md @@ -6,7 +6,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w # CI/CD Tunnel -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327409) in GitLab 14.1. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327409) in GitLab 14.1. +> - Pre-configured `KUBECONFIG` [added](https://gitlab.com/gitlab-org/gitlab/-/issues/324275) in GitLab 14.2. The CI/CD Tunnel enables users to access Kubernetes clusters from GitLab CI/CD jobs even if there is no network connectivity between GitLab Runner and a cluster. GitLab Runner does not have to be running in the same cluster. @@ -21,47 +22,22 @@ Prerequisites: - An [Agent record](index.md#create-an-agent-record-in-gitlab). - The agent is [installed in the cluster](index.md#install-the-agent-into-the-cluster). -To access your cluster from a CI/CD job through the tunnel: +If your project has one or more Agent records, a `KUBECONFIG` variable that is compatible with `kubectl` is provided to your CI/CD jobs. A separate context (`kubecontext`) is available for each configured Agent. By default, no context is selected. -1. In your `.gitlab-ci.yml` add a section that creates a `kubectl` compatible configuration file (`kubecontext`) and use it in one - or more jobs: +Contexts are named in the following format: `<agent-configuration-project-path>:<agent-name>`. - ```yaml - variables: - AGENT_ID: 4 # agent id that you got when you created the agent record - KUBE_CFG_FILE: "$CI_PROJECT_DIR/.kubeconfig.agent.yaml" +To access your cluster from a CI/CD job through the tunnel: - .kubectl_config: &kubectl_config - - | - cat << EOF > "$KUBE_CFG_FILE" - apiVersion: v1 - kind: Config - clusters: - - name: agent - cluster: - server: https://kas.gitlab.com/k8s-proxy/ - users: - - name: agent - user: - token: "ci:$AGENT_ID:$CI_JOB_TOKEN" - contexts: - - name: agent - context: - cluster: agent - user: agent - current-context: agent - EOF +1. In your `.gitlab-ci.yml` select the context for the agent you wish to use: + ```yaml deploy: image: name: bitnami/kubectl:latest entrypoint: [""] script: - - *kubectl_config - - kubectl --kubeconfig="$KUBE_CFG_FILE" get pods + - kubectl config use-context path/to/agent-configuration-project:your-agent-name + - kubectl get pods ``` 1. Execute `kubectl` commands directly against your cluster with this CI/CD job you just created. - -We are working on [creating the configuration file automatically](https://gitlab.com/gitlab-org/gitlab/-/issues/324275) -to simplify the process. diff --git a/doc/user/project/merge_requests/status_checks.md b/doc/user/project/merge_requests/status_checks.md index 70e2d718406..1576b639a76 100644 --- a/doc/user/project/merge_requests/status_checks.md +++ b/doc/user/project/merge_requests/status_checks.md @@ -11,9 +11,6 @@ disqus_identifier: 'https://docs.gitlab.com/ee/user/project/merge_requests/statu > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 14.0, disabled behind the `:ff_external_status_checks` feature flag. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/320783) in GitLab 14.1. -WARNING: -This feature might not be available to you. Check the **version history** note above for details. - You can create a status check that sends merge request data to third-party tools. When users create, change, or close merge requests, GitLab sends a notification. The users or automated workflows can then update the status of merge requests from outside of GitLab. diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index 2a34a579dc5..e2c9d8d41ab 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -60,14 +60,9 @@ read-only view to discourage this behavior. #### Compliance pipeline configuration **(ULTIMATE)** -> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3156) in GitLab 13.9. -> - [Deployed behind a feature flag](../../feature_flags.md). +> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3156) in GitLab 13.9, disabled behind `ff_evaluate_group_level_compliance_pipeline` [feature flag](../../../administration/feature_flags.md). > - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/300324) in GitLab 13.11. -> - Enabled on GitLab.com. -> - Recommended for production use. - -WARNING: -This feature might not be available to you. Check the **version history** note above for details. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/331231) in GitLab 14.2. Group owners can use the compliance pipeline configuration to define compliance requirements such as scans or tests, and enforce them in individual projects. diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index 305ffc6ab96..aceaf012f7e 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -121,17 +121,26 @@ module Gitlab Gitlab::Ci::Variables::Collection.new.tap do |variables| break variables unless project - # The order of the next 4 lines is important as priority of CI variables is + # The order of the following lines is important as priority of CI variables is # defined globally within GitLab. # # See more detail in the docs: https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence variables.concat(project.predefined_variables) + variables.concat(pipeline_predefined_variables(ref: ref)) variables.concat(project.ci_instance_variables_for(ref: ref)) variables.concat(project.group.ci_variables_for(ref, project)) if project.group variables.concat(project.ci_variables_for(ref: ref)) end end + # https://gitlab.com/gitlab-org/gitlab/-/issues/337633 aims to add all predefined variables + # to this list, but only CI_COMMIT_REF_NAME is available right now to support compliance pipelines. + def pipeline_predefined_variables(ref:) + Gitlab::Ci::Variables::Collection.new.tap do |v| + v.append(key: 'CI_COMMIT_REF_NAME', value: ref) + end + end + def track_and_raise_for_dev_exception(error) Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error, @context.sentry_payload) end diff --git a/lib/gitlab/git/remote_mirror.rb b/lib/gitlab/git/remote_mirror.rb index eb368af199d..2f618294e8e 100644 --- a/lib/gitlab/git/remote_mirror.rb +++ b/lib/gitlab/git/remote_mirror.rb @@ -5,11 +5,10 @@ module Gitlab class RemoteMirror include Gitlab::Git::WrapsGitalyErrors - attr_reader :repository, :ref_name, :remote_url, :only_branches_matching, :ssh_key, :known_hosts, :keep_divergent_refs + attr_reader :repository, :remote_url, :only_branches_matching, :ssh_key, :known_hosts, :keep_divergent_refs - def initialize(repository, ref_name, remote_url, only_branches_matching: [], ssh_key: nil, known_hosts: nil, keep_divergent_refs: false) + def initialize(repository, remote_url, only_branches_matching: [], ssh_key: nil, known_hosts: nil, keep_divergent_refs: false) @repository = repository - @ref_name = ref_name @remote_url = remote_url @only_branches_matching = only_branches_matching @ssh_key = ssh_key @@ -20,7 +19,6 @@ module Gitlab def update wrapped_gitaly_errors do repository.gitaly_remote_client.update_remote_mirror( - ref_name, remote_url, only_branches_matching, ssh_key: ssh_key, diff --git a/lib/gitlab/gitaly_client/remote_service.rb b/lib/gitlab/gitaly_client/remote_service.rb index 7e8dafe8500..535b987f91c 100644 --- a/lib/gitlab/gitaly_client/remote_service.rb +++ b/lib/gitlab/gitaly_client/remote_service.rb @@ -37,18 +37,13 @@ module Gitlab encode_utf8(response.ref) end - def update_remote_mirror(ref_name, remote_url, only_branches_matching, ssh_key: nil, known_hosts: nil, keep_divergent_refs: false) + def update_remote_mirror(remote_url, only_branches_matching, ssh_key: nil, known_hosts: nil, keep_divergent_refs: false) req_enum = Enumerator.new do |y| first_request = Gitaly::UpdateRemoteMirrorRequest.new( repository: @gitaly_repo ) - if remote_url - first_request.remote = Gitaly::UpdateRemoteMirrorRequest::Remote.new(url: remote_url) - else - first_request.ref_name = ref_name - end - + first_request.remote = Gitaly::UpdateRemoteMirrorRequest::Remote.new(url: remote_url) first_request.ssh_key = ssh_key if ssh_key.present? first_request.known_hosts = known_hosts if known_hosts.present? first_request.keep_divergent_refs = keep_divergent_refs diff --git a/lib/gitlab/usage_data_counters/hll_redis_counter.rb b/lib/gitlab/usage_data_counters/hll_redis_counter.rb index 597df9936ea..cd27c2f8b8d 100644 --- a/lib/gitlab/usage_data_counters/hll_redis_counter.rb +++ b/lib/gitlab/usage_data_counters/hll_redis_counter.rb @@ -117,7 +117,7 @@ module Gitlab private def track(values, event_name, context: '', time: Time.zone.now) - return unless usage_ping_enabled? + return unless ::ServicePing::ServicePingSettings.enabled? event = event_for(event_name) Gitlab::ErrorTracking.track_and_raise_for_dev_exception(UnknownEvent.new("Unknown event #{event_name}")) unless event.present? @@ -131,10 +131,6 @@ module Gitlab Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e) end - def usage_ping_enabled? - Gitlab::CurrentSettings.usage_ping_enabled? - end - # The array of valid context on which we allow tracking def valid_context_list Plan.all_plans diff --git a/lib/gitlab/usage_data_counters/redis_counter.rb b/lib/gitlab/usage_data_counters/redis_counter.rb index 2406f771fd8..591e431c871 100644 --- a/lib/gitlab/usage_data_counters/redis_counter.rb +++ b/lib/gitlab/usage_data_counters/redis_counter.rb @@ -4,13 +4,13 @@ module Gitlab module UsageDataCounters module RedisCounter def increment(redis_counter_key) - return unless Gitlab::CurrentSettings.usage_ping_enabled + return unless ::ServicePing::ServicePingSettings.enabled? Gitlab::Redis::SharedState.with { |redis| redis.incr(redis_counter_key) } end def increment_by(redis_counter_key, incr) - return unless Gitlab::CurrentSettings.usage_ping_enabled + return unless ::ServicePing::ServicePingSettings.enabled? Gitlab::Redis::SharedState.with { |redis| redis.incrby(redis_counter_key, incr) } end diff --git a/qa/qa/page/group/menu.rb b/qa/qa/page/group/menu.rb index 412f66f17c0..9f793934dc9 100644 --- a/qa/qa/page/group/menu.rb +++ b/qa/qa/page/group/menu.rb @@ -15,11 +15,6 @@ module QA element :group_package_settings_link end - view 'app/views/layouts/nav/sidebar/_analytics_links.html.haml' do - element :analytics_link - element :analytics_sidebar_submenu - end - def click_group_members_item hover_group_information do within_submenu do @@ -42,14 +37,6 @@ module QA end end - def click_contribution_analytics_item - hover_element(:analytics_link) do - within_submenu(:analytics_sidebar_submenu) do - click_element(:contribution_analytics_link) - end - end - end - def click_group_general_settings_item hover_element(:group_settings) do within_submenu(:group_sidebar_submenu) do diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 7f330681a82..fb86f4672bc 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -265,7 +265,6 @@ FactoryBot.define do trait :remote_mirror do transient do - remote_name { "remote_mirror_#{SecureRandom.hex}" } url { "http://foo.com" } enabled { true } end diff --git a/spec/features/clusters/cluster_health_dashboard_spec.rb b/spec/features/clusters/cluster_health_dashboard_spec.rb index 20c07f4d6ac..32caad775e4 100644 --- a/spec/features/clusters/cluster_health_dashboard_spec.rb +++ b/spec/features/clusters/cluster_health_dashboard_spec.rb @@ -63,21 +63,33 @@ RSpec.describe 'Cluster Health board', :js, :kubeclient, :use_clean_rails_memory context 'connected, prometheus returns data' do before do stub_connected - end - it 'renders charts' do visit cluster_path click_link 'Health' wait_for_requests + end + it 'renders charts' do expect(page).to have_css('.prometheus-graphs') expect(page).to have_css('.prometheus-graph') expect(page).to have_css('.prometheus-graph-title') expect(page).to have_css('[_echarts_instance_]') + expect(page).to have_css('.prometheus-graph', count: 2) expect(page).to have_content('Avg') end + + it 'focuses the single panel on toggle' do + click_button('More actions') + click_button('Expand panel') + + expect(page).to have_css('.prometheus-graph', count: 1) + + click_button('Collapse panel') + + expect(page).to have_css('.prometheus-graph', count: 2) + end end def stub_empty_response diff --git a/spec/frontend/content_editor/extensions/code_block_highlight_spec.js b/spec/frontend/content_editor/extensions/code_block_highlight_spec.js index 79e55db30cd..188e6580dc6 100644 --- a/spec/frontend/content_editor/extensions/code_block_highlight_spec.js +++ b/spec/frontend/content_editor/extensions/code_block_highlight_spec.js @@ -25,7 +25,6 @@ describe('content_editor/extensions/code_block_highlight', () => { expect(tiptapEditor.getJSON().content[0].attrs).toMatchObject({ language, - params: language, }); }); diff --git a/spec/frontend/tracking_spec.js b/spec/frontend/tracking_spec.js index 13498cfb823..a17efdd61a9 100644 --- a/spec/frontend/tracking_spec.js +++ b/spec/frontend/tracking_spec.js @@ -387,11 +387,13 @@ describe('Tracking', () => { beforeEach(() => { eventSpy = jest.spyOn(Tracking, 'event'); setHTMLFixture(` - <input data-track-${term}="render" data-track-label="label1" value=1 data-track-property="_property_"/> - <span data-track-${term}="render" data-track-label="label2" data-track-value=1> - Something - </span> - <input data-track-${term}="_render_bogus_" data-track-label="label3" value="_value_" data-track-property="_property_"/> + <div data-track-${term}="click_link" data-track-label="all_nested_links"> + <input data-track-${term}="render" data-track-label="label1" value=1 data-track-property="_property_"/> + <span data-track-${term}="render" data-track-label="label2" data-track-value=1> + <a href="#" id="link">Something</a> + </span> + <input data-track-${term}="_render_bogus_" data-track-label="label3" value="_value_" data-track-property="_property_"/> + </div> `); Tracking.trackLoadEvents('_category_'); // only happens once }); @@ -417,6 +419,35 @@ describe('Tracking', () => { ], ]); }); + + describe.each` + event | actionSuffix + ${'click'} | ${''} + ${'show.bs.dropdown'} | ${'_show'} + ${'hide.bs.dropdown'} | ${'_hide'} + `(`auto-tracking $event events on nested elements`, ({ event, actionSuffix }) => { + let link; + + beforeEach(() => { + link = document.querySelector('#link'); + eventSpy.mockClear(); + }); + + it(`avoids using ancestor [data-track-${term}="render"] tracking configurations`, () => { + link.dispatchEvent(new Event(event, { bubbles: true })); + + expect(eventSpy).not.toHaveBeenCalledWith( + '_category_', + `render${actionSuffix}`, + expect.any(Object), + ); + expect(eventSpy).toHaveBeenCalledWith( + '_category_', + `click_link${actionSuffix}`, + expect.objectContaining({ label: 'all_nested_links' }), + ); + }); + }); }); describe('tracking mixin', () => { diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js index 46a11bc28d8..90bc1980ac3 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js @@ -1,6 +1,6 @@ import { GlLoadingIcon, GlLink } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vue, { nextTick } from 'vue'; +import { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; @@ -14,7 +14,7 @@ jest.mock('~/flash'); const colors = Object.keys(mockSuggestedColors); const localVue = createLocalVue(); -Vue.use(VueApollo); +localVue.use(VueApollo); const userRecoverableError = { ...createLabelSuccessfulResponse, diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js index 51301387c99..8bd944a3d54 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js @@ -1,357 +1,213 @@ -import { GlIntersectionObserver, GlLoadingIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui'; +import { GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes'; +import { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import createFlash from '~/flash'; +import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; +import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_widget/constants'; import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue'; +import projectLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql'; import LabelItem from '~/vue_shared/components/sidebar/labels_select_widget/label_item.vue'; +import { mockConfig, labelsQueryResponse } from './mock_data'; -import * as actions from '~/vue_shared/components/sidebar/labels_select_widget/store/actions'; -import * as getters from '~/vue_shared/components/sidebar/labels_select_widget/store/getters'; -import mutations from '~/vue_shared/components/sidebar/labels_select_widget/store/mutations'; -import defaultState from '~/vue_shared/components/sidebar/labels_select_widget/store/state'; - -import { mockConfig, mockLabels, mockRegularLabel } from './mock_data'; +jest.mock('~/flash'); const localVue = createLocalVue(); -localVue.use(Vuex); +localVue.use(VueApollo); + +const selectedLabels = [ + { + id: 28, + title: 'Bug', + description: 'Label for bugs', + color: '#FF0000', + textColor: '#FFFFFF', + }, +]; describe('DropdownContentsLabelsView', () => { let wrapper; - const createComponent = (initialState = mockConfig) => { - const store = new Vuex.Store({ - getters, - mutations, - state: { - ...defaultState(), - footerCreateLabelTitle: 'Create label', - footerManageLabelTitle: 'Manage labels', - }, - actions: { - ...actions, - fetchLabels: jest.fn(), - }, - }); + const successfulQueryHandler = jest.fn().mockResolvedValue(labelsQueryResponse); - store.dispatch('setInitialState', initialState); - store.dispatch('receiveLabelsSuccess', mockLabels); + const createComponent = ({ + initialState = mockConfig, + queryHandler = successfulQueryHandler, + injected = {}, + } = {}) => { + const mockApollo = createMockApollo([[projectLabelsQuery, queryHandler]]); wrapper = shallowMount(DropdownContentsLabelsView, { localVue, - store, + apolloProvider: mockApollo, + provide: { + projectPath: 'test', + iid: 1, + allowLabelCreate: true, + labelsManagePath: '/gitlab-org/my-project/-/labels', + variant: DropdownVariant.Sidebar, + ...injected, + }, + propsData: { + ...initialState, + selectedLabels, + }, + stubs: { + GlSearchBoxByType, + }, }); }; - beforeEach(() => { - createComponent(); - }); - afterEach(() => { wrapper.destroy(); - wrapper = null; }); - const findDropdownContent = () => wrapper.find('[data-testid="dropdown-content"]'); + const findSearchInput = () => wrapper.findComponent(GlSearchBoxByType); + const findLabels = () => wrapper.findAllComponents(LabelItem); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + + const findLabelsList = () => wrapper.find('[data-testid="labels-list"]'); + const findDropdownWrapper = () => wrapper.find('[data-testid="dropdown-wrapper"]'); const findDropdownFooter = () => wrapper.find('[data-testid="dropdown-footer"]'); - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); - - describe('computed', () => { - describe('visibleLabels', () => { - it('returns matching labels filtered with `searchKey`', () => { - wrapper.setData({ - searchKey: 'bug', - }); - - expect(wrapper.vm.visibleLabels.length).toBe(1); - expect(wrapper.vm.visibleLabels[0].title).toBe('Bug'); - }); - - it('returns matching labels with fuzzy filtering', () => { - wrapper.setData({ - searchKey: 'bg', - }); - - expect(wrapper.vm.visibleLabels.length).toBe(2); - expect(wrapper.vm.visibleLabels[0].title).toBe('Bug'); - expect(wrapper.vm.visibleLabels[1].title).toBe('Boog'); - }); - - it('returns all labels when `searchKey` is empty', () => { - wrapper.setData({ - searchKey: '', - }); - - expect(wrapper.vm.visibleLabels.length).toBe(mockLabels.length); - }); - }); + const findNoResultsMessage = () => wrapper.find('[data-testid="no-results"]'); + const findCreateLabelButton = () => wrapper.find('[data-testid="create-label-button"]'); - describe('showNoMatchingResultsMessage', () => { - it.each` - searchKey | labels | labelsDescription | returnValue - ${''} | ${[]} | ${'empty'} | ${false} - ${'bug'} | ${[]} | ${'empty'} | ${true} - ${''} | ${mockLabels} | ${'not empty'} | ${false} - ${'bug'} | ${mockLabels} | ${'not empty'} | ${false} - `( - 'returns $returnValue when searchKey is "$searchKey" and visibleLabels is $labelsDescription', - async ({ searchKey, labels, returnValue }) => { - wrapper.setData({ - searchKey, - }); - - wrapper.vm.$store.dispatch('receiveLabelsSuccess', labels); - - await wrapper.vm.$nextTick(); - - expect(wrapper.vm.showNoMatchingResultsMessage).toBe(returnValue); - }, - ); + describe('when loading labels', () => { + it('renders disabled search input field', async () => { + createComponent(); + expect(findSearchInput().props('disabled')).toBe(true); }); - }); - - describe('methods', () => { - describe('isLabelSelected', () => { - it('returns true when provided `label` param is one of the selected labels', () => { - expect(wrapper.vm.isLabelSelected(mockRegularLabel)).toBe(true); - }); - it('returns false when provided `label` param is not one of the selected labels', () => { - expect(wrapper.vm.isLabelSelected(mockLabels[2])).toBe(false); - }); + it('renders loading icon', async () => { + createComponent(); + expect(findLoadingIcon().exists()).toBe(true); }); - describe('handleComponentAppear', () => { - it('calls `focusInput` on searchInput field', async () => { - wrapper.vm.$refs.searchInput.focusInput = jest.fn(); - - await wrapper.vm.handleComponentAppear(); - - expect(wrapper.vm.$refs.searchInput.focusInput).toHaveBeenCalled(); - }); + it('does not render labels list', async () => { + createComponent(); + expect(findLabelsList().exists()).toBe(false); }); + }); - describe('handleComponentDisappear', () => { - it('calls action `receiveLabelsSuccess` with empty array', () => { - jest.spyOn(wrapper.vm, 'receiveLabelsSuccess'); - - wrapper.vm.handleComponentDisappear(); - - expect(wrapper.vm.receiveLabelsSuccess).toHaveBeenCalledWith([]); - }); + describe('when labels are loaded', () => { + beforeEach(async () => { + createComponent(); + await waitForPromises(); }); - describe('handleCreateLabelClick', () => { - it('calls actions `receiveLabelsSuccess` with empty array and `toggleDropdownContentsCreateView`', () => { - jest.spyOn(wrapper.vm, 'receiveLabelsSuccess'); - jest.spyOn(wrapper.vm, 'toggleDropdownContentsCreateView'); - - wrapper.vm.handleCreateLabelClick(); - - expect(wrapper.vm.receiveLabelsSuccess).toHaveBeenCalledWith([]); - expect(wrapper.vm.toggleDropdownContentsCreateView).toHaveBeenCalled(); - }); + it('renders enabled search input field', async () => { + expect(findSearchInput().props('disabled')).toBe(false); }); - describe('handleKeyDown', () => { - it('decreases `currentHighlightItem` value by 1 when Up arrow key is pressed', () => { - wrapper.setData({ - currentHighlightItem: 1, - }); - - wrapper.vm.handleKeyDown({ - keyCode: UP_KEY_CODE, - }); - - expect(wrapper.vm.currentHighlightItem).toBe(0); - }); - - it('increases `currentHighlightItem` value by 1 when Down arrow key is pressed', () => { - wrapper.setData({ - currentHighlightItem: 1, - }); - - wrapper.vm.handleKeyDown({ - keyCode: DOWN_KEY_CODE, - }); - - expect(wrapper.vm.currentHighlightItem).toBe(2); - }); - - it('resets the search text when the Enter key is pressed', () => { - wrapper.setData({ - currentHighlightItem: 1, - searchKey: 'bug', - }); - - wrapper.vm.handleKeyDown({ - keyCode: ENTER_KEY_CODE, - }); - - expect(wrapper.vm.searchKey).toBe(''); - }); - - it('calls action `updateSelectedLabels` with currently highlighted label when Enter key is pressed', () => { - jest.spyOn(wrapper.vm, 'updateSelectedLabels').mockImplementation(); - wrapper.setData({ - currentHighlightItem: 1, - }); - - wrapper.vm.handleKeyDown({ - keyCode: ENTER_KEY_CODE, - }); - - expect(wrapper.vm.updateSelectedLabels).toHaveBeenCalledWith([ - { - ...mockLabels[1], - set: true, - }, - ]); - }); - - it('calls action `toggleDropdownContents` when Esc key is pressed', () => { - jest.spyOn(wrapper.vm, 'toggleDropdownContents').mockImplementation(); - wrapper.setData({ - currentHighlightItem: 1, - }); - - wrapper.vm.handleKeyDown({ - keyCode: ESC_KEY_CODE, - }); - - expect(wrapper.vm.toggleDropdownContents).toHaveBeenCalled(); - }); - - it('calls action `scrollIntoViewIfNeeded` in next tick when any key is pressed', () => { - jest.spyOn(wrapper.vm, 'scrollIntoViewIfNeeded').mockImplementation(); - wrapper.setData({ - currentHighlightItem: 1, - }); - - wrapper.vm.handleKeyDown({ - keyCode: DOWN_KEY_CODE, - }); - - return wrapper.vm.$nextTick(() => { - expect(wrapper.vm.scrollIntoViewIfNeeded).toHaveBeenCalled(); - }); - }); + it('does not render loading icon', async () => { + expect(findLoadingIcon().exists()).toBe(false); }); - describe('handleLabelClick', () => { - beforeEach(() => { - jest.spyOn(wrapper.vm, 'updateSelectedLabels').mockImplementation(); - }); - - it('calls action `updateSelectedLabels` with provided `label` param', () => { - wrapper.vm.handleLabelClick(mockRegularLabel); - - expect(wrapper.vm.updateSelectedLabels).toHaveBeenCalledWith([mockRegularLabel]); - }); + it('renders labels list', async () => { + expect(findLabelsList().exists()).toBe(true); + expect(findLabels()).toHaveLength(2); + }); - it('calls action `toggleDropdownContents` when `state.allowMultiselect` is false', () => { - jest.spyOn(wrapper.vm, 'toggleDropdownContents'); - wrapper.vm.$store.state.allowMultiselect = false; + it('changes highlighted label correctly on pressing down button', async () => { + expect(findLabels().at(0).attributes('highlight')).toBeUndefined(); - wrapper.vm.handleLabelClick(mockRegularLabel); + await findDropdownWrapper().trigger('keydown.down'); + expect(findLabels().at(0).attributes('highlight')).toBe('true'); - expect(wrapper.vm.toggleDropdownContents).toHaveBeenCalled(); - }); + await findDropdownWrapper().trigger('keydown.down'); + expect(findLabels().at(1).attributes('highlight')).toBe('true'); + expect(findLabels().at(0).attributes('highlight')).toBeUndefined(); }); - }); - describe('template', () => { - it('renders gl-intersection-observer as component root', () => { - expect(wrapper.find(GlIntersectionObserver).exists()).toBe(true); - }); + it('changes highlighted label correctly on pressing up button', async () => { + await findDropdownWrapper().trigger('keydown.down'); + await findDropdownWrapper().trigger('keydown.down'); + expect(findLabels().at(1).attributes('highlight')).toBe('true'); - it('renders gl-loading-icon component when `labelsFetchInProgress` prop is true', () => { - wrapper.vm.$store.dispatch('requestLabels'); + await findDropdownWrapper().trigger('keydown.up'); + expect(findLabels().at(0).attributes('highlight')).toBe('true'); + }); - return wrapper.vm.$nextTick(() => { - const loadingIconEl = findLoadingIcon(); + it('changes label selected state when Enter is pressed', async () => { + expect(findLabels().at(0).attributes('islabelset')).toBeUndefined(); + await findDropdownWrapper().trigger('keydown.down'); + await findDropdownWrapper().trigger('keydown.enter'); - expect(loadingIconEl.exists()).toBe(true); - expect(loadingIconEl.attributes('class')).toContain('labels-fetch-loading'); - }); + expect(findLabels().at(0).attributes('islabelset')).toBe('true'); }); - it('renders label search input element', () => { - const searchInputEl = wrapper.find(GlSearchBoxByType); + it('emits `closeDropdown event` when Esc button is pressed', () => { + findDropdownWrapper().trigger('keydown.esc'); - expect(searchInputEl.exists()).toBe(true); + expect(wrapper.emitted('closeDropdown')).toEqual([[selectedLabels]]); }); + }); - it('renders label elements for all labels', () => { - expect(wrapper.findAll(LabelItem)).toHaveLength(mockLabels.length); + it('when search returns 0 results', async () => { + createComponent({ + queryHandler: jest.fn().mockResolvedValue({ + data: { + workspace: { + labels: { + nodes: [], + }, + }, + }, + }), }); + findSearchInput().vm.$emit('input', '123'); + await waitForPromises(); + await nextTick(); - it('renders label element with `highlight` set to true when value of `currentHighlightItem` is more than -1', () => { - wrapper.setData({ - currentHighlightItem: 0, - }); + expect(findNoResultsMessage().isVisible()).toBe(true); + }); - return wrapper.vm.$nextTick(() => { - const labelItemEl = findDropdownContent().find(LabelItem); + it('calls `createFlash` when fetching labels failed', async () => { + createComponent({ queryHandler: jest.fn().mockRejectedValue('Houston, we have a problem!') }); + jest.advanceTimersByTime(DEFAULT_DEBOUNCE_AND_THROTTLE_MS); + await waitForPromises(); + expect(createFlash).toHaveBeenCalled(); + }); - expect(labelItemEl.attributes('highlight')).toBe('true'); - }); - }); + it('does not render footer on standalone dropdown', () => { + createComponent({ injected: { variant: DropdownVariant.Standalone } }); - it('renders element containing "No matching results" when `searchKey` does not match with any label', () => { - wrapper.setData({ - searchKey: 'abc', - }); + expect(findDropdownFooter().exists()).toBe(false); + }); - return wrapper.vm.$nextTick(() => { - const noMatchEl = findDropdownContent().find('li'); + it('renders footer on sidebar dropdown', () => { + createComponent(); - expect(noMatchEl.isVisible()).toBe(true); - expect(noMatchEl.text()).toContain('No matching results'); - }); - }); + expect(findDropdownFooter().exists()).toBe(true); + }); - it('renders empty content while loading', () => { - wrapper.vm.$store.state.labelsFetchInProgress = true; + it('renders footer on embedded dropdown', () => { + createComponent({ injected: { variant: DropdownVariant.Embedded } }); - return wrapper.vm.$nextTick(() => { - const dropdownContent = findDropdownContent(); - const loadingIcon = findLoadingIcon(); + expect(findDropdownFooter().exists()).toBe(true); + }); - expect(dropdownContent.exists()).toBe(true); - expect(dropdownContent.isVisible()).toBe(true); - expect(loadingIcon.exists()).toBe(true); - expect(loadingIcon.isVisible()).toBe(true); - }); - }); + it('does not render create label button if `allowLabelCreate` is false', () => { + createComponent({ injected: { allowLabelCreate: false } }); - it('renders footer list items', () => { - const footerLinks = findDropdownFooter().findAll(GlLink); - const createLabelLink = footerLinks.at(0); - const manageLabelsLink = footerLinks.at(1); + expect(findCreateLabelButton().exists()).toBe(false); + }); - expect(createLabelLink.exists()).toBe(true); - expect(createLabelLink.text()).toBe('Create label'); - expect(manageLabelsLink.exists()).toBe(true); - expect(manageLabelsLink.text()).toBe('Manage labels'); + describe('when `allowLabelCreate` is true', () => { + beforeEach(() => { + createComponent(); }); - it('does not render "Create label" footer link when `state.allowLabelCreate` is `false`', () => { - wrapper.vm.$store.state.allowLabelCreate = false; - - return wrapper.vm.$nextTick(() => { - const createLabelLink = findDropdownFooter().findAll(GlLink).at(0); - - expect(createLabelLink.text()).not.toBe('Create label'); - }); + it('renders create label button', () => { + expect(findCreateLabelButton().exists()).toBe(true); }); - it('does not render footer list items when `state.variant` is "standalone"', () => { - createComponent({ ...mockConfig, variant: 'standalone' }); - expect(findDropdownFooter().exists()).toBe(false); - }); + it('emits `toggleDropdownContentsCreateView` event on create label button click', () => { + findCreateLabelButton().vm.$emit('click'); - it('renders footer list items when `state.variant` is "embedded"', () => { - expect(findDropdownFooter().exists()).toBe(true); + expect(wrapper.emitted('toggleDropdownContentsCreateView')).toEqual([[]]); }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js index 8273bbdf7a7..3c2fd0c5acc 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js @@ -5,7 +5,7 @@ import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_w import DropdownContents from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue'; import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_widget/store'; -import { mockConfig } from './mock_data'; +import { mockConfig, mockLabels } from './mock_data'; const localVue = createLocalVue(); localVue.use(Vuex); @@ -19,6 +19,11 @@ const createComponent = (initialState = mockConfig, defaultProps = {}) => { propsData: { ...defaultProps, labelsCreateTitle: 'test', + selectedLabels: mockLabels, + allowMultiselect: true, + labelsListTitle: 'Assign labels', + footerCreateLabelTitle: 'create', + footerManageLabelTitle: 'manage', }, localVue, store, diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js index 66971446f47..e17dfd93efc 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js @@ -50,58 +50,6 @@ describe('LabelsSelectRoot', () => { }); describe('methods', () => { - describe('handleVuexActionDispatch', () => { - it('calls `handleDropdownClose` when params `action.type` is `toggleDropdownContents` and state has `showDropdownButton` & `showDropdownContents` props `false`', () => { - createComponent(); - jest.spyOn(wrapper.vm, 'handleDropdownClose').mockImplementation(); - - wrapper.vm.handleVuexActionDispatch( - { type: 'toggleDropdownContents' }, - { - showDropdownButton: false, - showDropdownContents: false, - labels: [{ id: 1 }, { id: 2, touched: true }], - }, - ); - - expect(wrapper.vm.handleDropdownClose).toHaveBeenCalledWith( - expect.arrayContaining([ - { - id: 2, - touched: true, - }, - ]), - ); - }); - - it('calls `handleDropdownClose` with state.labels filterd using `set` prop when dropdown variant is `embedded`', () => { - createComponent({ - ...mockConfig, - variant: 'embedded', - }); - - jest.spyOn(wrapper.vm, 'handleDropdownClose').mockImplementation(); - - wrapper.vm.handleVuexActionDispatch( - { type: 'toggleDropdownContents' }, - { - showDropdownButton: false, - showDropdownContents: false, - labels: [{ id: 1 }, { id: 2, set: true }], - }, - ); - - expect(wrapper.vm.handleDropdownClose).toHaveBeenCalledWith( - expect.arrayContaining([ - { - id: 2, - set: true, - }, - ]), - ); - }); - }); - describe('handleDropdownClose', () => { beforeEach(() => { createComponent(); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js index 9e29030fb56..5dd8fc1b8b2 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js @@ -48,6 +48,8 @@ export const mockConfig = { labelsManagePath: '/gitlab-org/my-project/-/labels', labelsFilterBasePath: '/gitlab-org/my-project/issues', labelsFilterParam: 'label_name', + footerCreateLabelTitle: 'create', + footerManageLabelTitle: 'manage', }; export const mockSuggestedColors = { @@ -91,3 +93,26 @@ export const createLabelSuccessfulResponse = { }, }, }; + +export const labelsQueryResponse = { + data: { + workspace: { + labels: { + nodes: [ + { + color: '#330066', + description: null, + id: 'gid://gitlab/ProjectLabel/1', + title: 'Label1', + }, + { + color: '#2f7b2e', + description: null, + id: 'gid://gitlab/ProjectLabel/2', + title: 'Label2', + }, + ], + }, + }, + }, +}; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js index 27de7de2411..ee905410ffa 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js @@ -1,8 +1,4 @@ -import MockAdapter from 'axios-mock-adapter'; - import testAction from 'helpers/vuex_action_helper'; -import createFlash from '~/flash'; -import axios from '~/lib/utils/axios_utils'; import * as actions from '~/vue_shared/components/sidebar/labels_select_widget/store/actions'; import * as types from '~/vue_shared/components/sidebar/labels_select_widget/store/mutation_types'; import defaultState from '~/vue_shared/components/sidebar/labels_select_widget/store/state'; @@ -72,90 +68,6 @@ describe('LabelsSelect Actions', () => { }); }); - describe('requestLabels', () => { - it('sets value of `state.labelsFetchInProgress` to `true`', (done) => { - testAction(actions.requestLabels, {}, state, [{ type: types.REQUEST_LABELS }], [], done); - }); - }); - - describe('receiveLabelsSuccess', () => { - it('sets provided labels to `state.labels`', (done) => { - const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; - - testAction( - actions.receiveLabelsSuccess, - labels, - state, - [{ type: types.RECEIVE_SET_LABELS_SUCCESS, payload: labels }], - [], - done, - ); - }); - }); - - describe('receiveLabelsFailure', () => { - it('sets value `state.labelsFetchInProgress` to `false`', (done) => { - testAction( - actions.receiveLabelsFailure, - {}, - state, - [{ type: types.RECEIVE_SET_LABELS_FAILURE }], - [], - done, - ); - }); - - it('shows flash error', () => { - actions.receiveLabelsFailure({ commit: () => {} }); - - expect(createFlash).toHaveBeenCalledWith({ message: 'Error fetching labels.' }); - }); - }); - - describe('fetchLabels', () => { - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - state.labelsFetchPath = 'labels.json'; - }); - - afterEach(() => { - mock.restore(); - }); - - describe('on success', () => { - it('dispatches `requestLabels` & `receiveLabelsSuccess` actions', (done) => { - const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; - mock.onGet(/labels.json/).replyOnce(200, labels); - - testAction( - actions.fetchLabels, - {}, - state, - [], - [{ type: 'requestLabels' }, { type: 'receiveLabelsSuccess', payload: labels }], - done, - ); - }); - }); - - describe('on failure', () => { - it('dispatches `requestLabels` & `receiveLabelsFailure` actions', (done) => { - mock.onGet(/labels.json/).replyOnce(500, {}); - - testAction( - actions.fetchLabels, - {}, - state, - [], - [{ type: 'requestLabels' }, { type: 'receiveLabelsFailure' }], - done, - ); - }); - }); - }); - describe('updateSelectedLabels', () => { it('updates `state.labels` based on provided `labels` param', (done) => { const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js index 9e965cb33e8..1f0e0eee420 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js @@ -67,58 +67,6 @@ describe('LabelsSelect Mutations', () => { }); }); - describe(`${types.REQUEST_LABELS}`, () => { - it('sets value of `state.labelsFetchInProgress` to true', () => { - const state = { - labelsFetchInProgress: false, - }; - mutations[types.REQUEST_LABELS](state); - - expect(state.labelsFetchInProgress).toBe(true); - }); - }); - - describe(`${types.RECEIVE_SET_LABELS_SUCCESS}`, () => { - const selectedLabels = [{ id: 2 }, { id: 4 }]; - const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; - - it('sets value of `state.labelsFetchInProgress` to false', () => { - const state = { - selectedLabels, - labelsFetchInProgress: true, - }; - mutations[types.RECEIVE_SET_LABELS_SUCCESS](state, labels); - - expect(state.labelsFetchInProgress).toBe(false); - }); - - it('sets provided `labels` to `state.labels` along with `set` prop based on `state.selectedLabels`', () => { - const selectedLabelIds = selectedLabels.map((label) => label.id); - const state = { - selectedLabels, - labelsFetchInProgress: true, - }; - mutations[types.RECEIVE_SET_LABELS_SUCCESS](state, labels); - - state.labels.forEach((label) => { - if (selectedLabelIds.includes(label.id)) { - expect(label.set).toBe(true); - } - }); - }); - }); - - describe(`${types.RECEIVE_SET_LABELS_FAILURE}`, () => { - it('sets value of `state.labelsFetchInProgress` to false', () => { - const state = { - labelsFetchInProgress: true, - }; - mutations[types.RECEIVE_SET_LABELS_FAILURE](state); - - expect(state.labelsFetchInProgress).toBe(false); - }); - }); - describe(`${types.UPDATE_SELECTED_LABELS}`, () => { let labels; diff --git a/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js b/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js deleted file mode 100644 index b85f50ec998..00000000000 --- a/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * This file should only contain browser specific specs. - * If you need to add or update a spec, please see spec/frontend/monitoring/components/*.js - * https://gitlab.com/gitlab-org/gitlab/-/issues/194244#note_343427737 - * https://gitlab.com/groups/gitlab-org/-/epics/895#what-if-theres-a-karma-spec-which-is-simply-unmovable-to-jest-ie-it-is-dependent-on-a-running-browser-environment - */ - -import { createLocalVue } from '@vue/test-utils'; -import MockAdapter from 'axios-mock-adapter'; -import Vue from 'vue'; -import axios from '~/lib/utils/axios_utils'; -import Dashboard from '~/monitoring/components/dashboard.vue'; -import { createStore } from '~/monitoring/stores'; -import { metricsDashboardPayload, dashboardProps } from '../fixture_data'; -import { mockApiEndpoint } from '../mock_data'; -import { setupStoreWithData } from '../store_utils'; - -const localVue = createLocalVue(); - -describe('Dashboard', () => { - let DashboardComponent; - let mock; - let store; - let component; - let wrapper; - - beforeEach(() => { - setFixtures(` - <div class="prometheus-graphs"></div> - <div class="layout-page"></div> - `); - - store = createStore(); - mock = new MockAdapter(axios); - DashboardComponent = localVue.extend(Dashboard); - }); - - afterEach(() => { - if (component) { - component.$destroy(); - } - if (wrapper) { - wrapper.destroy(); - } - mock.restore(); - }); - - describe('responds to window resizes', () => { - let promPanel; - let promGroup; - let panelToggle; - let chart; - beforeEach(() => { - mock.onGet(mockApiEndpoint).reply(200, metricsDashboardPayload); - - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...dashboardProps, - hasMetrics: true, - showPanels: true, - }, - store, - provide: { hasManagedPrometheus: false }, - }); - - setupStoreWithData(component.$store); - - return Vue.nextTick().then(() => { - [promPanel] = component.$el.querySelectorAll('.prometheus-panel'); - promGroup = promPanel.querySelector('.prometheus-graph-group'); - panelToggle = promPanel.querySelector('.js-graph-group-toggle'); - chart = promGroup.querySelector('.position-relative svg'); - }); - }); - - it('setting chart size to zero when panel group is hidden', () => { - expect(promGroup.style.display).toBe(''); - expect(chart.clientWidth).toBeGreaterThan(0); - - panelToggle.click(); - return Vue.nextTick().then(() => { - expect(promGroup.style.display).toBe('none'); - expect(chart.clientWidth).toBe(0); - promPanel.style.width = '500px'; - }); - }); - - it('expanding chart panel group after resize displays chart', () => { - panelToggle.click(); - - expect(chart.clientWidth).toBeGreaterThan(0); - }); - }); -}); diff --git a/spec/javascripts/monitoring/fixture_data.js b/spec/javascripts/monitoring/fixture_data.js deleted file mode 100644 index 1375c27cdde..00000000000 --- a/spec/javascripts/monitoring/fixture_data.js +++ /dev/null @@ -1 +0,0 @@ -export * from '../../frontend/monitoring/fixture_data'; diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js deleted file mode 100644 index c80401e8c1d..00000000000 --- a/spec/javascripts/monitoring/mock_data.js +++ /dev/null @@ -1,5 +0,0 @@ -// No new code should be added to this file. Instead, modify the -// file this one re-exports from. For more detail about why, see: -// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349 - -export * from '../../frontend/monitoring/mock_data'; diff --git a/spec/javascripts/monitoring/store_utils.js b/spec/javascripts/monitoring/store_utils.js deleted file mode 100644 index 1222716c829..00000000000 --- a/spec/javascripts/monitoring/store_utils.js +++ /dev/null @@ -1 +0,0 @@ -export * from '../../frontend/monitoring/store_utils'; diff --git a/spec/lib/gitlab/git/remote_mirror_spec.rb b/spec/lib/gitlab/git/remote_mirror_spec.rb index 0954879f6bd..4b827e5d2d0 100644 --- a/spec/lib/gitlab/git/remote_mirror_spec.rb +++ b/spec/lib/gitlab/git/remote_mirror_spec.rb @@ -6,30 +6,17 @@ RSpec.describe Gitlab::Git::RemoteMirror do describe '#update' do let(:project) { create(:project, :repository) } let(:repository) { project.repository } - let(:ref_name) { 'foo' } let(:url) { 'https://example.com' } let(:options) { { only_branches_matching: ['master'], ssh_key: 'KEY', known_hosts: 'KNOWN HOSTS', keep_divergent_refs: true } } - subject(:remote_mirror) { described_class.new(repository, ref_name, url, **options) } + subject(:remote_mirror) { described_class.new(repository, url, **options) } - shared_examples 'an update' do - it 'delegates to the Gitaly client' do - expect(repository.gitaly_remote_client) - .to receive(:update_remote_mirror) - .with(ref_name, url, ['master'], ssh_key: 'KEY', known_hosts: 'KNOWN HOSTS', keep_divergent_refs: true) - - remote_mirror.update # rubocop:disable Rails/SaveBang - end - end - - context 'with url' do - it_behaves_like 'an update' - end - - context 'without url' do - let(:url) { nil } + it 'delegates to the Gitaly client' do + expect(repository.gitaly_remote_client) + .to receive(:update_remote_mirror) + .with(url, ['master'], ssh_key: 'KEY', known_hosts: 'KNOWN HOSTS', keep_divergent_refs: true) - it_behaves_like 'an update' + remote_mirror.update # rubocop:disable Rails/SaveBang end it 'wraps gitaly errors' do diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb index c01a070fcb8..3d0f8358406 100644 --- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb @@ -35,34 +35,19 @@ RSpec.describe Gitlab::GitalyClient::RemoteService do end describe '#update_remote_mirror' do - let(:ref_name) { 'remote_mirror_1' } let(:only_branches_matching) { %w[my-branch master] } let(:ssh_key) { 'KEY' } let(:known_hosts) { 'KNOWN HOSTS' } + let(:url) { 'http:://git.example.com/my-repo.git' } + let(:expected_params) { { remote: Gitaly::UpdateRemoteMirrorRequest::Remote.new(url: url) } } - shared_examples 'an update' do - it 'sends an update_remote_mirror message' do - expect_any_instance_of(Gitaly::RemoteService::Stub) - .to receive(:update_remote_mirror) - .with(array_including(gitaly_request_with_params(expected_params)), kind_of(Hash)) - .and_return(double(:update_remote_mirror_response)) - - client.update_remote_mirror(ref_name, url, only_branches_matching, ssh_key: ssh_key, known_hosts: known_hosts, keep_divergent_refs: true) - end - end - - context 'with remote name' do - let(:url) { nil } - let(:expected_params) { { ref_name: ref_name } } - - it_behaves_like 'an update' - end - - context 'with remote URL' do - let(:url) { 'http:://git.example.com/my-repo.git' } - let(:expected_params) { { remote: Gitaly::UpdateRemoteMirrorRequest::Remote.new(url: url) } } + it 'sends an update_remote_mirror message' do + expect_any_instance_of(Gitaly::RemoteService::Stub) + .to receive(:update_remote_mirror) + .with(array_including(gitaly_request_with_params(expected_params)), kind_of(Hash)) + .and_return(double(:update_remote_mirror_response)) - it_behaves_like 'an update' + client.update_remote_mirror(url, only_branches_matching, ssh_key: ssh_key, known_hosts: known_hosts, keep_divergent_refs: true) end end diff --git a/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb index b1d5d106082..d4148b57348 100644 --- a/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Gitlab::UsageDataCounters::CiTemplateUniqueCounter do Gitlab::Ci::Pipeline::Chain::Config::Content::AutoDevops.new(pipeline, command).content, project: project, user: double, - sha: double + sha: 'd310cc759caaa20cd05a9e0983d6017896d9c34c' ).execute config_source = :auto_devops_source diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb index d89202ae7fe..887759014f5 100644 --- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb @@ -143,7 +143,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s context 'when usage_ping is disabled' do it 'does not track the event' do - stub_application_setting(usage_ping_enabled: false) + allow(::ServicePing::ServicePingSettings).to receive(:enabled?).and_return(false) described_class.track_event(weekly_event, values: entity1, time: Date.current) @@ -153,7 +153,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s context 'when usage_ping is enabled' do before do - stub_application_setting(usage_ping_enabled: true) + allow(::ServicePing::ServicePingSettings).to receive(:enabled?).and_return(true) end it 'tracks event when using symbol' do diff --git a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb index d4f6110b3df..753e09731bf 100644 --- a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb @@ -8,12 +8,12 @@ RSpec.describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shar subject { Class.new.extend(described_class) } before do - stub_application_setting(usage_ping_enabled: setting_value) + allow(::ServicePing::ServicePingSettings).to receive(:enabled?).and_return(service_ping_enabled) end describe '.increment' do context 'when usage_ping is disabled' do - let(:setting_value) { false } + let(:service_ping_enabled) { false } it 'counter is not increased' do expect do @@ -23,7 +23,7 @@ RSpec.describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shar end context 'when usage_ping is enabled' do - let(:setting_value) { true } + let(:service_ping_enabled) { true } it 'counter is increased' do expect do @@ -35,7 +35,7 @@ RSpec.describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shar describe '.increment_by' do context 'when usage_ping is disabled' do - let(:setting_value) { false } + let(:service_ping_enabled) { false } it 'counter is not increased' do expect do @@ -45,7 +45,7 @@ RSpec.describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shar end context 'when usage_ping is enabled' do - let(:setting_value) { true } + let(:service_ping_enabled) { true } it 'counter is increased' do expect do diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index 058cc2c5925..382359ccb17 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -105,35 +105,6 @@ RSpec.describe RemoteMirror, :mailer do end end - describe '#remote_name' do - context 'when remote name is persisted in the database' do - it 'returns remote name with random value' do - allow(SecureRandom).to receive(:hex).and_return('secret') - - remote_mirror = create(:remote_mirror) - - expect(remote_mirror.remote_name).to eq('remote_mirror_secret') - end - end - - context 'when remote name is not persisted in the database' do - it 'returns remote name with remote mirror id' do - remote_mirror = create(:remote_mirror) - remote_mirror.remote_name = nil - - expect(remote_mirror.remote_name).to eq("remote_mirror_#{remote_mirror.id}") - end - end - - context 'when remote is not persisted in the database' do - it 'returns nil' do - remote_mirror = build(:remote_mirror, remote_name: nil) - - expect(remote_mirror.remote_name).to be_nil - end - end - end - describe '#bare_url' do it 'returns the URL without any credentials' do remote_mirror = build(:remote_mirror, url: 'http://user:pass@example.com/foo') @@ -158,7 +129,6 @@ RSpec.describe RemoteMirror, :mailer do expect(git_remote_mirror).to have_received(:new).with( mirror.project.repository.raw, - mirror.remote_name, mirror.url, keep_divergent_refs: true ) diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb index 19694a0a354..6e9404166f3 100644 --- a/spec/services/git/branch_hooks_service_spec.rb +++ b/spec/services/git/branch_hooks_service_spec.rb @@ -134,7 +134,7 @@ RSpec.describe Git::BranchHooksService, :clean_gitlab_redis_shared_state do context 'when usage ping is disabled' do before do - stub_application_setting(usage_ping_enabled: false) + allow(::ServicePing::ServicePingSettings).to receive(:enabled?).and_return(false) end it 'does not track the event' do diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb index 686b204c726..f4a6d1b19e7 100644 --- a/spec/services/projects/update_remote_mirror_service_spec.rb +++ b/spec/services/projects/update_remote_mirror_service_spec.rb @@ -7,8 +7,6 @@ RSpec.describe Projects::UpdateRemoteMirrorService do let_it_be(:remote_project) { create(:forked_project_with_submodules) } let_it_be(:remote_mirror) { create(:remote_mirror, project: project, enabled: true) } - let(:remote_name) { remote_mirror.remote_name } - subject(:service) { described_class.new(project, project.creator) } describe '#execute' do diff --git a/spec/services/service_ping/service_ping_settings_spec.rb b/spec/services/service_ping/service_ping_settings_spec.rb index 8446fee36c5..90a5c6b30eb 100644 --- a/spec/services/service_ping/service_ping_settings_spec.rb +++ b/spec/services/service_ping/service_ping_settings_spec.rb @@ -27,4 +27,20 @@ RSpec.describe ServicePing::ServicePingSettings do end end end + + describe '#enabled?' do + describe 'has the correct enabled' do + it 'when false' do + stub_config_setting(usage_ping_enabled: false) + + expect(described_class.enabled?).to eq(false) + end + + it 'when true' do + stub_config_setting(usage_ping_enabled: true) + + expect(described_class.enabled?).to eq(true) + end + end + end end |