diff options
106 files changed, 751 insertions, 392 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index eac0d2748ae..b08dbdf9888 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -368,6 +368,8 @@ Cop/SidekiqOptionsQueue: Graphql/ResolverType: Enabled: true + Exclude: + - 'app/graphql/resolvers/base_resolver.rb' Include: - 'app/graphql/resolvers/**/*' - 'ee/app/graphql/resolvers/**/*' diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 506c6042d81..290b776b33c 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -32,14 +32,6 @@ Graphql/IDType: - 'app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb' - 'app/graphql/resolvers/user_merge_requests_resolver.rb' -Graphql/ResolverType: - Exclude: - - 'app/graphql/resolvers/base_resolver.rb' - - 'app/graphql/resolvers/ci/jobs_resolver.rb' - - 'app/graphql/resolvers/ci/pipeline_stages_resolver.rb' - - 'app/graphql/resolvers/users/group_count_resolver.rb' - - 'ee/app/graphql/resolvers/vulnerabilities_base_resolver.rb' - Gitlab/PolicyRuleBoolean: Exclude: - 'ee/app/policies/ee/identity_provider_policy.rb' diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js index 4961017b6af..634313f666b 100644 --- a/app/assets/javascripts/boards/boards_util.js +++ b/app/assets/javascripts/boards/boards_util.js @@ -2,20 +2,28 @@ import { sortBy } from 'lodash'; import axios from '~/lib/utils/axios_utils'; import { ListType } from './constants'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import boardsStore from '~/boards/stores/boards_store'; export function getMilestone() { return null; } +export function updateListPosition(listObj) { + const { listType } = listObj; + let { position } = listObj; + if (listType === ListType.closed) { + position = Infinity; + } else if (listType === ListType.backlog) { + position = -Infinity; + } + + return { ...listObj, position }; +} + export function formatBoardLists(lists) { - const formattedLists = lists.nodes.map(list => - boardsStore.updateListPosition({ ...list, doNotFetchIssues: true }), - ); - return formattedLists.reduce((map, list) => { + return lists.nodes.reduce((map, list) => { return { ...map, - [list.id]: list, + [list.id]: updateListPosition(list), }; }, {}); } @@ -85,22 +93,22 @@ export function fullLabelId(label) { export function moveIssueListHelper(issue, fromList, toList) { const updatedIssue = issue; if ( - toList.type === ListType.label && + toList.listType === ListType.label && !updatedIssue.labels.find(label => label.id === toList.label.id) ) { updatedIssue.labels.push(toList.label); } - if (fromList?.label && fromList.type === ListType.label) { + if (fromList?.label && fromList.listType === ListType.label) { updatedIssue.labels = updatedIssue.labels.filter(label => fromList.label.id !== label.id); } if ( - toList.type === ListType.assignee && + toList.listType === ListType.assignee && !updatedIssue.assignees.find(assignee => assignee.id === toList.assignee.id) ) { updatedIssue.assignees.push(toList.assignee); } - if (fromList?.assignee && fromList.type === ListType.assignee) { + if (fromList?.assignee && fromList.listType === ListType.assignee) { updatedIssue.assignees = updatedIssue.assignees.filter( assignee => assignee.id !== fromList.assignee.id, ); @@ -118,6 +126,10 @@ export function getBoardsPath(endpoint, board) { return axios.post(path, { board }); } +export function isListDraggable(list) { + return list.listType !== ListType.backlog && list.listType !== ListType.closed; +} + export default { getMilestone, formatIssue, @@ -125,4 +137,5 @@ export default { fullBoardId, fullLabelId, getBoardsPath, + isListDraggable, }; diff --git a/app/assets/javascripts/boards/components/board_column_new.vue b/app/assets/javascripts/boards/components/board_column_new.vue index 9ae9d4697d9..7839f45c48b 100644 --- a/app/assets/javascripts/boards/components/board_column_new.vue +++ b/app/assets/javascripts/boards/components/board_column_new.vue @@ -2,6 +2,7 @@ import { mapGetters, mapActions, mapState } from 'vuex'; import BoardListHeader from 'ee_else_ce/boards/components/board_list_header_new.vue'; import BoardList from './board_list_new.vue'; +import { isListDraggable } from '../boards_util'; export default { components: { @@ -35,6 +36,9 @@ export default { listIssues() { return this.getIssuesByList(this.list.id); }, + isListDraggable() { + return isListDraggable(this.list); + }, }, watch: { filterParams: { @@ -47,7 +51,6 @@ export default { }, methods: { ...mapActions(['fetchIssuesForList']), - // TODO: Reordering of lists https://gitlab.com/gitlab-org/gitlab/-/issues/280515 }, }; </script> @@ -55,13 +58,12 @@ export default { <template> <div :class="{ - 'is-draggable': !list.preset, - 'is-expandable': list.isExpandable, - 'is-collapsed': !list.isExpanded, - 'board-type-assignee': list.type === 'assignee', + 'is-draggable': isListDraggable, + 'is-collapsed': list.collapsed, + 'board-type-assignee': list.listType === 'assignee', }" :data-id="list.id" - class="board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal" + class="board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal is-expandable" data-qa-selector="board_list" > <div diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index fb8f3014869..b366aa6fdb3 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -103,9 +103,6 @@ export default { :key="list.id" ref="board" :can-admin-list="canAdminList" - :class="{ - 'is-draggable': !list.preset, - }" :list="list" :disabled="disabled" /> diff --git a/app/assets/javascripts/boards/components/board_list_header_new.vue b/app/assets/javascripts/boards/components/board_list_header_new.vue index b7c892e3b52..44eb2aa34c2 100644 --- a/app/assets/javascripts/boards/components/board_list_header_new.vue +++ b/app/assets/javascripts/boards/components/board_list_header_new.vue @@ -9,15 +9,22 @@ import { GlSprintf, GlTooltipDirective, } from '@gitlab/ui'; -import { n__, s__ } from '~/locale'; +import { n__, s__, __ } from '~/locale'; import AccessorUtilities from '../../lib/utils/accessor'; import IssueCount from './issue_count.vue'; import eventHub from '../eventhub'; import sidebarEventHub from '~/sidebar/event_hub'; import { inactiveId, LIST, ListType } from '../constants'; import { isScopedLabel } from '~/lib/utils/common_utils'; +import { isListDraggable } from '~/boards/boards_util'; export default { + i18n: { + newIssue: __('New issue'), + listSettings: __('List settings'), + expand: s__('Boards|Expand'), + collapse: s__('Boards|Collapse'), + }, components: { GlButtonGroup, GlButton, @@ -66,47 +73,47 @@ export default { return Boolean(this.currentUserId); }, listType() { - return this.list.type; + return this.list.listType; }, listAssignee() { return this.list?.assignee?.username || ''; }, listTitle() { - return this.list?.label?.description || this.list.title || ''; + return this.list?.label?.description || this.list?.assignee?.name || this.list.title || ''; }, showListHeaderButton() { return !this.disabled && this.listType !== ListType.closed; }, showMilestoneListDetails() { return ( - this.list.type === ListType.milestone && + this.listType === ListType.milestone && this.list.milestone && - (this.list.isExpanded || !this.isSwimlanesHeader) + (!this.list.collapsed || !this.isSwimlanesHeader) ); }, showAssigneeListDetails() { return ( - this.list.type === ListType.assignee && (this.list.isExpanded || !this.isSwimlanesHeader) + this.listType === ListType.assignee && (!this.list.collapsed || !this.isSwimlanesHeader) ); }, issuesCount() { - return this.list.issuesSize; + return this.list.issuesCount; }, issuesTooltipLabel() { return n__(`%d issue`, `%d issues`, this.issuesCount); }, chevronTooltip() { - return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand'); + return this.list.collapsed ? this.$options.i18n.expand : this.$options.i18n.collapse; }, chevronIcon() { - return this.list.isExpanded ? 'chevron-right' : 'chevron-down'; + return this.list.collapsed ? 'chevron-down' : 'chevron-right'; }, isNewIssueShown() { return this.listType === ListType.backlog || this.showListHeaderButton; }, isSettingsShown() { return ( - this.listType !== ListType.backlog && this.showListHeaderButton && this.list.isExpanded + this.listType !== ListType.backlog && this.showListHeaderButton && !this.list.collapsed ); }, uniqueKey() { @@ -119,6 +126,9 @@ export default { headerStyle() { return { borderTopColor: this.list?.label?.color }; }, + userCanDrag() { + return !this.disabled && isListDraggable(this.list); + }, }, methods: { ...mapActions(['updateList', 'setActiveId']), @@ -137,7 +147,7 @@ export default { eventHub.$emit(`toggle-issue-form-${this.list.id}`); }, toggleExpanded() { - this.list.isExpanded = !this.list.isExpanded; + this.list.collapsed = !this.list.collapsed; if (!this.isLoggedIn) { this.addToLocalStorage(); @@ -151,11 +161,11 @@ export default { }, addToLocalStorage() { if (AccessorUtilities.isLocalStorageAccessSafe()) { - localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded); + localStorage.setItem(`${this.uniqueKey}.expanded`, !this.list.collapsed); } }, updateListFunction() { - this.updateList({ listId: this.list.id, collapsed: !this.list.isExpanded }); + this.updateList({ listId: this.list.id, collapsed: this.list.collapsed }); }, }, }; @@ -165,7 +175,7 @@ export default { <header :class="{ 'has-border': list.label && list.label.color, - 'gl-h-full': !list.isExpanded, + 'gl-h-full': list.collapsed, 'board-inner gl-rounded-top-left-base gl-rounded-top-right-base': isSwimlanesHeader, }" :style="headerStyle" @@ -175,16 +185,15 @@ export default { > <h3 :class="{ - 'user-can-drag': !disabled && !list.preset, - 'gl-py-3 gl-h-full': !list.isExpanded && !isSwimlanesHeader, - 'gl-border-b-0': !list.isExpanded || isSwimlanesHeader, - 'gl-py-2': !list.isExpanded && isSwimlanesHeader, - 'gl-flex-direction-column': !list.isExpanded, + 'user-can-drag': userCanDrag, + 'gl-py-3 gl-h-full': list.collapsed && !isSwimlanesHeader, + 'gl-border-b-0': list.collapsed || isSwimlanesHeader, + 'gl-py-2': list.collapsed && isSwimlanesHeader, + 'gl-flex-direction-column': list.collapsed, }" class="board-title gl-m-0 gl-display-flex gl-align-items-center gl-font-base gl-px-3 js-board-handle" > <gl-button - v-if="list.isExpandable" v-gl-tooltip.hover :aria-label="chevronTooltip" :title="chevronTooltip" @@ -200,8 +209,8 @@ export default { aria-hidden="true" class="milestone-icon" :class="{ - 'gl-mt-3 gl-rotate-90': !list.isExpanded, - 'gl-mr-2': list.isExpanded, + 'gl-mt-3 gl-rotate-90': list.collapsed, + 'gl-mr-2': !list.collapsed, }" > <gl-icon name="timer" /> @@ -209,17 +218,17 @@ export default { <a v-if="showAssigneeListDetails" - :href="list.assignee.path" + :href="list.assignee.webUrl" class="user-avatar-link js-no-trigger" :class="{ - 'gl-mt-3 gl-rotate-90': !list.isExpanded, + 'gl-mt-3 gl-rotate-90': list.collapsed, }" > <img v-gl-tooltip.hover.bottom :title="listAssignee" :alt="list.assignee.name" - :src="list.assignee.avatar" + :src="list.assignee.avatarUrl" class="avatar s20" height="20" width="20" @@ -229,9 +238,9 @@ export default { <div class="board-title-text" :class="{ - 'gl-display-none': !list.isExpanded && isSwimlanesHeader, - 'gl-flex-grow-0 gl-my-3 gl-mx-0': !list.isExpanded, - 'gl-flex-grow-1': list.isExpanded, + 'gl-display-none': list.collapsed && isSwimlanesHeader, + 'gl-flex-grow-0 gl-my-3 gl-mx-0': list.collapsed, + 'gl-flex-grow-1': !list.collapsed, }" > <!-- EE start --> @@ -239,16 +248,16 @@ export default { v-if="listType !== 'label'" v-gl-tooltip.hover :class="{ - 'gl-display-block': !list.isExpanded || listType === 'milestone', + 'gl-display-block': list.collapsed || listType === 'milestone', }" :title="listTitle" class="board-title-main-text gl-text-truncate" > - {{ list.title }} + {{ listTitle }} </span> <span v-if="listType === 'assignee'" - v-show="list.isExpanded" + v-show="!list.collapsed" class="gl-ml-2 gl-font-weight-normal gl-text-gray-500" > @{{ listAssignee }} @@ -260,21 +269,21 @@ export default { :background-color="list.label.color" :description="list.label.description" :scoped="showScopedLabels(list.label)" - :size="!list.isExpanded ? 'sm' : ''" + :size="list.collapsed ? 'sm' : ''" :title="list.label.title" /> </div> <!-- EE start --> <span - v-if="isSwimlanesHeader && !list.isExpanded" + v-if="isSwimlanesHeader && list.collapsed" ref="collapsedInfo" aria-hidden="true" class="board-header-collapsed-info-icon gl-cursor-pointer gl-text-gray-500" > <gl-icon name="information" /> </span> - <gl-tooltip v-if="isSwimlanesHeader && !list.isExpanded" :target="() => $refs.collapsedInfo"> + <gl-tooltip v-if="isSwimlanesHeader && list.collapsed" :target="() => $refs.collapsedInfo"> <div class="gl-font-weight-bold gl-pb-2">{{ collapsedTooltipTitle }}</div> <div v-if="list.maxIssueCount !== 0"> • @@ -296,8 +305,8 @@ export default { <div class="issue-count-badge gl-display-inline-flex gl-pr-0 no-drag gl-text-gray-500" :class="{ - 'gl-display-none!': !list.isExpanded && isSwimlanesHeader, - 'gl-p-0': !list.isExpanded, + 'gl-display-none!': list.collapsed && isSwimlanesHeader, + 'gl-p-0': list.collapsed, }" > <span class="gl-display-inline-flex"> @@ -323,11 +332,11 @@ export default { > <gl-button v-if="isNewIssueShown" - v-show="list.isExpanded" + v-show="!list.collapsed" ref="newIssueBtn" v-gl-tooltip.hover - :aria-label="__('New issue')" - :title="__('New issue')" + :aria-label="$options.i18n.newIssue" + :title="$options.i18n.newIssue" class="issue-count-badge-add-button no-drag" icon="plus" @click="showNewIssueForm" @@ -337,13 +346,13 @@ export default { v-if="isSettingsShown" ref="settingsBtn" v-gl-tooltip.hover - :aria-label="__('List settings')" + :aria-label="$options.i18n.listSettings" class="no-drag js-board-settings-button" - :title="__('List settings')" + :title="$options.i18n.listSettings" icon="settings" @click="openSidebarSettings" /> - <gl-tooltip :target="() => $refs.settingsBtn">{{ __('List settings') }}</gl-tooltip> + <gl-tooltip :target="() => $refs.settingsBtn">{{ $options.i18n.listSettings }}</gl-tooltip> </gl-button-group> </h3> </header> diff --git a/app/assets/javascripts/boards/components/board_list_new.vue b/app/assets/javascripts/boards/components/board_list_new.vue index 291abc9849b..ada80e6913c 100644 --- a/app/assets/javascripts/boards/components/board_list_new.vue +++ b/app/assets/javascripts/boards/components/board_list_new.vue @@ -12,6 +12,11 @@ import { sprintf, __ } from '~/locale'; export default { name: 'BoardList', + i18n: { + loadingIssues: __('Loading issues'), + loadingMoreissues: __('Loading more issues'), + showingAllIssues: __('Showing all issues'), + }, components: { BoardCard, BoardNewIssue, @@ -49,11 +54,11 @@ export default { paginatedIssueText() { return sprintf(__('Showing %{pageSize} of %{total} issues'), { pageSize: this.issues.length, - total: this.list.issuesSize, + total: this.list.issuesCount, }); }, issuesSizeExceedsMax() { - return this.list.maxIssueCount > 0 && this.list.issuesSize > this.list.maxIssueCount; + return this.list.maxIssueCount > 0 && this.list.issuesCount > this.list.maxIssueCount; }, hasNextPage() { return this.pageInfoByListId[this.list.id].hasNextPage; @@ -61,10 +66,16 @@ export default { loading() { return this.listsFlags[this.list.id]?.isLoading; }, + loadingMore() { + return this.listsFlags[this.list.id]?.isLoadingMore; + }, listRef() { // When list is draggable, the reference to the list needs to be accessed differently return this.canAdminList ? this.$refs.list.$el : this.$refs.list; }, + showingAllIssues() { + return this.issues.length === this.list.issuesCount; + }, treeRootWrapper() { return this.canAdminList ? Draggable : 'ul'; }, @@ -72,7 +83,7 @@ export default { const options = { ...defaultSortableConfig, fallbackOnBody: false, - group: 'boards-list', + group: 'board-list', tag: 'ul', 'ghost-class': 'board-card-drag-active', 'data-list-id': this.list.id, @@ -85,7 +96,6 @@ export default { watch: { filters: { handler() { - this.list.loadingMore = false; this.listRef.scrollTop = 0; }, deep: true, @@ -124,13 +134,7 @@ export default { this.listRef.scrollTop = 0; }, loadNextPage() { - const loadingDone = () => { - this.list.loadingMore = false; - }; - this.list.loadingMore = true; - this.fetchIssuesForList({ listId: this.list.id, fetchNext: true }) - .then(loadingDone) - .catch(loadingDone); + this.fetchIssuesForList({ listId: this.list.id, fetchNext: true }); }, toggleForm() { this.showIssueForm = !this.showIssueForm; @@ -138,7 +142,7 @@ export default { onScroll() { window.requestAnimationFrame(() => { if ( - !this.list.loadingMore && + !this.loadingMore && this.scrollTop() > this.scrollHeight() - this.scrollOffset && this.hasNextPage ) { @@ -198,26 +202,26 @@ export default { <template> <div - v-show="list.isExpanded" + v-show="!list.collapsed" class="board-list-component gl-relative gl-h-full gl-display-flex gl-flex-direction-column" data-qa-selector="board_list_cards_area" > <div v-if="loading" class="gl-mt-4 gl-text-center" - :aria-label="__('Loading issues')" + :aria-label="$options.i18n.loadingIssues" data-testid="board_list_loading" > <gl-loading-icon /> </div> - <board-new-issue v-if="list.type !== 'closed' && showIssueForm" :list="list" /> + <board-new-issue v-if="list.listType !== 'closed' && showIssueForm" :list="list" /> <component :is="treeRootWrapper" v-show="!loading" ref="list" v-bind="treeRootOptions" :data-board="list.id" - :data-board-type="list.type" + :data-board-type="list.listType" :class="{ 'bg-danger-100': issuesSizeExceedsMax }" class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-2 js-board-list" data-testid="tree-root-wrapper" @@ -234,8 +238,8 @@ export default { :disabled="disabled" /> <li v-if="showCount" class="board-list-count gl-text-center" data-issue-id="-1"> - <gl-loading-icon v-show="list.loadingMore" label="Loading more issues" /> - <span v-if="issues.length === list.issuesSize">{{ __('Showing all issues') }}</span> + <gl-loading-icon v-if="loadingMore" :label="$options.i18n.loadingMoreissues" /> + <span v-if="showingAllIssues">{{ $options.i18n.showingAllIssues }}</span> <span v-else>{{ paginatedIssueText }}</span> </li> </component> diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue index 80070b25bd0..60db8fefe82 100644 --- a/app/assets/javascripts/boards/components/board_settings_sidebar.vue +++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue @@ -53,7 +53,7 @@ export default { return this.activeList.label; }, boardListType() { - return this.activeList.type || null; + return this.activeList.type || this.activeList.listType || null; }, listTypeTitle() { return this.$options.labelListText; diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue index 67e8a32dbe2..ddd20ff281c 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.vue +++ b/app/assets/javascripts/boards/components/issue_card_inner.vue @@ -10,6 +10,7 @@ import IssueDueDate from './issue_due_date.vue'; import IssueTimeEstimate from './issue_time_estimate.vue'; import boardsStore from '../stores/boards_store'; import { isScopedLabel } from '~/lib/utils/common_utils'; +import { ListType } from '../constants'; export default { components: { @@ -122,7 +123,13 @@ export default { return true; }, isNonListLabel(label) { - return label.id && !(this.list.type === 'label' && this.list.title === label.title); + return ( + label.id && + !( + (this.list.type || this.list.listType) === ListType.label && + this.list.title === label.title + ) + ); }, filterByLabel(label) { if (!this.updateFilters) return; diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue index f90fe582566..9c90938fc52 100644 --- a/app/assets/javascripts/boards/components/project_select.vue +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -7,6 +7,7 @@ import eventHub from '../eventhub'; import Api from '../../api'; import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants'; import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; +import { ListType } from '../constants'; export default { name: 'BoardProjectSelect', @@ -53,7 +54,7 @@ export default { this.loading = true; const additionalAttrs = {}; - if (this.list.type && this.list.type !== 'backlog') { + if ((this.list.type || this.list.listType) !== ListType.backlog) { additionalAttrs.min_access_level = featureAccessLevel.EVERYONE; } diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index 1a745eef482..b4c646b0223 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -12,7 +12,6 @@ import { formatListsPageInfo, formatIssue, } from '../boards_util'; -import boardStore from '~/boards/stores/boards_store'; import createFlash from '~/flash'; import { __ } from '~/locale'; import updateAssigneesMutation from '~/vue_shared/components/sidebar/queries/updateAssignees.mutation.graphql'; @@ -119,11 +118,7 @@ export default { }, addList: ({ commit }, list) => { - // Temporarily using positioning logic from boardStore - commit( - types.RECEIVE_ADD_LIST_SUCCESS, - boardStore.updateListPosition({ ...list, doNotFetchIssues: true }), - ); + commit(types.RECEIVE_ADD_LIST_SUCCESS, list); }, fetchLabels: ({ state, commit }, searchTerm) => { diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js index 1860cc442a5..b58564e358e 100644 --- a/app/assets/javascripts/boards/stores/mutations.js +++ b/app/assets/javascripts/boards/stores/mutations.js @@ -13,7 +13,7 @@ const notImplemented = () => { export const removeIssueFromList = ({ state, listId, issueId }) => { Vue.set(state.issuesByListId, listId, pull(state.issuesByListId[listId], issueId)); const list = state.boardLists[listId]; - Vue.set(state.boardLists, listId, { ...list, issuesSize: list.issuesSize - 1 }); + Vue.set(state.boardLists, listId, { ...list, issuesCount: list.issuesCount - 1 }); }; export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atIndex }) => { @@ -27,7 +27,7 @@ export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfter listIssues.splice(newIndex, 0, issueId); Vue.set(state.issuesByListId, listId, listIssues); const list = state.boardLists[listId]; - Vue.set(state.boardLists, listId, { ...list, issuesSize: list.issuesSize + 1 }); + Vue.set(state.boardLists, listId, { ...list, issuesCount: list.issuesCount + 1 }); }; export default { diff --git a/app/assets/javascripts/search/sidebar/components/app.vue b/app/assets/javascripts/search/sidebar/components/app.vue index aa11b2025f2..e233d18b716 100644 --- a/app/assets/javascripts/search/sidebar/components/app.vue +++ b/app/assets/javascripts/search/sidebar/components/app.vue @@ -26,7 +26,7 @@ export default { <template> <form - class="gl-display-flex gl-flex-direction-column col-md-3 gl-mr-4 gl-mb-6 gl-mt-5" + class="search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4 gl-mb-6 gl-mt-5" @submit.prevent="applyQuery" > <status-filter /> diff --git a/app/assets/javascripts/terraform/components/states_table.vue b/app/assets/javascripts/terraform/components/states_table.vue index cf9c6e19094..d0d49233334 100644 --- a/app/assets/javascripts/terraform/components/states_table.vue +++ b/app/assets/javascripts/terraform/components/states_table.vue @@ -1,14 +1,18 @@ <script> -import { GlBadge, GlIcon, GlSprintf, GlTable, GlTooltip } from '@gitlab/ui'; +import { GlBadge, GlIcon, GlLink, GlSprintf, GlTable, GlTooltip } from '@gitlab/ui'; import { s__ } from '~/locale'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import CiBadge from '~/vue_shared/components/ci_badge_link.vue'; import StateActions from './states_table_actions.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; export default { components: { + CiBadge, GlBadge, GlIcon, + GlLink, GlSprintf, GlTable, GlTooltip, @@ -32,20 +36,24 @@ export default { const columns = [ { key: 'name', - thClass: 'gl-display-none', + label: this.$options.i18n.name, + }, + { + key: 'pipeline', + label: this.$options.i18n.pipeline, }, { key: 'updated', - thClass: 'gl-display-none', - tdClass: 'gl-text-right', + label: this.$options.i18n.details, }, ]; if (this.terraformAdmin) { columns.push({ key: 'actions', - thClass: 'gl-display-none', - tdClass: 'gl-w-10', + label: this.$options.i18n.actions, + thClass: 'gl-w-12', + tdClass: 'gl-text-right', }); } @@ -53,8 +61,13 @@ export default { }, }, i18n: { + actions: s__('Terraform|Actions'), + details: s__('Terraform|Details'), + jobStatus: s__('Terraform|Job status'), locked: s__('Terraform|Locked'), lockedByUser: s__('Terraform|Locked by %{user} %{timeAgo}'), + name: s__('Terraform|Name'), + pipeline: s__('Terraform|Pipeline'), unknownUser: s__('Terraform|Unknown User'), updatedUser: s__('Terraform|%{user} updated %{timeAgo}'), }, @@ -65,6 +78,21 @@ export default { lockedByUserName(item) { return item.lockedByUser?.name || this.$options.i18n.unknownUser; }, + pipelineDetailedStatus(item) { + return item.latestVersion?.job?.detailedStatus; + }, + pipelineID(item) { + let id = item.latestVersion?.job?.pipeline?.id; + + if (id) { + id = getIdFromGraphQLId(id); + } + + return id; + }, + pipelinePath(item) { + return item.latestVersion?.job?.pipeline?.path; + }, updatedTime(item) { return item.latestVersion?.updatedAt || item.updatedAt; }, @@ -73,9 +101,18 @@ export default { </script> <template> - <gl-table :items="states" :fields="fields" data-testid="terraform-states-table"> + <gl-table + :items="states" + :fields="fields" + data-testid="terraform-states-table" + fixed + stacked="md" + > <template #cell(name)="{ item }"> - <div class="gl-display-flex align-items-center" data-testid="terraform-states-table-name"> + <div + class="gl-display-flex align-items-center gl-justify-content-end gl-justify-content-md-start" + data-testid="terraform-states-table-name" + > <p class="gl-font-weight-bold gl-m-0 gl-text-gray-900"> {{ item.name }} </p> @@ -105,6 +142,34 @@ export default { </div> </template> + <template #cell(pipeline)="{ item }"> + <div data-testid="terraform-states-table-pipeline" class="gl-min-h-7"> + <gl-link v-if="pipelineID(item)" :href="pipelinePath(item)"> + #{{ pipelineID(item) }} + </gl-link> + + <div + v-if="pipelineDetailedStatus(item)" + :id="`terraformJobStatusContainer${item.name}`" + class="gl-my-2" + > + <ci-badge + :id="`terraformJobStatus${item.name}`" + :status="pipelineDetailedStatus(item)" + class="gl-py-1" + /> + + <gl-tooltip + :container="`terraformJobStatusContainer${item.name}`" + :target="`terraformJobStatus${item.name}`" + placement="right" + > + {{ $options.i18n.jobStatus }} + </gl-tooltip> + </div> + </div> + </template> + <template #cell(updated)="{ item }"> <p class="gl-m-0" data-testid="terraform-states-table-updated"> <gl-sprintf :message="$options.i18n.updatedUser"> diff --git a/app/assets/javascripts/terraform/graphql/fragments/state_version.fragment.graphql b/app/assets/javascripts/terraform/graphql/fragments/state_version.fragment.graphql index bd139b05b99..70ba5c960be 100644 --- a/app/assets/javascripts/terraform/graphql/fragments/state_version.fragment.graphql +++ b/app/assets/javascripts/terraform/graphql/fragments/state_version.fragment.graphql @@ -8,4 +8,19 @@ fragment StateVersion on TerraformStateVersion { createdByUser { ...User } + + job { + detailedStatus { + detailsPath + group + icon + label + text + } + + pipeline { + id + path + } + } } diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue index 1b7e51b7d02..f388a468fd2 100644 --- a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue +++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue @@ -20,6 +20,7 @@ import CiIcon from './ci_icon.vue'; * - Pipeline show view - header * - Job show view - header * - MR widget + * - Terraform table */ export default { diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 502a1881fd2..cd99c667001 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -1,5 +1,7 @@ $search-dropdown-max-height: 400px; $search-avatar-size: 16px; +$search-sidebar-min-width: 240px; +$search-sidebar-max-width: 300px; .search-results { .search-result-row { @@ -17,6 +19,13 @@ $search-avatar-size: 16px; } } +.search-sidebar { + @include media-breakpoint-up(md) { + min-width: $search-sidebar-min-width; + max-width: $search-sidebar-max-width; + } +} + .search form:hover, .file-finder-input:hover, .issuable-search-form:hover, diff --git a/app/controllers/concerns/dependency_proxy/auth.rb b/app/controllers/concerns/dependency_proxy/auth.rb index 22618ca6366..1276feedba6 100644 --- a/app/controllers/concerns/dependency_proxy/auth.rb +++ b/app/controllers/concerns/dependency_proxy/auth.rb @@ -24,7 +24,7 @@ module DependencyProxy private def dependency_proxy_for_private_groups? - Feature.enabled?(:dependency_proxy_for_private_groups, default_enabled: false) + Feature.enabled?(:dependency_proxy_for_private_groups, default_enabled: true) end def request_bearer_token! diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb index 37c0f80310c..56c3b398949 100644 --- a/app/graphql/mutations/snippets/create.rb +++ b/app/graphql/mutations/snippets/create.rb @@ -4,7 +4,8 @@ module Mutations module Snippets class Create < BaseMutation include SpammableMutationFields - include ResolvesProject + + authorize :create_snippet graphql_name 'CreateSnippet' @@ -37,17 +38,15 @@ module Mutations description: 'Actions to perform over the snippet repository and blobs', required: false - def resolve(args) - project_path = args.delete(:project_path) - + def resolve(project_path: nil, **args) if project_path.present? - project = find_project!(project_path: project_path) - elsif !can_create_personal_snippet? - raise_resource_not_available_error! + project = authorized_find!(project_path) + else + authorize!(:global) end service_response = ::Snippets::CreateService.new(project, - context[:current_user], + current_user, create_params(args)).execute snippet = service_response.payload[:snippet] @@ -67,20 +66,8 @@ module Mutations private - def find_project!(project_path:) - authorized_find!(full_path: project_path) - end - - def find_object(full_path:) - resolve_project(full_path: full_path) - end - - def authorized_resource?(project) - Ability.allowed?(context[:current_user], :create_snippet, project) - end - - def can_create_personal_snippet? - Ability.allowed?(context[:current_user], :create_snippet) + def find_object(full_path) + Project.find_by_full_path(full_path) end def create_params(args) diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb index bda43f243c1..6df1ad6d8b9 100644 --- a/app/graphql/mutations/snippets/update.rb +++ b/app/graphql/mutations/snippets/update.rb @@ -27,11 +27,11 @@ module Mutations description: 'Actions to perform over the snippet repository and blobs', required: false - def resolve(args) - snippet = authorized_find!(id: args.delete(:id)) + def resolve(id:, **args) + snippet = authorized_find!(id: id) result = ::Snippets::UpdateService.new(snippet.project, - context[:current_user], + current_user, update_params(args)).execute(snippet) snippet = result.payload[:snippet] diff --git a/app/graphql/resolvers/ci/jobs_resolver.rb b/app/graphql/resolvers/ci/jobs_resolver.rb index 8a9ae42b375..2c4911748a5 100644 --- a/app/graphql/resolvers/ci/jobs_resolver.rb +++ b/app/graphql/resolvers/ci/jobs_resolver.rb @@ -5,6 +5,8 @@ module Resolvers class JobsResolver < BaseResolver alias_method :pipeline, :object + type ::Types::Ci::JobType.connection_type, null: true + argument :security_report_types, [Types::Security::ReportTypeEnum], required: false, description: 'Filter jobs by the type of security report they produce' diff --git a/app/graphql/resolvers/ci/pipeline_stages_resolver.rb b/app/graphql/resolvers/ci/pipeline_stages_resolver.rb index f9817d8b97b..98170e0cd2e 100644 --- a/app/graphql/resolvers/ci/pipeline_stages_resolver.rb +++ b/app/graphql/resolvers/ci/pipeline_stages_resolver.rb @@ -5,6 +5,9 @@ module Resolvers class PipelineStagesResolver < BaseResolver include LooksAhead + type Types::Ci::StageType.connection_type, null: true + extras [:lookahead] + alias_method :pipeline, :object def resolve_with_lookahead diff --git a/app/graphql/resolvers/design_management/versions_resolver.rb b/app/graphql/resolvers/design_management/versions_resolver.rb index 23858c8e991..3c718a631db 100644 --- a/app/graphql/resolvers/design_management/versions_resolver.rb +++ b/app/graphql/resolvers/design_management/versions_resolver.rb @@ -9,6 +9,8 @@ module Resolvers VersionID = ::Types::GlobalIDType[::DesignManagement::Version] + extras [:parent] + argument :earlier_or_equal_to_sha, GraphQL::STRING_TYPE, as: :sha, required: false, diff --git a/app/graphql/resolvers/issue_status_counts_resolver.rb b/app/graphql/resolvers/issue_status_counts_resolver.rb index 5d0d5693244..58cff559d0d 100644 --- a/app/graphql/resolvers/issue_status_counts_resolver.rb +++ b/app/graphql/resolvers/issue_status_counts_resolver.rb @@ -6,6 +6,8 @@ module Resolvers type Types::IssueStatusCountsType, null: true + extras [:lookahead] + def continue_issue_resolve(parent, finder, **args) finder.parent_param = parent apply_lookahead(Gitlab::IssuablesCountForState.new(finder, parent)) diff --git a/app/graphql/resolvers/users/group_count_resolver.rb b/app/graphql/resolvers/users/group_count_resolver.rb index 5033c26554a..ebfe594d31d 100644 --- a/app/graphql/resolvers/users/group_count_resolver.rb +++ b/app/graphql/resolvers/users/group_count_resolver.rb @@ -3,6 +3,8 @@ module Resolvers module Users class GroupCountResolver < BaseResolver + type GraphQL::INT_TYPE, null: true + alias_method :user, :object def resolve(**args) diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb index 7d736db137a..84467c8ad29 100644 --- a/app/graphql/types/ci/job_type.rb +++ b/app/graphql/types/ci/job_type.rb @@ -5,8 +5,9 @@ module Types # rubocop: disable Graphql/AuthorizeTypes class JobType < BaseObject graphql_name 'CiJob' + authorize :read_build - field :pipeline, Types::Ci::PipelineType, null: false, + field :pipeline, Types::Ci::PipelineType, null: true, description: 'Pipeline the job belongs to' field :name, GraphQL::STRING_TYPE, null: true, description: 'Name of the job' diff --git a/app/graphql/types/terraform/state_type.rb b/app/graphql/types/terraform/state_type.rb index f1bc66bba78..d97e673bf31 100644 --- a/app/graphql/types/terraform/state_type.rb +++ b/app/graphql/types/terraform/state_type.rb @@ -19,7 +19,6 @@ module Types field :locked_by_user, Types::UserType, null: true, - authorize: :read_user, description: 'The user currently holding a lock on the Terraform state' field :locked_at, Types::TimeType, diff --git a/app/graphql/types/terraform/state_version_type.rb b/app/graphql/types/terraform/state_version_type.rb index a681358001b..a3af5c876ca 100644 --- a/app/graphql/types/terraform/state_version_type.rb +++ b/app/graphql/types/terraform/state_version_type.rb @@ -15,7 +15,6 @@ module Types field :created_by_user, Types::UserType, null: true, - authorize: :read_user, description: 'The user that created this version' field :download_path, GraphQL::STRING_TYPE, @@ -24,7 +23,6 @@ module Types field :job, Types::Ci::JobType, null: true, - authorize: :read_build, description: 'The job that created this version' field :serial, GraphQL::INT_TYPE, diff --git a/app/models/project.rb b/app/models/project.rb index 80f4137db14..de4a3cf6bff 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2495,7 +2495,7 @@ class Project < ApplicationRecord end def service_desk_custom_address_enabled? - ::Gitlab::ServiceDeskEmail.enabled? && ::Feature.enabled?(:service_desk_custom_address, self) + ::Gitlab::ServiceDeskEmail.enabled? && ::Feature.enabled?(:service_desk_custom_address, self, default_enabled: true) end def root_namespace diff --git a/app/services/service_desk_settings/update_service.rb b/app/services/service_desk_settings/update_service.rb index c837b75f439..32d1c5c1c87 100644 --- a/app/services/service_desk_settings/update_service.rb +++ b/app/services/service_desk_settings/update_service.rb @@ -5,7 +5,7 @@ module ServiceDeskSettings def execute settings = ServiceDeskSetting.safe_find_or_create_by!(project_id: project.id) - unless ::Feature.enabled?(:service_desk_custom_address, project) + unless ::Feature.enabled?(:service_desk_custom_address, project, default_enabled: true) params.delete(:project_key) end diff --git a/app/views/groups/dependency_proxies/show.html.haml b/app/views/groups/dependency_proxies/show.html.haml index abbfb2d3b91..2ecf92e0769 100644 --- a/app/views/groups/dependency_proxies/show.html.haml +++ b/app/views/groups/dependency_proxies/show.html.haml @@ -7,7 +7,7 @@ - link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('user/packages/dependency_proxy/index') } = _('Create a local proxy for storing frequently used upstream images. %{link_start}Learn more%{link_end} about dependency proxies.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe } -- if Feature.enabled?(:dependency_proxy_for_private_groups, default_enabled: false) || @group.public? +- if Feature.enabled?(:dependency_proxy_for_private_groups, default_enabled: true) || @group.public? - if can?(current_user, :admin_dependency_proxy, @group) = form_for(@dependency_proxy, method: :put, url: group_dependency_proxy_path(@group)) do |f| .form-group diff --git a/app/views/projects/terraform/index.html.haml b/app/views/projects/terraform/index.html.haml index d8196649b38..21a4fe5eae6 100644 --- a/app/views/projects/terraform/index.html.haml +++ b/app/views/projects/terraform/index.html.haml @@ -1,3 +1,5 @@ +- add_page_specific_style 'page_bundles/ci_status' + - breadcrumb_title _('Terraform') - page_title _('Terraform') diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index e0f59b84493..80d0253d273 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -1,8 +1,10 @@ +- search_bar_classes = 'search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4' + - if @search_objects.to_a.empty? .gl-display-md-flex - if %w(issues merge_requests).include?(@scope) - #js-search-sidebar.gl-display-flex.gl-flex-direction-column.col-md-3.gl-mr-4{ } - .gl-w-full + #js-search-sidebar{ class: search_bar_classes } + .gl-w-full.gl-flex-fill-1.gl-overflow-x-hidden = render partial: "search/results/empty" = render_if_exists 'shared/promotions/promote_advanced_search' - else @@ -11,8 +13,8 @@ .results.gl-display-md-flex.gl-mt-3 - if %w(issues merge_requests).include?(@scope) - #js-search-sidebar.gl-display-flex.gl-flex-direction-column.col-md-3.gl-mr-4{ } - .gl-w-full + #js-search-sidebar{ class: search_bar_classes } + .gl-w-full.gl-flex-fill-1.gl-overflow-x-hidden - if @scope == 'commits' %ul.content-list.commit-list = render partial: "search/results/commit", collection: @search_objects diff --git a/changelogs/unreleased/276777-dep-proxy-feature-flag-removal.yml b/changelogs/unreleased/276777-dep-proxy-feature-flag-removal.yml new file mode 100644 index 00000000000..0edfe42301a --- /dev/null +++ b/changelogs/unreleased/276777-dep-proxy-feature-flag-removal.yml @@ -0,0 +1,5 @@ +--- +title: Dependency Proxy for private groups and Dependency Proxy authentication +merge_request: 49519 +author: +type: added diff --git a/changelogs/unreleased/281039-sidebar-wdith-on-large-screens.yml b/changelogs/unreleased/281039-sidebar-wdith-on-large-screens.yml new file mode 100644 index 00000000000..421f90c2843 --- /dev/null +++ b/changelogs/unreleased/281039-sidebar-wdith-on-large-screens.yml @@ -0,0 +1,5 @@ +--- +title: Global Search - Fix Sidebar Whitespace +merge_request: 48832 +author: +type: changed diff --git a/changelogs/unreleased/emilyring-terraform-pipeline.yml b/changelogs/unreleased/emilyring-terraform-pipeline.yml new file mode 100644 index 00000000000..07d0c27bad1 --- /dev/null +++ b/changelogs/unreleased/emilyring-terraform-pipeline.yml @@ -0,0 +1,5 @@ +--- +title: Add pipeline information to Terraform state list +merge_request: 49042 +author: +type: added diff --git a/changelogs/unreleased/sdesk_by_default.yml b/changelogs/unreleased/sdesk_by_default.yml new file mode 100644 index 00000000000..c3ae6cb4e49 --- /dev/null +++ b/changelogs/unreleased/sdesk_by_default.yml @@ -0,0 +1,5 @@ +--- +title: Allow to configure custom service desk email address suffix +merge_request: 49932 +author: +type: added diff --git a/config/feature_flags/development/dependency_proxy_for_private_groups.yml b/config/feature_flags/development/dependency_proxy_for_private_groups.yml index 60dc1b6f928..0bc795ba02e 100644 --- a/config/feature_flags/development/dependency_proxy_for_private_groups.yml +++ b/config/feature_flags/development/dependency_proxy_for_private_groups.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/276777 milestone: '13.7' type: development group: group::package -default_enabled: false +default_enabled: true diff --git a/config/feature_flags/development/service_desk_custom_address.yml b/config/feature_flags/development/service_desk_custom_address.yml index 9897347d733..e7db2f10e2f 100644 --- a/config/feature_flags/development/service_desk_custom_address.yml +++ b/config/feature_flags/development/service_desk_custom_address.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/284656 milestone: type: development group: group::certify -default_enabled: false +default_enabled: true diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 66f167f6d7b..44a5d69dbde 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -304,6 +304,8 @@ - 1 - - requirements_management_process_requirements_reports - 1 +- - security_auto_fix + - 1 - - security_scans - 2 - - self_monitoring_project_create diff --git a/doc/administration/compliance.md b/doc/administration/compliance.md index 974ca05e68a..0ab7cf72afe 100644 --- a/doc/administration/compliance.md +++ b/doc/administration/compliance.md @@ -15,7 +15,7 @@ GitLab’s [security features](../security/README.md) may also help you meet rel |**[Restrict SSH Keys](../security/ssh_keys_restrictions.md)**<br>Control the technology and key length of SSH keys used to access GitLab|Core+|| |**[Granular user roles and flexible permissions](../user/permissions.md)**<br>Manage access and permissions with five different user roles and settings for external users. Set permissions according to people's role, rather than either read or write access to a repository. Don't share the source code with people that only need access to the issue tracker.|Core+|✓| |**[Enforce TOS acceptance](../user/admin_area/settings/terms.md)**<br>Enforce your users accepting new terms of service by blocking GitLab traffic.|Core+|| -|**[Email all users of a project, group, or entire server](../user/admin_area/settings/terms.md)**<br>An admin can email groups of users based on project or group membership, or email everyone using the GitLab instance. This is great for scheduled maintenance or upgrades.|Starter+|| +|**[Email all users of a project, group, or entire server](../tools/email.md)**<br>An admin can email groups of users based on project or group membership, or email everyone using the GitLab instance. This is great for scheduled maintenance or upgrades.|Starter+|| |**[Omnibus package supports log forwarding](https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-forwarding)**<br>Forward your logs to a central system.|Starter+|| |**[Lock project membership to group](../user/group/index.md#member-lock)**<br>Group owners can prevent new members from being added to projects within a group.|Starter+|✓| |**[LDAP group sync](auth/ldap/index.md#group-sync)**<br>GitLab Enterprise Edition gives admins the ability to automatically sync groups and manage SSH keys, permissions, and authentication, so you can focus on building your product, not configuring your tools.|Starter+|| diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md index 23e7942fdad..a7756e46353 100644 --- a/doc/administration/instance_limits.md +++ b/doc/administration/instance_limits.md @@ -567,12 +567,12 @@ More information can be found in the [Push event activities limit and bulk push On GitLab.com, the maximum file size for a package that's uploaded to the [GitLab Package Registry](../user/packages/package_registry/index.md) varies by format: -- Conan: 3GB +- Conan: 5GB - Generic: 5GB -- Maven: 3GB -- NPM: 500MB -- NuGet: 500MB -- PyPI: 3GB +- Maven: 5GB +- NPM: 5GB +- NuGet: 5GB +- PyPI: 5GB To set this limit on a self-managed installation, run the following in the [GitLab Rails console](operations/rails_console.md#starting-a-rails-console-session): diff --git a/doc/administration/packages/dependency_proxy.md b/doc/administration/packages/dependency_proxy.md index 9c22a093b2b..9e315722c24 100644 --- a/doc/administration/packages/dependency_proxy.md +++ b/doc/administration/packages/dependency_proxy.md @@ -161,3 +161,22 @@ This section describes the earlier configuration format. ``` 1. [Restart GitLab](../restart_gitlab.md#installations-from-source "How to restart GitLab") for the changes to take effect. + +## Disabling Authentication + +Authentication was introduced in 13.7 as part of [enabling private groups to use the +Dependency Proxy](https://gitlab.com/gitlab-org/gitlab/-/issues/11582). If you +previously used the Dependency Proxy without authentication and need to disable +this feature while you update your workflow to [authenticate with the Dependency +Proxy](../../user/packages/dependency_proxy/index.md#authenticate-with-the-dependency-proxy), +the following commands can be issued in a Rails console: + +```ruby +# Disable the authentication +Feature.disable(:dependency_proxy_for_private_groups) + +# Re-enable the authentication +Feature.enable(:dependency_proxy_for_private_groups) +``` + +The ability to disable this feature will be [removed in 13.9](https://gitlab.com/gitlab-org/gitlab/-/issues/276777). diff --git a/doc/api/access_requests.md b/doc/api/access_requests.md index 6802cb75953..db68c140ae6 100644 --- a/doc/api/access_requests.md +++ b/doc/api/access_requests.md @@ -1,6 +1,6 @@ --- -stage: Create -group: Source Code +stage: Manage +group: Access 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" type: reference, api --- @@ -11,9 +11,11 @@ type: reference, api ## Valid access levels - The access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized: +The access levels are defined in the `Gitlab::Access` module, and the +following levels are recognized: - No access (`0`) +- Minimal access (`5`) ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220203) in GitLab 13.5.) - Guest (`10`) - Reporter (`20`) - Developer (`30`) diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 388d9b5461f..d86708e11fd 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -2498,7 +2498,7 @@ type CiJob { """ Pipeline the job belongs to """ - pipeline: Pipeline! + pipeline: Pipeline """ Schedule for the build diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index df10ade69a7..9205dccd6ab 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -6737,13 +6737,9 @@ ], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Pipeline", - "ofType": null - } + "kind": "OBJECT", + "name": "Pipeline", + "ofType": null }, "isDeprecated": false, "deprecationReason": null diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index b32d34b68f9..556ded84621 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -430,7 +430,7 @@ Represents the total number of issues and their weights for a particular day. | `detailedStatus` | DetailedStatus | Detailed status of the job | | `name` | String | Name of the job | | `needs` | CiJobConnection | Builds that must complete before the jobs run | -| `pipeline` | Pipeline! | Pipeline the job belongs to | +| `pipeline` | Pipeline | Pipeline the job belongs to | | `scheduledAt` | Time | Schedule for the build | ### CiJobArtifact diff --git a/doc/api/invitations.md b/doc/api/invitations.md index e5663f6efe1..0891a343cce 100644 --- a/doc/api/invitations.md +++ b/doc/api/invitations.md @@ -15,6 +15,7 @@ To send an invitation, you must have access to the project or group you are send levels are defined in the `Gitlab::Access` module. Currently, these levels are valid: - No access (`0`) +- Minimal access (`5`) ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220203) in GitLab 13.5.) - Guest (`10`) - Reporter (`20`) - Developer (`30`) diff --git a/doc/api/members.md b/doc/api/members.md index d526efffda4..05914b50cd7 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -1,6 +1,6 @@ --- -stage: none -group: unassigned +stage: Manage +group: Access 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 --- @@ -11,6 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w The access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized: - No access (`0`) +- Minimal access (`5`) ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220203) in GitLab 13.5.) - Guest (`10`) - Reporter (`20`) - Developer (`30`) diff --git a/doc/ci/test_cases/index.md b/doc/ci/test_cases/index.md index 28e373241f3..6385b1638ff 100644 --- a/doc/ci/test_cases/index.md +++ b/doc/ci/test_cases/index.md @@ -8,12 +8,8 @@ type: reference # Test Cases **(ULTIMATE)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233479) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.6. -> - It's [deployed behind a feature flag](../../user/feature_flags.md), enabled by default. -> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/241983) on GitLab 13.6. -> - It's enabled on GitLab.com. -> - It's recommended for production use. -> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-test-cases). **(ULTIMATE ONLY)** +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233479) in GitLab 13.6. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/241983) in GitLab 13.7. Test cases in GitLab can help your teams create testing scenarios in their existing development platform. @@ -22,6 +18,8 @@ use external test planning tools, which require additional overhead, context swi ## Create a test case +Users with Reporter or higher [permissions](../../user/permissions.md) can create test cases. + To create a test case in a GitLab project: 1. Navigate to **CI/CD > Test Cases**. @@ -34,6 +32,8 @@ To create a test case in a GitLab project: You can view all test cases in the project in the Test Cases list. Filter the issue list with a search query, including labels or the test case's title. +Users with Guest or higher [permissions](../../user/permissions.md) can view test cases. + ![Test case list page](img/test_case_list_v13_6.png) To view a test case: @@ -43,10 +43,27 @@ To view a test case: ![An example test case page](img/test_case_show_v13_6.png) +## Edit a test case + +You can edit a test case's title and description. + +Users with Reporter or higher [permissions](../../user/permissions.md) can edit test cases. +Users demoted to the Guest role can continue to edit the test cases they created +when they were in the higher role. + +To edit a test case: + +1. [View a test case](#view-a-test-case). +1. Select **Edit title and description** (**{pencil}**). +1. Edit the test case's title or description. +1. Select **Save changes**. + ## Archive a test case When you want to stop using a test case, you can archive it. You can [reopen an archived test case](#reopen-an-archived-test-case) later. +Users with Reporter or higher [permissions](../../user/permissions.md) can archive test cases. + To archive a test case, on the test case's page, select the **Archive test case** button. To view archived test cases: @@ -58,23 +75,6 @@ To view archived test cases: If you decide to start using an archived test case again, you can reopen it. -To reopen an archived test case, on the test case's page, select the **Reopen test case** button. - -### Enable or disable Test Cases **(ULTIMATE ONLY)** - -The GitLab Test Cases feature is under development but ready for production use. -It is deployed behind a feature flag that is **enabled by default**. -[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) -can opt to disable it. - -To enable it: - -```ruby -Feature.enable(:quality_test_cases, Project.find_by_full_path('<project_path>')) -``` - -To disable it: +Users with Reporter or higher [permissions](../../user/permissions.md) can reopen test cases. -```ruby -Feature.disable(:quality_test_cases, Project.find_by_full_path('<project_path>')) -``` +To reopen an archived test case, on the test case's page, select **Reopen test case**. diff --git a/doc/development/documentation/testing.md b/doc/development/documentation/testing.md index 5ec79d48e5d..1819af04609 100644 --- a/doc/development/documentation/testing.md +++ b/doc/development/documentation/testing.md @@ -192,6 +192,20 @@ You can use Vale: - [In a Git hook](#configure-pre-push-hooks). Vale only reports errors in the Git hook (the same configuration as the CI/CD pipelines), and does not report suggestions or warnings. +#### Vale + +Vale returns three types of results: `suggestion`, `warning`, and `error`: + +- **Suggestion**-level results are writing tips and aren't displayed in CI + job output. Suggestions don't break CI. +- **Warning**-level results are [Style Guide](styleguide/index.md) violations, aren't displayed in CI + job output, and should contain clear explanations of how to resolve the warning. + Warnings may be technical debt, or can be future error-level test items + (after the Technical Writing team completes its cleanup). Warnings don't break CI. +- **Error**-level results are Style Guide violations, and should contain clear explanations + about how to resolve the error. Errors break CI and are displayed in CI job output. + of how to resolve the error. Errors break CI and are displayed in CI job output. + ### Install linters At a minimum, install [markdownlint](#markdownlint) and [Vale](#vale) to match the checks run in diff --git a/doc/user/admin_area/analytics/dev_ops_report.md b/doc/user/admin_area/analytics/dev_ops_report.md index c99f7201cb2..7466abe42b2 100644 --- a/doc/user/admin_area/analytics/dev_ops_report.md +++ b/doc/user/admin_area/analytics/dev_ops_report.md @@ -9,18 +9,18 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30469) in GitLab 9.3. > - [Renamed from Conversational Development Index](https://gitlab.com/gitlab-org/gitlab/-/issues/20976) in GitLab 12.6. -NOTE: -Your GitLab instance's [usage ping](../settings/usage_statistics.md#usage-ping) must be activated in order to use this feature. - The DevOps Report gives you an overview of your entire instance's adoption of [Concurrent DevOps](https://about.gitlab.com/topics/concurrent-devops/) from planning to monitoring. To see DevOps Report, go to **Admin Area > Analytics > DevOps Report**. -## DevOps Score +## DevOps Score **(CORE)** -DevOps Score displays the usage of GitLab's major features on your instance over +NOTE: +Your GitLab instance's [usage ping](../settings/usage_statistics.md#usage-ping) must be activated in order to use this feature. + +The DevOps Score tab displays the usage of GitLab's major features on your instance over the last 30 days, averaged over the number of billable users in that time period. It also provides a Lead score per feature, which is calculated based on GitLab's analysis of top-performing instances based on [usage ping data](../settings/usage_statistics.md#usage-ping) that GitLab has @@ -35,3 +35,27 @@ improve your scores. Usage ping data is aggregated on GitLab's servers for analysis. Your usage information is **not sent** to any other GitLab instances. If you have just started using GitLab, it may take a few weeks for data to be collected before this feature is available. + +## DevOps Adoption **(ULTIMATE)** + +[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247112) in GitLab 13.7. + +The DevOps Adoption tab shows you which segments of your organization are using GitLab's most essential features: + +- Issues +- Merge Requests +- Approvals +- Runners +- Pipelines +- Deploys +- Scanning + +Segments are arbitrary collections of GitLab groups and projects that you define. You might define a segment to represent a small team, a large department, or a whole organization. You are limited to creating a maximum of 20 segments, and each segment is limited to a maximum of 20 groups. Buttons to manage your segments appear in the DevOps Adoption section of the page. + +DevOps Adoption allows you to: + +- Verify whether you are getting the return on investment that you expected from GitLab. +- Identify specific groups that are lagging in their adoption of GitLab so you can help them along in their DevOps journey. +- Find the groups that have adopted certain features and can provide guidance to other groups on how to use those features. + +![DevOps Report](img/dev_ops_adoption_v13_7.png) diff --git a/doc/user/admin_area/analytics/img/dev_ops_adoption_v13_7.png b/doc/user/admin_area/analytics/img/dev_ops_adoption_v13_7.png Binary files differnew file mode 100644 index 00000000000..0f1f16bead6 --- /dev/null +++ b/doc/user/admin_area/analytics/img/dev_ops_adoption_v13_7.png diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md index f0cae369fec..46837ea2091 100644 --- a/doc/user/application_security/dependency_scanning/index.md +++ b/doc/user/application_security/dependency_scanning/index.md @@ -141,6 +141,16 @@ gemnasium-dependency_scanning: DS_REMEDIATE: "false" ``` +To override the `dependencies: []` attribute, add an override job as above, targeting this attribute: + +```yaml +include: + - template: Dependency-Scanning.gitlab-ci.yml + +gemnasium-dependency_scanning: + dependencies: ["build"] +``` + ### Available variables Dependency scanning can be [configured](#customizing-the-dependency-scanning-settings) diff --git a/doc/user/clusters/agent/index.md b/doc/user/clusters/agent/index.md index 0ce8888272a..5963485aebc 100644 --- a/doc/user/clusters/agent/index.md +++ b/doc/user/clusters/agent/index.md @@ -249,11 +249,14 @@ example [`resources.yml` file](#example-resourcesyml-file) in the following ways The agent can use the WebSockets or gRPC protocols to connect to the Agent Server. Select the option appropriate for your cluster configuration and GitLab architecture: - The `wss` scheme (an encrypted WebSockets connection) is specified by default - after you install `gitlab-kas` sub-chart or enable `kas` for Omnibus GitLab. - In this case, you must set `wss://GitLab.host.tld:443/-/kubernetes-agent` as + after you install the `gitlab-kas` sub-chart, or enable `gitlab-kas` for Omnibus GitLab. + When using the sub-chart, you must set `wss://kas.host.tld:443` as + `kas-address`, where `host.tld` is the domain you've setup for your GitLab installation. + When using Omnibus GitLab, you must set `wss://GitLab.host.tld:443/-/kubernetes-agent` as `kas-address`, where `GitLab.host.tld` is your GitLab hostname. - - Specify the `ws` scheme (such as `ws://GitLab.host.tld:80/-/kubernetes-agent`) + - When using the sub-chart, specify the `ws` scheme (such as `ws://kas.host.tld:80`) to use an unencrypted WebSockets connection. + When using the Omnibus GitLab, specify the `ws` scheme (such as `ws://GitLab.host.tld:80/-/kubernetes-agent`). - Specify the `grpc` scheme if both Agent and Server are installed in one cluster. In this case, you may specify `kas-address` value as `grpc://gitlab-kas.<your-namespace>:5005`) to use gRPC directly, where `gitlab-kas` @@ -262,6 +265,10 @@ example [`resources.yml` file](#example-resourcesyml-file) in the following ways Follow the [Support TLS for gRPC communication issue](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/issues/7) for progress updates. + - When deploying KAS through the [GitLab chart](https://docs.gitlab.com/charts/), it's possible to customize the `kas-address` for `wss` and `ws` schemes to whatever you need. + Check the [chart's KAS Ingress docs](https://docs.gitlab.com/charts/charts/gitlab/kas/#ingress) + to learn more about it. + - In the near future, Omnibus GitLab intends to provision `gitlab-kas` under a sub-domain by default, instead of the `/-/kubernetes-agent` path. Please follow [this issue](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5784) for details. - If you defined your own secret name, replace `gitlab-agent-token` with your secret name in the `secretName:` section. @@ -309,7 +316,8 @@ spec: args: - --token-file=/config/token - --kas-address - - wss://gitlab.host.tld:443/-/kubernetes-agent + - wss://kas.host.tld:443 # change this line for the one below if using Omnibus GitLab + # - wss://gitlab.host.tld:443/-/kubernetes-agent volumeMounts: - name: token-volume mountPath: /config diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md index 8634b3f5095..f6de4117f7c 100644 --- a/doc/user/group/saml_sso/index.md +++ b/doc/user/group/saml_sso/index.md @@ -222,18 +222,20 @@ On subsequent visits, you should be able to go [sign in to GitLab.com with SAML] ### Configure user settings from SAML response -GitLab allows setting certain user attributes based on values from the SAML response. +[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/263661) in GitLab 13.7. + +GitLab allows setting certain user attributes based on values from the SAML response. This affects users created on first sign-in via Group SAML. Existing users' -attributes are not affected regardless of the values sent in the SAML response. +attributes are not affected regardless of the values sent in the SAML response. #### Supported user attributes - `can_create_group` - 'true' or 'false' to indicate whether the user can create new groups. Default is `true`. -- `projects_limit` - The total number of personal projects a user can create. - A value of `0` means the user cannot create new projects in their personal +- `projects_limit` - The total number of personal projects a user can create. + A value of `0` means the user cannot create new projects in their personal namespace. Default is `10000`. - + #### Example SAML response You can find SAML responses in the developer tools or console of your browser, diff --git a/doc/user/packages/dependency_proxy/index.md b/doc/user/packages/dependency_proxy/index.md index 3ebf4d55f86..d7586227ace 100644 --- a/doc/user/packages/dependency_proxy/index.md +++ b/doc/user/packages/dependency_proxy/index.md @@ -57,6 +57,19 @@ Prerequisites: ### Authenticate with the Dependency Proxy +> - [Authentication and support for private groups](https://gitlab.com/gitlab-org/gitlab/-/issues/11582) in [GitLab Core](https://about.gitlab.com/pricing/) 13.7. +> - It was [deployed behind a feature flag](../../feature_flags.md), disabled by default. +> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49519) on GitLab 13.7. +> - It's enabled on GitLab.com. +> - It's recommended for production use. +> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](../../../administration/packages/dependency_proxy.md#disabling-authentication). **(CORE ONLY)** + +WARNING: +This feature might not be available to you. Check the **version history** note above for details. +The requirement to authenticate is a breaking change added in 13.7. An [administrator can temporarily +disable it](../../../administration/packages/dependency_proxy.md#disabling-authentication) if it +has disrupted your existing Dependency Proxy usage. + Because the Dependency Proxy is storing Docker images in a space associated with your group, you must authenticate against the Dependency Proxy. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index c0134420968..0eefef4d37e 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -63,9 +63,9 @@ The following table depicts the various user permission levels in a project. | See a job log | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ | | See a job with [debug logging](../ci/variables/README.md#debug-logging) | | | ✓ | ✓ | ✓ | | Download and browse job artifacts | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ | +| Create confidential issue | ✓ | ✓ | ✓ | ✓ | ✓ | | Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ | | See related issues | ✓ | ✓ | ✓ | ✓ | ✓ | -| Create confidential issue | ✓ | ✓ | ✓ | ✓ | ✓ | | View [Releases](project/releases/index.md) | ✓ (*6*) | ✓ | ✓ | ✓ | ✓ | | View requirements **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ | | View Insights **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ | @@ -93,6 +93,10 @@ The following table depicts the various user permission levels in a project. | Create new merge request | | ✓ | ✓ | ✓ | ✓ | | View metrics dashboard annotations | | ✓ | ✓ | ✓ | ✓ | | Create/edit requirements **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ | +| Create new [test case](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ | +| Archive [test case](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ | +| Move [test case](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ | +| Reopen [test case](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ | | Pull [packages](packages/index.md) | | ✓ | ✓ | ✓ | ✓ | | Publish [packages](packages/index.md) | | | ✓ | ✓ | ✓ | | Create/edit/delete a Cleanup policy | | | ✓ | ✓ | ✓ | diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index e38c94261eb..48b753a8d4f 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -307,6 +307,9 @@ your account. We suggest copying and printing them, or downloading them using the **Download codes** button for storage in a safe place. If you choose to download them, the file is called `gitlab-recovery-codes.txt`. +The UI now includes **Copy codes** and **Print codes** buttons, for your convenience. +[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267730) in GitLab 13.7. + If you lose the recovery codes or just want to generate new ones, you can do so from the [two-factor authentication account settings page](#regenerate-2fa-recovery-codes) or [using SSH](#generate-new-recovery-codes-using-ssh). diff --git a/doc/user/project/service_desk.md b/doc/user/project/service_desk.md index 3b9a2319ca5..4f3ca12c582 100644 --- a/doc/user/project/service_desk.md +++ b/doc/user/project/service_desk.md @@ -4,7 +4,7 @@ group: Certify 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 Desk +# Service Desk **(CORE)** > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/149) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.1. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/214839) to [GitLab Starter](https://about.gitlab.com/pricing/) in 13.0. @@ -129,10 +129,12 @@ this name in the `From` header. The default display name is `GitLab Support Bot` ### Using custom email address **(CORE ONLY)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2201) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.0. - -NOTE: -This feature is disabled by default. For steps to enable it, see [Enable custom email address](#enable-custom-email-address). +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2201) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.0. +> - It was [deployed behind a feature flag](../feature_flags.md), disabled by default. +> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/284656) on GitLab 13.7. +> - It's enabled on GitLab.com. +> - It's recommended for production use. +> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-custom-email-address). **(CORE ONLY)** If the `service_desk_email` feature flag is enabled in your configuration, then it's possible to create Service Desk issues by sending emails to the @@ -198,18 +200,27 @@ In this case, suppose the `mygroup/myproject` project Service Desk settings has suffix set to `support`, and a user sends an email to `project_contact+mygroup-myproject-support@example.com`. As a result, a new Service Desk issue is created from this email in the `mygroup/myproject` project. -#### Enable custom email address +The configuration options are the same as for configuring +[incoming email](../../administration/incoming_email.md#set-it-up). + +#### Disable custom email address **(CORE ONLY)** + +Service Desk custom email is under development but ready for production use. +It is deployed behind a feature flag that is **enabled by default**. +[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) +can opt to disable it. -This feature comes with the `service_desk_custom_address` feature flag disabled by default. -To turn on the feature, ask a GitLab administrator with Rails console access to run the following -command: +To enable it: ```ruby Feature.enable(:service_desk_custom_address) ``` -The configuration options are the same as for configuring -[incoming email](../../administration/incoming_email.md#set-it-up). +To disable it: + +```ruby +Feature.disable(:service_desk_custom_address) +``` ## Using Service Desk diff --git a/doc/user/usage_quotas.md b/doc/user/usage_quotas.md index 82fb8d787b0..a6047dc47b3 100644 --- a/doc/user/usage_quotas.md +++ b/doc/user/usage_quotas.md @@ -1,7 +1,7 @@ --- type: howto stage: Fulfillment -group: Fulfillment +group: Provision 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/#designated-technical-writers --- diff --git a/lib/gitlab/batch_pop_queueing.rb b/lib/gitlab/batch_pop_queueing.rb index 61011abddf5..e18f1320ea4 100644 --- a/lib/gitlab/batch_pop_queueing.rb +++ b/lib/gitlab/batch_pop_queueing.rb @@ -9,7 +9,7 @@ module Gitlab # and the following items wait until the next items have been popped from the queue. # On the other hand, this queueing system, the former part is same, however, # it pops the enqueued items as batch. This is especially useful when you want to - # drop redandant items from the queue in order to process important items only, + # drop redundant items from the queue in order to process important items only, # thus it's more efficient than the traditional queueing system. # # Caveats: diff --git a/lib/gitlab/email/handler/service_desk_handler.rb b/lib/gitlab/email/handler/service_desk_handler.rb index 6c229291f39..0bbe3980f67 100644 --- a/lib/gitlab/email/handler/service_desk_handler.rb +++ b/lib/gitlab/email/handler/service_desk_handler.rb @@ -68,7 +68,7 @@ module Gitlab end def valid_project_key?(project, slug) - project.present? && slug == project.full_path_slug && Feature.enabled?(:service_desk_custom_address, project) + project.present? && slug == project.full_path_slug && Feature.enabled?(:service_desk_custom_address, project, default_enabled: true) end def create_issue! diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 83dc52a3463..f92745e57f7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16504,6 +16504,9 @@ msgstr "" msgid "Loading issues" msgstr "" +msgid "Loading more issues" +msgstr "" + msgid "Loading snippet" msgstr "" @@ -27072,9 +27075,15 @@ msgstr "" msgid "Terraform|A Terraform report was generated in your pipelines." msgstr "" +msgid "Terraform|Actions" +msgstr "" + msgid "Terraform|An error occurred while loading your Terraform States" msgstr "" +msgid "Terraform|Details" +msgstr "" + msgid "Terraform|Download JSON" msgstr "" @@ -27087,6 +27096,9 @@ msgstr "" msgid "Terraform|Get started with Terraform" msgstr "" +msgid "Terraform|Job status" +msgstr "" + msgid "Terraform|Lock" msgstr "" @@ -27096,6 +27108,12 @@ msgstr "" msgid "Terraform|Locked by %{user} %{timeAgo}" msgstr "" +msgid "Terraform|Name" +msgstr "" + +msgid "Terraform|Pipeline" +msgstr "" + msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete" msgstr "" diff --git a/package.json b/package.json index 7bd12a6479e..7e3fcf47bc9 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,8 @@ "@babel/plugin-syntax-import-meta": "^7.10.1", "@babel/preset-env": "^7.10.1", "@gitlab/at.js": "1.5.5", - "@gitlab/svgs": "1.176.0", - "@gitlab/ui": "24.6.0", + "@gitlab/svgs": "1.177.0", + "@gitlab/ui": "24.7.0", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "^6.0.3-3", "@rails/ujs": "^6.0.3-2", diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb index 0f7e4fbbc97..24a148e9330 100644 --- a/qa/qa/git/repository.rb +++ b/qa/qa/git/repository.rb @@ -14,7 +14,7 @@ module QA include Support::Run attr_writer :use_lfs, :gpg_key_id - attr_accessor :env_vars + attr_accessor :env_vars, :default_branch InvalidCredentialsError = Class.new(RuntimeError) @@ -25,6 +25,7 @@ module QA self.env_vars = [%Q{HOME="#{tmp_home_dir}"}] @use_lfs = false @gpg_key_id = nil + @default_branch = Runtime::Env.default_branch end def self.perform(*args) @@ -123,7 +124,7 @@ module QA run_git("git rev-parse --abbrev-ref HEAD").to_s end - def push_changes(branch = 'master', push_options: nil) + def push_changes(branch = @default_branch, push_options: nil) cmd = ['git push'] cmd << push_options_hash_to_string(push_options) cmd << uri @@ -209,6 +210,13 @@ module QA File.delete(netrc_file_path) if File.exist?(netrc_file_path) end + def remote_branches + # This gets the remote branch names + # When executed on a fresh repo it returns the default branch name + + run_git('git --no-pager branch --list --remotes --format="%(refname:lstrip=3)"').to_s.split("\n") + end + private attr_reader :uri, :username, :password, :ssh, :use_lfs diff --git a/qa/qa/page/project/operations/metrics/show.rb b/qa/qa/page/project/operations/metrics/show.rb index 35dfd92f119..6e8a52ab2e6 100644 --- a/qa/qa/page/project/operations/metrics/show.rb +++ b/qa/qa/page/project/operations/metrics/show.rb @@ -70,7 +70,7 @@ module QA end end - def duplicate_dashboard(save_as = 'test_duplication.yml', commit_option = 'Commit to master branch') + def duplicate_dashboard(save_as = 'test_duplication.yml', commit_option = 'Commit to default branch') click_element :actions_menu_dropdown click_on 'Duplicate current dashboard' fill_element :duplicate_dashboard_filename_field, "#{SecureRandom.hex(8)}-#{save_as}" diff --git a/qa/qa/resource/events/project.rb b/qa/qa/resource/events/project.rb index 8c97f66c663..0348f2f05f5 100644 --- a/qa/qa/resource/events/project.rb +++ b/qa/qa/resource/events/project.rb @@ -20,7 +20,7 @@ module QA end end - def wait_for_push_new_branch(branch_name = "master") + def wait_for_push_new_branch(branch_name = self.default_branch) QA::Runtime::Logger.debug(%Q[#{self.class.name} - wait_for_push_new_branch with branch_name "#{branch_name}"]) wait_for_event do events(action: 'pushed').any? { |event| event.dig(:push_data, :ref) == branch_name } diff --git a/qa/qa/resource/file.rb b/qa/qa/resource/file.rb index 0d2bf9890ea..4ca180373f6 100644 --- a/qa/qa/resource/file.rb +++ b/qa/qa/resource/file.rb @@ -30,7 +30,7 @@ module QA end def branch - @branch ||= "master" + @branch ||= project.default_branch end def fabricate! diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index dca8fb6dc6b..ecf25b797a8 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -34,7 +34,7 @@ module QA attribute :target do Repository::ProjectPush.fabricate! do |resource| resource.project = project - resource.branch_name = 'master' + resource.branch_name = project.default_branch resource.new_branch = @target_new_branch resource.remote_branch = target_branch end @@ -56,7 +56,6 @@ module QA @title = 'QA test - merge request' @description = 'This is a test merge request' @source_branch = "qa-test-feature-#{SecureRandom.hex(8)}" - @target_branch = "master" @assignee = nil @milestone = nil @labels = [] @@ -68,7 +67,7 @@ module QA end def fabricate! - populate(:target, :source) + populate_target_and_source_if_required project.visit! Page::Project::Show.perform(&:new_merge_request) @@ -89,7 +88,7 @@ module QA def fabricate_via_api! resource_web_url(api_get) rescue ResourceNotFoundError - populate(:target, :source) unless @no_preparation + populate_target_and_source_if_required super end @@ -144,6 +143,12 @@ module QA super(api_resource) end + + def populate_target_and_source_if_required + @target_branch ||= project.default_branch + + populate(:target, :source) unless @no_preparation + end end end end diff --git a/qa/qa/resource/pipeline.rb b/qa/qa/resource/pipeline.rb index a115de3e825..907a6cb8558 100644 --- a/qa/qa/resource/pipeline.rb +++ b/qa/qa/resource/pipeline.rb @@ -22,7 +22,6 @@ module QA attribute :variables def initialize - @ref = 'master' @variables = [] end @@ -34,6 +33,10 @@ module QA Page::Project::Pipeline::New.perform(&:click_run_pipeline_button) end + def ref + project.default_branch + end + def api_get_path "/projects/#{project.id}/pipelines/#{id}" end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index a46c2e8eca5..a92f7912b9e 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -15,7 +15,6 @@ module QA attr_writer :github_personal_access_token attr_writer :github_repository_path - attribute :default_branch attribute :id attribute :name attribute :add_name_uuid @@ -26,6 +25,10 @@ module QA attribute :template_name attribute :import + attribute :default_branch do + api_response[:default_branch] || Runtime::Env.default_branch + end + attribute :group do Group.fabricate! end diff --git a/qa/qa/resource/repository/commit.rb b/qa/qa/resource/repository/commit.rb index 4eb9c1d218f..f5dba164de6 100644 --- a/qa/qa/resource/repository/commit.rb +++ b/qa/qa/resource/repository/commit.rb @@ -81,7 +81,7 @@ module QA def api_post_body { - branch: @branch || "master", + branch: @branch || project.default_branch, author_email: @author_email || Runtime::User.default_email, author_name: @author_name || Runtime::User.username, commit_message: commit_message, diff --git a/qa/qa/resource/repository/project_push.rb b/qa/qa/resource/repository/project_push.rb index c46921ad0c7..ef4873e9483 100644 --- a/qa/qa/resource/repository/project_push.rb +++ b/qa/qa/resource/repository/project_push.rb @@ -23,7 +23,6 @@ module QA @file_name = "file-#{SecureRandom.hex(8)}.txt" @file_content = '# This is test project' @commit_message = "This is a test commit" - @branch_name = 'master' @new_branch = true @project_name = 'project-with-code' @wait_for_push = true @@ -39,6 +38,8 @@ module QA end def fabricate! + @branch_name ||= project.default_branch + super project.wait_for_push @commit_message if @wait_for_push end diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb index 5266f8b9bea..f5b6040d927 100644 --- a/qa/qa/resource/repository/push.rb +++ b/qa/qa/resource/repository/push.rb @@ -17,7 +17,6 @@ module QA @file_name = "file-#{SecureRandom.hex(8)}.txt" @file_content = '# This is test file' @commit_message = "This is a test commit" - @branch_name = 'master' @new_branch = true @repository_http_uri = "" @ssh_key = nil @@ -78,6 +77,8 @@ module QA @output += repository.clone repository.configure_identity(name, email) + @branch_name ||= default_branch(repository) + @output += repository.checkout(branch_name, new_branch: new_branch) if @tag_name @@ -105,6 +106,10 @@ module QA private + def default_branch(repository) + repository.remote_branches.last || Runtime::Env.default_branch + end + def commit_to(repository) @gpg_key_id.nil? ? repository.commit(@commit_message) : repository.commit_with_gpg(@commit_message) end diff --git a/qa/qa/resource/repository/wiki_push.rb b/qa/qa/resource/repository/wiki_push.rb index edf76c7cd78..f188e52c969 100644 --- a/qa/qa/resource/repository/wiki_push.rb +++ b/qa/qa/resource/repository/wiki_push.rb @@ -12,11 +12,14 @@ module QA end end + def branch_name + @branch_name ||= wiki.project.default_branch + end + def initialize @file_name = 'Home.md' @file_content = 'This line was created using git push' @commit_message = 'Updating using git push' - @branch_name = 'master' @new_branch = false end diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 54b670485eb..d6ac278dbc8 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -63,7 +63,7 @@ module QA end def pipeline_from_project_name - ci_project_name.to_s.start_with?('gitlab-qa') ? 'master' : ci_project_name + ci_project_name.to_s.start_with?('gitlab-qa') ? Runtime::Env.default_branch : ci_project_name end def additional_repository_storage @@ -102,6 +102,10 @@ module QA enabled?(ENV['QA_DEBUG'], default: false) end + def default_branch + ENV['QA_DEFAULT_BRANCH'] || 'master' + end + def log_destination ENV['QA_LOG_PATH'] || $stdout end diff --git a/qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb index 6654a35915f..89bf92cd3af 100644 --- a/qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb +++ b/qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb @@ -37,7 +37,7 @@ module QA Support::Waiter.wait_until(retry_on_exception: true, sleep_interval: 5) do Resource::Repository::Commit.fabricate_via_api! do |commits| commits.project = project - commits.sha = 'master' + commits.sha = project.default_branch end end diff --git a/qa/qa/specs/features/api/3_create/repository/files_spec.rb b/qa/qa/specs/features/api/3_create/repository/files_spec.rb index f539aae9b29..1099234537a 100644 --- a/qa/qa/specs/features/api/3_create/repository/files_spec.rb +++ b/qa/qa/specs/features/api/3_create/repository/files_spec.rb @@ -21,27 +21,29 @@ module QA a_hash_including(name: project_name, path: project_name) ) + default_branch = json_body[:default_branch].to_s.empty? ? Runtime::Env.default_branch : json_body[:default_branch] + create_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md") - post create_file_request.url, branch: 'master', content: 'Hello world', commit_message: 'Add README.md' + post create_file_request.url, branch: default_branch, content: 'Hello world', commit_message: 'Add README.md' expect_status(201) expect(json_body).to match( - a_hash_including(branch: 'master', file_path: 'README.md') + a_hash_including(branch: default_branch, file_path: 'README.md') ) - get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md", ref: 'master') + get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md", ref: default_branch) get get_file_request.url expect_status(200) expect(json_body).to match( a_hash_including( - ref: 'master', + ref: default_branch, file_path: 'README.md', file_name: 'README.md', encoding: 'base64', content: 'SGVsbG8gd29ybGQ=' ) ) - delete_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md", branch: 'master', commit_message: 'Remove README.md') + delete_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md", branch: default_branch, commit_message: 'Remove README.md') delete delete_file_request.url expect_status(204) @@ -80,10 +82,12 @@ module QA create_project_request = Runtime::API::Request.new(@api_client, '/projects') post create_project_request.url, path: project_name, name: project_name + default_branch = json_body[:default_branch].to_s.empty? ? Runtime::Env.default_branch : json_body[:default_branch] + create_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg") - post create_file_request.url, branch: 'master', content: svg_file, commit_message: 'Add test.svg' + post create_file_request.url, branch: default_branch, content: svg_file, commit_message: 'Add test.svg' - get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg/raw", ref: 'master') + get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg/raw", ref: default_branch) 3.times do response = get get_file_request.url diff --git a/qa/qa/specs/features/api/4_verify/pipeline_deletion_spec.rb b/qa/qa/specs/features/api/4_verify/pipeline_deletion_spec.rb index e663d122240..1d1b765bb9f 100644 --- a/qa/qa/specs/features/api/4_verify/pipeline_deletion_spec.rb +++ b/qa/qa/specs/features/api/4_verify/pipeline_deletion_spec.rb @@ -42,7 +42,7 @@ module QA end let!(:pipeline_id) do - pipeline_create_request = Runtime::API::Request.new(api_client, "/projects/#{project.id}/pipeline?ref=master") + pipeline_create_request = Runtime::API::Request.new(api_client, "/projects/#{project.id}/pipeline?ref=#{project.default_branch}") JSON.parse(post(pipeline_create_request.url, nil))['id'] end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb index 3609d083fde..db96c2d4ad3 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb @@ -6,17 +6,18 @@ module QA it 'user creates an event in the activity page upon Git push', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/407' do Flow::Login.sign_in - Resource::Repository::ProjectPush.fabricate! do |push| + project = Resource::Repository::ProjectPush.fabricate! do |push| push.file_name = 'README.md' push.file_content = '# This is a test project' push.commit_message = 'Add README.md' - end.project.visit! + end.project + project.visit! Page::Project::Menu.perform(&:click_activity) Page::Project::Activity.perform do |activity| activity.click_push_events - expect(activity).to have_content('pushed new branch master') + expect(activity).to have_content("pushed new branch #{project.default_branch}") end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb b/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb index 2889875cf24..0fec7bc9e9d 100644 --- a/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb @@ -36,7 +36,6 @@ module QA QA::Support::Retrier.retry_on_exception do Resource::Repository::ProjectPush.fabricate! do |push| push.project = project - push.branch_name = 'master' push.new_branch = false push.file_name = "file_#{SecureRandom.hex(4)}.txt" end diff --git a/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb b/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb index 37ddd1425a8..d53e7fcf69a 100644 --- a/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb @@ -80,7 +80,6 @@ module QA def push_commit(commit_message) Resource::Repository::ProjectPush.fabricate! do |push| - push.branch_name = 'master' push.commit_message = commit_message push.file_content = commit_message push.project = project @@ -101,7 +100,7 @@ module QA end def master_branch_exists? - project.repository_branches.map { |item| item[:name] }.include?("master") + project.repository_branches.map { |item| item[:name] }.include?(project.default_branch) end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index 02fbb0bbbd7..823a033ab6d 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb @@ -27,7 +27,6 @@ module QA push.project = project push.file_name = "other.txt" push.file_content = "New file added!" - push.branch_name = "master" push.new_branch = false end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_merge_ref_diff_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_merge_ref_diff_spec.rb index 40c22d6c78c..24b92164060 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_merge_ref_diff_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_merge_ref_diff_spec.rb @@ -37,8 +37,8 @@ module QA mr_page.click_diffs_tab mr_page.click_target_version_dropdown - expect(mr_page.version_dropdown_content).to include('master (HEAD)') - expect(mr_page.version_dropdown_content).not_to include('master (base)') + expect(mr_page.version_dropdown_content).to include("#{project.default_branch} (HEAD)") + expect(mr_page.version_dropdown_content).not_to include("#{project.default_branch} (base)") expect(mr_page).to have_file(merge_request.file_name) expect(mr_page).not_to have_file(new_file_name) end @@ -62,8 +62,8 @@ module QA mr_page.click_diffs_tab mr_page.click_target_version_dropdown - expect(mr_page.version_dropdown_content).to include('master (HEAD)') - expect(mr_page.version_dropdown_content).to include('master (base)') + expect(mr_page.version_dropdown_content).to include("#{project.default_branch} (HEAD)") + expect(mr_page.version_dropdown_content).to include("#{project.default_branch} (base)") expect(mr_page).to have_file(merge_request.file_name) expect(mr_page).to have_file(new_file_name) end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb index 98cbc5c0a93..8df68e0f53b 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb @@ -3,7 +3,7 @@ module QA RSpec.describe 'Create' do describe 'Create, list, and delete branches via web' do - master_branch = 'master' + master_branch = nil second_branch = 'second-branch' third_branch = 'third-branch' file_1_master = 'file.txt' @@ -21,12 +21,16 @@ module QA project = Resource::Project.fabricate_via_api! do |proj| proj.name = 'project-qa-test' proj.description = 'project for qa test' + proj.initialize_with_readme = true end + master_branch = project.default_branch + Git::Repository.perform do |repository| repository.uri = project.repository_http_location.uri repository.use_default_credentials repository.try_add_credentials_to_netrc + repository.default_branch = master_branch repository.act do clone diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb index efd61a2e63a..a21c5d58aad 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb @@ -7,7 +7,6 @@ module QA let(:changed_content) { 'changes' } let(:commit_message) { 'Changes to snippets' } let(:added_content) { 'updated ' } - let(:branch_name) { 'master' } let(:snippet) do Resource::Snippet.fabricate! do |snippet| @@ -41,7 +40,7 @@ module QA end it 'clones, pushes, and pulls a snippet over HTTP, edits via UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/826' do - Resource::Repository::Push.fabricate! do |push| + push = Resource::Repository::Push.fabricate! do |push| push.repository_http_uri = repository_uri_http push.file_name = new_file push.file_content = changed_content @@ -61,7 +60,7 @@ module QA Git::Repository.perform do |repository| repository.init_repository - repository.pull(repository_uri_http, branch_name) + repository.pull(repository_uri_http, push.branch_name) expect(repository.commits.size).to eq(3) expect(repository.commits.first).to include('Update snippet') @@ -70,7 +69,7 @@ module QA end it 'clones, pushes, and pulls a snippet over SSH, deletes via UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/825' do - Resource::Repository::Push.fabricate! do |push| + push = Resource::Repository::Push.fabricate! do |push| push.repository_ssh_uri = repository_uri_ssh push.ssh_key = ssh_key push.file_name = new_file @@ -90,7 +89,7 @@ module QA repository.use_ssh_key(ssh_key) repository.init_repository - expect { repository.pull(repository_uri_ssh, branch_name) } + expect { repository.pull(repository_uri_ssh, push.branch_name) } .to raise_error(QA::Support::Run::CommandError, /fatal: Could not read from remote repository\./) end end diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb index 79e2677da66..4ce6c3fdcd3 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb @@ -7,7 +7,7 @@ module QA let(:changed_content) { 'changes' } let(:commit_message) { 'Changes to snippets' } let(:added_content) { 'updated ' } - let(:branch_name) { 'master' } + let(:branch_name) { snippet.project.default_branch } let(:snippet) do Resource::ProjectSnippet.fabricate! do |snippet| diff --git a/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb index 3da76259888..552302addf9 100644 --- a/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb @@ -54,7 +54,7 @@ module QA script: - 'gradle publish' only: - - master + - "#{project.default_branch}" tags: - "runner-for-#{project.name}" YAML diff --git a/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb index 54e28082436..be806fcbb3e 100644 --- a/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb @@ -51,7 +51,7 @@ module QA - dotnet nuget add source "$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text - dotnet nuget push "bin/Release/*.nupkg" --source gitlab only: - - master + - "#{project.default_branch}" tags: - "runner-for-#{project.name}" YAML diff --git a/qa/qa/tools/generate_perf_testdata.rb b/qa/qa/tools/generate_perf_testdata.rb index e77678a1527..546f7e7cdca 100644 --- a/qa/qa/tools/generate_perf_testdata.rb +++ b/qa/qa/tools/generate_perf_testdata.rb @@ -95,20 +95,20 @@ module QA def create_many_merge_requests 30.times do |i| - create_a_merge_request_api_req("#{@group_name}%2F#{@project_name}", "branch#{i}", "master", "MR#{i}") + create_a_merge_request_api_req("#{@group_name}%2F#{@project_name}", "branch#{i}", Runtime::Env.default_branch, "MR#{i}") end @urls[:mr_list_page] = @urls[:project_page] + "/merge_requests" STDOUT.puts "Created many MRs: #{@urls[:mr_list_page]}" end def create_many_new_files - create_a_new_file_api_req("hello.txt", "master", "#{@group_name}%2F#{@project_name}", "hello", "my new content") + create_a_new_file_api_req("hello.txt", Runtime::Env.default_branch, "#{@group_name}%2F#{@project_name}", "hello", "my new content") 30.times do |i| - create_a_new_file_api_req("hello#{i}.txt", "master", "#{@group_name}%2F#{@project_name}", "hello", "my new content") + create_a_new_file_api_req("hello#{i}.txt", Runtime::Env.default_branch, "#{@group_name}%2F#{@project_name}", "hello", "my new content") create_a_new_file_api_req("hello#{i}.txt", "branch#{i}", "#{@group_name}%2F#{@project_name}", "hello", "my new content") end - @urls[:files_page] = @urls[:project_page] + "/tree/master" + @urls[:files_page] = @urls[:project_page] + "/tree/#{Runtime::Env.default_branch}" STDOUT.puts "Added many new files: #{@urls[:files_page]}" end @@ -138,7 +138,7 @@ module QA 16.times do |i| faker_line_arr = Faker::Lorem.sentences(1500) content = faker_line_arr.join("\n\r") - create_a_new_file_api_req("hello#{i + 100}.txt", "master", "#{@group_name}%2F#{@project_name}", "Add hello#{i + 100}.txt", content) + create_a_new_file_api_req("hello#{i + 100}.txt", Runtime::Env.default_branch, "#{@group_name}%2F#{@project_name}", "Add hello#{i + 100}.txt", content) content_arr[i] = faker_line_arr end @@ -151,7 +151,7 @@ module QA update_file_api_req("hello#{i + 100}.txt", "performance", "#{@group_name}%2F#{@project_name}", "Update hello#{i + 100}.txt", content) end - create_mr_response = create_a_merge_request_api_req("#{@group_name}%2F#{@project_name}", "performance", "master", "Large_MR") + create_mr_response = create_a_merge_request_api_req("#{@group_name}%2F#{@project_name}", "performance", Runtime::Env.default_branch, "Large_MR") iid = JSON.parse(create_mr_response.body)["iid"] diff_refs = JSON.parse(create_mr_response.body)["diff_refs"] @@ -200,7 +200,7 @@ module QA create_a_branch_api_req(branch_name, project_path) create_a_new_file_api_req(file_name, branch_name, project_path, "Initial commit for new file", "Initial file content") - create_mr_response = create_a_merge_request_api_req(project_path, branch_name, "master", "MR with many commits-#{SecureRandom.hex(8)}") + create_mr_response = create_a_merge_request_api_req(project_path, branch_name, Runtime::Env.default_branch, "MR with many commits-#{SecureRandom.hex(8)}") @urls[:mr_with_many_commits] = JSON.parse(create_mr_response.body)["web_url"] 100.times do |i| update_file_api_req(file_name, branch_name, project_path, Faker::Lorem.sentences(5).join(" "), Faker::Lorem.sentences(500).join("\n")) @@ -268,7 +268,7 @@ module QA def create_a_branch_api_req(branch_name, project_path_or_id) call_api(expected_response_code: 201) do - post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/branches").url, "branch=#{branch_name}&ref=master" + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/branches").url, "branch=#{branch_name}&ref=#{Runtime::Env.default_branch}" end end diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb index 02bb7783c28..77639c54b79 100644 --- a/qa/spec/git/repository_spec.rb +++ b/qa/spec/git/repository_spec.rb @@ -110,7 +110,7 @@ RSpec.describe QA::Git::Repository do end describe '#push_changes' do - let(:branch) { 'master' } + let(:branch) { QA::Runtime::Env.default_branch } let(:call_method) { repository.push_changes } let(:command) { "git push #{repo_uri_with_credentials} #{branch}" } diff --git a/qa/spec/resource/events/project_spec.rb b/qa/spec/resource/events/project_spec.rb index 88d50749a0a..fa74e2c8477 100644 --- a/qa/spec/resource/events/project_spec.rb +++ b/qa/spec/resource/events/project_spec.rb @@ -6,6 +6,10 @@ RSpec.describe QA::Resource::Events::Project do def api_get_path '/foo' end + + def default_branch + 'master' + end end end @@ -53,7 +57,7 @@ RSpec.describe QA::Resource::Events::Project do end describe "#wait_for_push_new_branch" do - it 'waits for a push to master if no branch is given' do + it 'waits for a push to the default branch if no branch is given' do expect(subject).to receive(:api_get_from).with('/foo/events?action=pushed') expect { subject.wait_for_push_new_branch }.not_to raise_error end diff --git a/qa/spec/specs/helpers/quarantine_spec.rb b/qa/spec/specs/helpers/quarantine_spec.rb index 41bc3eadff4..80fd65faeed 100644 --- a/qa/spec/specs/helpers/quarantine_spec.rb +++ b/qa/spec/specs/helpers/quarantine_spec.rb @@ -423,17 +423,17 @@ RSpec.describe QA::Specs::Helpers::Quarantine do end end - context 'when a pipeline triggered from master runs in gitlab-qa' do + context 'when a pipeline triggered from the default branch runs in gitlab-qa' do before do stub_env('CI_PROJECT_NAME', 'gitlab-qa') described_class.configure_rspec end - it 'runs on master pipelines' do + it 'runs on default branch pipelines' do group = describe_successfully do it('runs on master pipeline given a single pipeline', only: { pipeline: :master } ) {} it('runs in master given an array of pipelines', only: { pipeline: [:canary, :master] }) {} - it('does not run in non-master pipelines', only: { pipeline: [:nightly, :not_nightly, :not_master] } ) {} + it('does not run in non-default pipelines', only: { pipeline: [:nightly, :not_nightly, :not_master] } ) {} end aggregate_failures do diff --git a/qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb b/qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb index f4a7897b41f..40a8be8202a 100644 --- a/qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb +++ b/qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb @@ -30,17 +30,17 @@ module QA ) end - # Require approval from code owners on master + # Require approval from code owners on the default branch # The default branch is already protected, and we can't update a protected branch via the API (yet) # so we unprotect it first and then protect it again with the desired parameters Resource::ProtectedBranch.unprotect_via_api! do |protected_branch| protected_branch.project = project - protected_branch.branch_name = 'master' + protected_branch.branch_name = project.default_branch end Resource::ProtectedBranch.fabricate_via_api! do |protected_branch| protected_branch.project = project - protected_branch.branch_name = 'master' + protected_branch.branch_name = project.default_branch protected_branch.new_branch = false protected_branch.require_code_owner_approval = true end diff --git a/spec/frontend/boards/board_list_new_spec.js b/spec/frontend/boards/board_list_new_spec.js index 585c0f37329..96b03ed927e 100644 --- a/spec/frontend/boards/board_list_new_spec.js +++ b/spec/frontend/boards/board_list_new_spec.js @@ -1,5 +1,3 @@ -/* global List */ - import Vuex from 'vuex'; import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame'; import { createLocalVue, mount } from '@vue/test-utils'; @@ -7,7 +5,7 @@ import eventHub from '~/boards/eventhub'; import BoardList from '~/boards/components/board_list_new.vue'; import BoardCard from '~/boards/components/board_card.vue'; import '~/boards/models/list'; -import { listObj, mockIssuesByListId, issues, mockIssues } from './mock_data'; +import { mockList, mockIssuesByListId, issues, mockIssues } from './mock_data'; import defaultState from '~/boards/stores/state'; const localVue = createLocalVue(); @@ -44,12 +42,10 @@ const createComponent = ({ ...state, }); - const list = new List({ - ...listObj, - id: 'gid://gitlab/List/1', + const list = { + ...mockList, ...listProps, - doNotFetchIssues: true, - }); + }; const issue = { title: 'Testing', id: 1, @@ -59,8 +55,8 @@ const createComponent = ({ assignees: [], ...listIssueProps, }; - if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesSize')) { - list.issuesSize = 1; + if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesCount')) { + list.issuesCount = 1; } const component = mount(BoardList, { @@ -158,7 +154,7 @@ describe('Board list component', () => { it('shows how many more issues to load', async () => { wrapper.vm.showCount = true; - wrapper.setProps({ list: { issuesSize: 20 } }); + wrapper.setProps({ list: { issuesCount: 20 } }); await wrapper.vm.$nextTick(); expect(wrapper.find('.board-list-count').text()).toBe('Showing 1 of 20 issues'); @@ -168,7 +164,7 @@ describe('Board list component', () => { describe('load more issues', () => { beforeEach(() => { wrapper = createComponent({ - listProps: { issuesSize: 25 }, + listProps: { issuesCount: 25 }, }); }); @@ -179,15 +175,19 @@ describe('Board list component', () => { }); it('does not load issues if already loading', () => { - wrapper.vm.listRef.dispatchEvent(new Event('scroll')); + wrapper = createComponent({ + state: { listsFlags: { 'gid://gitlab/List/1': { isLoadingMore: true } } }, + }); wrapper.vm.listRef.dispatchEvent(new Event('scroll')); - expect(actions.fetchIssuesForList).toHaveBeenCalledTimes(1); + expect(actions.fetchIssuesForList).not.toHaveBeenCalled(); }); it('shows loading more spinner', async () => { + wrapper = createComponent({ + state: { listsFlags: { 'gid://gitlab/List/1': { isLoadingMore: true } } }, + }); wrapper.vm.showCount = true; - wrapper.vm.list.loadingMore = true; await wrapper.vm.$nextTick(); expect(wrapper.find('.board-list-count .gl-spinner').exists()).toBe(true); @@ -197,13 +197,13 @@ describe('Board list component', () => { describe('max issue count warning', () => { beforeEach(() => { wrapper = createComponent({ - listProps: { issuesSize: 50 }, + listProps: { issuesCount: 50 }, }); }); describe('when issue count exceeds max issue count', () => { it('sets background to bg-danger-100', async () => { - wrapper.setProps({ list: { issuesSize: 4, maxIssueCount: 3 } }); + wrapper.setProps({ list: { issuesCount: 4, maxIssueCount: 3 } }); await wrapper.vm.$nextTick(); expect(wrapper.find('.bg-danger-100').exists()).toBe(true); @@ -212,7 +212,7 @@ describe('Board list component', () => { describe('when list issue count does NOT exceed list max issue count', () => { it('does not sets background to bg-danger-100', () => { - wrapper.setProps({ list: { issuesSize: 2, maxIssueCount: 3 } }); + wrapper.setProps({ list: { issuesCount: 2, maxIssueCount: 3 } }); expect(wrapper.find('.bg-danger-100').exists()).toBe(false); }); diff --git a/spec/frontend/boards/components/board_column_new_spec.js b/spec/frontend/boards/components/board_column_new_spec.js index 4aafc3a867a..81c0e60f931 100644 --- a/spec/frontend/boards/components/board_column_new_spec.js +++ b/spec/frontend/boards/components/board_column_new_spec.js @@ -2,7 +2,6 @@ import { shallowMount } from '@vue/test-utils'; import { listObj } from 'jest/boards/mock_data'; import BoardColumn from '~/boards/components/board_column_new.vue'; -import List from '~/boards/models/list'; import { ListType } from '~/boards/constants'; import { createStore } from '~/boards/stores'; @@ -20,24 +19,22 @@ describe('Board Column Component', () => { const listMock = { ...listObj, - list_type: listType, + listType, collapsed, }; if (listType === ListType.assignee) { delete listMock.label; - listMock.user = {}; + listMock.assignee = {}; } - const list = new List({ ...listMock, doNotFetchIssues: true }); - store = createStore(); wrapper = shallowMount(BoardColumn, { store, propsData: { disabled: false, - list, + list: listMock, }, provide: { boardId, @@ -60,7 +57,7 @@ describe('Board Column Component', () => { it('has class is-collapsed when list is collapsed', () => { createComponent({ collapsed: false }); - expect(wrapper.vm.list.isExpanded).toBe(true); + expect(isCollapsed()).toBe(false); }); it('does not have class is-collapsed when list is expanded', () => { diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js index 5918710cbf5..291013c561e 100644 --- a/spec/frontend/boards/components/board_content_spec.js +++ b/spec/frontend/boards/components/board_content_spec.js @@ -5,7 +5,7 @@ import Draggable from 'vuedraggable'; import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue'; import getters from 'ee_else_ce/boards/stores/getters'; import BoardColumn from '~/boards/components/board_column.vue'; -import { mockListsWithModel } from '../mock_data'; +import { mockLists, mockListsWithModel } from '../mock_data'; import BoardContent from '~/boards/components/board_content.vue'; const localVue = createLocalVue(); @@ -20,7 +20,7 @@ describe('BoardContent', () => { const defaultState = { isShowingEpicsSwimlanes: false, - boardLists: mockListsWithModel, + boardLists: mockLists, error: undefined, }; @@ -59,7 +59,7 @@ describe('BoardContent', () => { it('renders a BoardColumn component per list', () => { createComponent(); - expect(wrapper.findAll(BoardColumn)).toHaveLength(mockListsWithModel.length); + expect(wrapper.findAll(BoardColumn)).toHaveLength(mockLists.length); }); it('does not display EpicsSwimlanes component', () => { diff --git a/spec/frontend/boards/components/board_list_header_new_spec.js b/spec/frontend/boards/components/board_list_header_new_spec.js index 12306052ff6..7428dfae83f 100644 --- a/spec/frontend/boards/components/board_list_header_new_spec.js +++ b/spec/frontend/boards/components/board_list_header_new_spec.js @@ -1,9 +1,8 @@ import Vuex from 'vuex'; import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { listObj } from 'jest/boards/mock_data'; +import { mockLabelList } from 'jest/boards/mock_data'; import BoardListHeader from '~/boards/components/board_list_header_new.vue'; -import List from '~/boards/models/list'; import { ListType } from '~/boards/constants'; const localVue = createLocalVue(); @@ -32,21 +31,19 @@ describe('Board List Header Component', () => { const boardId = '1'; const listMock = { - ...listObj, - list_type: listType, + ...mockLabelList, + listType, collapsed, }; if (listType === ListType.assignee) { delete listMock.label; - listMock.user = {}; + listMock.assignee = {}; } - const list = new List({ ...listMock, doNotFetchIssues: true }); - if (withLocalStorage) { localStorage.setItem( - `boards.${boardId}.${list.type}.${list.id}.expanded`, + `boards.${boardId}.${listMock.listType}.${listMock.id}.expanded`, (!collapsed).toString(), ); } @@ -62,7 +59,7 @@ describe('Board List Header Component', () => { localVue, propsData: { disabled: false, - list, + list: listMock, }, provide: { boardId, @@ -72,10 +69,11 @@ describe('Board List Header Component', () => { }); }; - const isExpanded = () => wrapper.vm.list.isExpanded; - const isCollapsed = () => !isExpanded(); + const isCollapsed = () => wrapper.vm.list.collapsed; + const isExpanded = () => !isCollapsed; const findAddIssueButton = () => wrapper.find({ ref: 'newIssueBtn' }); + const findTitle = () => wrapper.find('.board-title'); const findCaret = () => wrapper.find('.board-title-caret'); describe('Add issue button', () => { @@ -125,7 +123,7 @@ describe('Board List Header Component', () => { it('collapses expanded Column when clicking the collapse icon', async () => { createComponent(); - expect(isExpanded()).toBe(true); + expect(isCollapsed()).toBe(false); findCaret().vm.$emit('click'); @@ -166,4 +164,24 @@ describe('Board List Header Component', () => { expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(String(isExpanded())); }); }); + + describe('user can drag', () => { + const cannotDragList = [ListType.backlog, ListType.closed]; + const canDragList = [ListType.label, ListType.milestone, ListType.assignee]; + + it.each(cannotDragList)( + 'does not have user-can-drag-class so user cannot drag list', + listType => { + createComponent({ listType }); + + expect(findTitle().classes()).not.toContain('user-can-drag'); + }, + ); + + it.each(canDragList)('has user-can-drag-class so user can drag list', listType => { + createComponent({ listType }); + + expect(findTitle().classes()).toContain('user-can-drag'); + }); + }); }); diff --git a/spec/frontend/boards/components/board_new_issue_new_spec.js b/spec/frontend/boards/components/board_new_issue_new_spec.js index af4bad65121..ee1c4f31cf0 100644 --- a/spec/frontend/boards/components/board_new_issue_new_spec.js +++ b/spec/frontend/boards/components/board_new_issue_new_spec.js @@ -3,7 +3,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import BoardNewIssue from '~/boards/components/board_new_issue_new.vue'; import '~/boards/models/list'; -import { mockListsWithModel } from '../mock_data'; +import { mockList } from '../mock_data'; const localVue = createLocalVue(); @@ -37,7 +37,7 @@ describe('Issue boards new issue form', () => { wrapper = shallowMount(BoardNewIssue, { propsData: { disabled: false, - list: mockListsWithModel[0], + list: mockList, }, store, localVue, diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index a81db9c8404..ea6c52c6830 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -97,7 +97,7 @@ export const mockMilestone = { due_date: '2019-12-31', }; -const assignees = [ +export const assignees = [ { id: 'gid://gitlab/User/2', username: 'angelina.herman', @@ -282,38 +282,39 @@ export const setMockEndpoints = (opts = {}) => { }); }; -export const mockLists = [ - { - id: 'gid://gitlab/List/1', - title: 'Backlog', - position: null, - listType: 'backlog', - collapsed: false, - label: null, - assignee: null, - milestone: null, - loading: false, - issuesSize: 1, - }, - { - id: 'gid://gitlab/List/2', +export const mockList = { + id: 'gid://gitlab/List/1', + title: 'Backlog', + position: null, + listType: 'backlog', + collapsed: false, + label: null, + assignee: null, + milestone: null, + loading: false, + issuesCount: 1, +}; + +export const mockLabelList = { + id: 'gid://gitlab/List/2', + title: 'To Do', + position: 0, + listType: 'label', + collapsed: false, + label: { + id: 'gid://gitlab/GroupLabel/121', title: 'To Do', - position: 0, - listType: 'label', - collapsed: false, - label: { - id: 'gid://gitlab/GroupLabel/121', - title: 'To Do', - color: '#F0AD4E', - textColor: '#FFFFFF', - description: null, - }, - assignee: null, - milestone: null, - loading: false, - issuesSize: 0, + color: '#F0AD4E', + textColor: '#FFFFFF', + description: null, }, -]; + assignee: null, + milestone: null, + loading: false, + issuesCount: 0, +}; + +export const mockLists = [mockList, mockLabelList]; export const mockListsById = keyBy(mockLists, 'id'); diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index 294e7eb4578..80a2881ad25 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -1,6 +1,5 @@ import testAction from 'helpers/vuex_action_helper'; import { - mockListsWithModel, mockLists, mockListsById, mockIssue, @@ -229,8 +228,8 @@ describe('createList', () => { describe('moveList', () => { it('should commit MOVE_LIST mutation and dispatch updateList action', done => { const initialBoardListsState = { - 'gid://gitlab/List/1': mockListsWithModel[0], - 'gid://gitlab/List/2': mockListsWithModel[1], + 'gid://gitlab/List/1': mockLists[0], + 'gid://gitlab/List/2': mockLists[1], }; const state = { @@ -252,7 +251,7 @@ describe('moveList', () => { [ { type: types.MOVE_LIST, - payload: { movedList: mockListsWithModel[0], listAtNewIndex: mockListsWithModel[1] }, + payload: { movedList: mockLists[0], listAtNewIndex: mockLists[1] }, }, ], [ @@ -271,8 +270,8 @@ describe('moveList', () => { it('should not commit MOVE_LIST or dispatch updateList if listId and replacedListId are the same', () => { const initialBoardListsState = { - 'gid://gitlab/List/1': mockListsWithModel[0], - 'gid://gitlab/List/2': mockListsWithModel[1], + 'gid://gitlab/List/1': mockLists[0], + 'gid://gitlab/List/2': mockLists[1], }; const state = { @@ -512,7 +511,7 @@ describe('moveIssue', () => { endpoints: { fullPath: 'gitlab-org', boardId: '1' }, boardType: 'group', disabled: false, - boardLists: mockListsWithModel, + boardLists: mockLists, issuesByListId: listIssues, issues, }; diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js index 3d5ba8ddc5b..6ceb8867d1f 100644 --- a/spec/frontend/boards/stores/getters_spec.js +++ b/spec/frontend/boards/stores/getters_spec.js @@ -6,7 +6,7 @@ import { mockIssues, mockIssuesByListId, issues, - mockListsWithModel, + mockLists, } from '../mock_data'; describe('Boards - Getters', () => { @@ -94,22 +94,22 @@ describe('Boards - Getters', () => { const boardsState = { boardLists: { - 'gid://gitlab/List/1': mockListsWithModel[0], - 'gid://gitlab/List/2': mockListsWithModel[1], + 'gid://gitlab/List/1': mockLists[0], + 'gid://gitlab/List/2': mockLists[1], }, }; describe('getListByLabelId', () => { it('returns list for a given label id', () => { expect(getters.getListByLabelId(boardsState)('gid://gitlab/GroupLabel/121')).toEqual( - mockListsWithModel[1], + mockLists[1], ); }); }); describe('getListByTitle', () => { it('returns list for a given list title', () => { - expect(getters.getListByTitle(boardsState)('To Do')).toEqual(mockListsWithModel[1]); + expect(getters.getListByTitle(boardsState)('To Do')).toEqual(mockLists[1]); }); }); }); diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js index 2efd9af9c26..f767c0d81f1 100644 --- a/spec/frontend/boards/stores/mutations_spec.js +++ b/spec/frontend/boards/stores/mutations_spec.js @@ -1,7 +1,7 @@ import mutations from '~/boards/stores/mutations'; import * as types from '~/boards/stores/mutation_types'; import defaultState from '~/boards/stores/state'; -import { mockListsWithModel, mockLists, rawIssue, mockIssue, mockIssue2 } from '../mock_data'; +import { mockLists, rawIssue, mockIssue, mockIssue2 } from '../mock_data'; const expectNotImplemented = action => { it('is not implemented', () => { @@ -13,8 +13,8 @@ describe('Board Store Mutations', () => { let state; const initialBoardListsState = { - 'gid://gitlab/List/1': mockListsWithModel[0], - 'gid://gitlab/List/2': mockListsWithModel[1], + 'gid://gitlab/List/1': mockLists[0], + 'gid://gitlab/List/2': mockLists[1], }; beforeEach(() => { @@ -124,10 +124,10 @@ describe('Board Store Mutations', () => { describe('RECEIVE_ADD_LIST_SUCCESS', () => { it('adds list to boardLists state', () => { - mutations.RECEIVE_ADD_LIST_SUCCESS(state, mockListsWithModel[0]); + mutations.RECEIVE_ADD_LIST_SUCCESS(state, mockLists[0]); expect(state.boardLists).toEqual({ - [mockListsWithModel[0].id]: mockListsWithModel[0], + [mockLists[0].id]: mockLists[0], }); }); }); @@ -144,13 +144,13 @@ describe('Board Store Mutations', () => { }; mutations.MOVE_LIST(state, { - movedList: mockListsWithModel[0], - listAtNewIndex: mockListsWithModel[1], + movedList: mockLists[0], + listAtNewIndex: mockLists[1], }); expect(state.boardLists).toEqual({ - 'gid://gitlab/List/2': mockListsWithModel[1], - 'gid://gitlab/List/1': mockListsWithModel[0], + 'gid://gitlab/List/2': mockLists[1], + 'gid://gitlab/List/1': mockLists[0], }); }); }); @@ -160,8 +160,8 @@ describe('Board Store Mutations', () => { state = { ...state, boardLists: { - 'gid://gitlab/List/2': mockListsWithModel[1], - 'gid://gitlab/List/1': mockListsWithModel[0], + 'gid://gitlab/List/2': mockLists[1], + 'gid://gitlab/List/1': mockLists[0], }, error: undefined, }; @@ -175,7 +175,7 @@ describe('Board Store Mutations', () => { describe('REMOVE_LIST', () => { it('removes list from boardLists', () => { - const [list, secondList] = mockListsWithModel; + const [list, secondList] = mockLists; const expected = { [secondList.id]: secondList, }; @@ -455,13 +455,13 @@ describe('Board Store Mutations', () => { boardLists: initialBoardListsState, }; - expect(state.boardLists['gid://gitlab/List/1'].issuesSize).toBe(1); + expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(1); - mutations.ADD_ISSUE_TO_LIST(state, { list: mockListsWithModel[0], issue: mockIssue2 }); + mutations.ADD_ISSUE_TO_LIST(state, { list: mockLists[0], issue: mockIssue2 }); expect(state.issuesByListId['gid://gitlab/List/1']).toContain(mockIssue2.id); expect(state.issues[mockIssue2.id]).toEqual(mockIssue2); - expect(state.boardLists['gid://gitlab/List/1'].issuesSize).toBe(2); + expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(2); }); }); diff --git a/spec/frontend/terraform/components/states_table_spec.js b/spec/frontend/terraform/components/states_table_spec.js index 3f644bd1d38..f2b7bc00e5b 100644 --- a/spec/frontend/terraform/components/states_table_spec.js +++ b/spec/frontend/terraform/components/states_table_spec.js @@ -38,6 +38,19 @@ describe('StatesTable', () => { createdByUser: { name: 'user-3', }, + job: { + detailedStatus: { + detailsPath: '/job-path-3', + group: 'failed', + icon: 'status_failed', + label: 'failed', + text: 'failed', + }, + pipeline: { + id: 'gid://gitlab/Ci::Pipeline/3', + path: '/pipeline-path-3', + }, + }, }, }, { @@ -48,6 +61,19 @@ describe('StatesTable', () => { latestVersion: { updatedAt: '2020-10-09T00:00:00Z', createdByUser: null, + job: { + detailedStatus: { + detailsPath: '/job-path-4', + group: 'passed', + icon: 'status_success', + label: 'passed', + text: 'passed', + }, + pipeline: { + id: 'gid://gitlab/Ci::Pipeline/4', + path: '/pipeline-path-4', + }, + }, }, }, ], @@ -107,6 +133,23 @@ describe('StatesTable', () => { expect(state.text()).toMatchInterpolatedText(updateTime); }); + it.each` + pipelineText | toolTipAdded | lineNumber + ${''} | ${false} | ${0} + ${''} | ${false} | ${1} + ${'#3 failed Job status'} | ${true} | ${2} + ${'#4 passed Job status'} | ${true} | ${3} + `( + 'displays the pipeline information for line "$lineNumber"', + ({ pipelineText, toolTipAdded, lineNumber }) => { + const states = wrapper.findAll('[data-testid="terraform-states-table-pipeline"]'); + const state = states.at(lineNumber); + + expect(state.find(GlTooltip).exists()).toBe(toolTipAdded); + expect(state.text()).toMatchInterpolatedText(pipelineText); + }, + ); + it('displays no actions dropdown', () => { expect(findActions().length).toEqual(0); }); diff --git a/yarn.lock b/yarn.lock index 1ef53f01f83..695f2190ec8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -861,15 +861,15 @@ eslint-plugin-vue "^6.2.1" vue-eslint-parser "^7.0.0" -"@gitlab/svgs@1.176.0": - version "1.176.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.176.0.tgz#3b6d415704ed36db87146cfa361f3f9e2c01f535" - integrity sha512-cl3SlGmLF/bnPAxrg52eRQOxRSrU54AB3yfV9TLi2iNAg/Jws0nuvdVZ4LRKKyyzHmT6yfVusCIGCISzvON6ew== - -"@gitlab/ui@24.6.0": - version "24.6.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-24.6.0.tgz#e27a00a12e264e993147b31f1965cd3fa861eef4" - integrity sha512-Gb2mmQyd34T2DNKiGHauqy9T1JMxh6vVshIMSM0nfNnnl66p1FJwlAg0e1scZml1DYaXaDGVDtGkvOqszeE7GQ== +"@gitlab/svgs@1.177.0": + version "1.177.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.177.0.tgz#e481ed327a11d3834c8b1668d7485b9eefef97f5" + integrity sha512-L7DggusgkbubNFCRIYtCuYiLx+t5Hp8y/XIxJ3RM5mqAfxkTR1KxALNLDP9CT7xWieHDhNvgcXAdamGoi0ofDQ== + +"@gitlab/ui@24.7.0": + version "24.7.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-24.7.0.tgz#7cd0eb0fee4b24f62f1ae8c375ef2c488506c397" + integrity sha512-MR/QyJZYwiHaIuOqEEjSaAu4xfnKSgg503mUlfRhvdLoEMWqAhIs8LjRLqdIV72yVsXmw/HKht16XWpbibg7yQ== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.3.0" |