Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e168d3919a
commit
ece36a2169
|
|
@ -1 +1 @@
|
|||
v16.0.0-rc2
|
||||
v16.0.0
|
||||
|
|
|
|||
|
|
@ -130,8 +130,11 @@ export default {
|
|||
},
|
||||
},
|
||||
watch: {
|
||||
message() {
|
||||
this.renderPreview();
|
||||
message: {
|
||||
handler() {
|
||||
this.renderPreview();
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -1,24 +1,42 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { refreshCurrentPage, queryToObject } from '~/lib/utils/url_utility';
|
||||
import { s__ } from '~/locale';
|
||||
import BoardContent from '~/boards/components/board_content.vue';
|
||||
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
|
||||
import BoardTopBar from '~/boards/components/board_top_bar.vue';
|
||||
import { listsQuery } from 'ee_else_ce/boards/constants';
|
||||
import { formatBoardLists } from 'ee_else_ce/boards/boards_util';
|
||||
import activeBoardItemQuery from 'ee_else_ce/boards/graphql/client/active_board_item.query.graphql';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
fetchError: s__(
|
||||
'Boards|An error occurred while fetching the board lists. Please reload the page.',
|
||||
),
|
||||
},
|
||||
components: {
|
||||
BoardContent,
|
||||
BoardSettingsSidebar,
|
||||
BoardTopBar,
|
||||
},
|
||||
inject: ['initialBoardId', 'initialFilterParams', 'isIssueBoard', 'isApolloBoard'],
|
||||
inject: [
|
||||
'fullPath',
|
||||
'initialBoardId',
|
||||
'initialFilterParams',
|
||||
'isIssueBoard',
|
||||
'isGroupBoard',
|
||||
'issuableType',
|
||||
'boardType',
|
||||
'isApolloBoard',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
activeListId: '',
|
||||
boardId: this.initialBoardId,
|
||||
filterParams: { ...this.initialFilterParams },
|
||||
isShowingEpicsSwimlanes: Boolean(queryToObject(window.location.search).group_by),
|
||||
apolloError: null,
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
|
|
@ -38,10 +56,39 @@ export default {
|
|||
return !this.isApolloBoard;
|
||||
},
|
||||
},
|
||||
boardListsApollo: {
|
||||
query() {
|
||||
return listsQuery[this.issuableType].query;
|
||||
},
|
||||
variables() {
|
||||
return this.listQueryVariables;
|
||||
},
|
||||
skip() {
|
||||
return !this.isApolloBoard;
|
||||
},
|
||||
update(data) {
|
||||
const { lists } = data[this.boardType].board;
|
||||
return formatBoardLists(lists);
|
||||
},
|
||||
error() {
|
||||
this.apolloError = this.$options.i18n.fetchError;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['isSidebarOpen']),
|
||||
listQueryVariables() {
|
||||
return {
|
||||
...(this.isIssueBoard && {
|
||||
isGroup: this.isGroupBoard,
|
||||
isProject: !this.isGroupBoard,
|
||||
}),
|
||||
fullPath: this.fullPath,
|
||||
boardId: this.boardId,
|
||||
filters: this.filterParams,
|
||||
};
|
||||
},
|
||||
isSwimlanesOn() {
|
||||
return (gon?.licensed_features?.swimlanes && this.isShowingEpicsSwimlanes) ?? false;
|
||||
},
|
||||
|
|
@ -51,6 +98,9 @@ export default {
|
|||
}
|
||||
return this.isSidebarOpen;
|
||||
},
|
||||
activeList() {
|
||||
return this.activeListId ? this.boardListsApollo[this.activeListId] : undefined;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('popstate', refreshCurrentPage);
|
||||
|
|
@ -85,14 +135,20 @@ export default {
|
|||
@toggleSwimlanes="isShowingEpicsSwimlanes = $event"
|
||||
/>
|
||||
<board-content
|
||||
v-if="!isApolloBoard || boardListsApollo"
|
||||
:board-id="boardId"
|
||||
:is-swimlanes-on="isSwimlanesOn"
|
||||
:filter-params="filterParams"
|
||||
:board-lists-apollo="boardListsApollo"
|
||||
:apollo-error="apolloError"
|
||||
@setActiveList="setActiveId"
|
||||
/>
|
||||
<board-settings-sidebar
|
||||
v-if="!isApolloBoard || activeList"
|
||||
:list="activeList"
|
||||
:list-id="activeListId"
|
||||
:board-id="boardId"
|
||||
:query-variables="listQueryVariables"
|
||||
@unsetActiveId="setActiveId('')"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,20 +5,13 @@ import { sortBy, throttle } from 'lodash';
|
|||
import Draggable from 'vuedraggable';
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { contentTop } from '~/lib/utils/common_utils';
|
||||
import { s__ } from '~/locale';
|
||||
import eventHub from '~/boards/eventhub';
|
||||
import { formatBoardLists } from 'ee_else_ce/boards/boards_util';
|
||||
import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
|
||||
import { defaultSortableOptions } from '~/sortable/constants';
|
||||
import { DraggableItemTypes, listsQuery } from 'ee_else_ce/boards/constants';
|
||||
import { DraggableItemTypes } from 'ee_else_ce/boards/constants';
|
||||
import BoardColumn from './board_column.vue';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
fetchError: s__(
|
||||
'Boards|An error occurred while fetching the board lists. Please reload the page.',
|
||||
),
|
||||
},
|
||||
draggableItemTypes: DraggableItemTypes,
|
||||
components: {
|
||||
BoardAddNewColumn,
|
||||
|
|
@ -29,17 +22,7 @@ export default {
|
|||
EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
|
||||
GlAlert,
|
||||
},
|
||||
inject: [
|
||||
'canAdminList',
|
||||
'boardType',
|
||||
'fullPath',
|
||||
'issuableType',
|
||||
'isIssueBoard',
|
||||
'isEpicBoard',
|
||||
'isGroupBoard',
|
||||
'disabled',
|
||||
'isApolloBoard',
|
||||
],
|
||||
inject: ['canAdminList', 'isIssueBoard', 'isEpicBoard', 'disabled', 'isApolloBoard'],
|
||||
props: {
|
||||
boardId: {
|
||||
type: String,
|
||||
|
|
@ -53,56 +36,27 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
boardListsApollo: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {},
|
||||
},
|
||||
apolloError: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
boardHeight: null,
|
||||
boardListsApollo: {},
|
||||
apolloError: null,
|
||||
updatedBoardId: this.boardId,
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
boardListsApollo: {
|
||||
query() {
|
||||
return listsQuery[this.issuableType].query;
|
||||
},
|
||||
variables() {
|
||||
return this.queryVariables;
|
||||
},
|
||||
skip() {
|
||||
return !this.isApolloBoard;
|
||||
},
|
||||
update(data) {
|
||||
const { lists } = data[this.boardType].board;
|
||||
return formatBoardLists(lists);
|
||||
},
|
||||
result() {
|
||||
// this allows us to delay fetching lists when we switch a board to fetch the actual board lists
|
||||
// instead of fetching lists for the "previous" board
|
||||
this.updatedBoardId = this.boardId;
|
||||
},
|
||||
error() {
|
||||
this.apolloError = this.$options.i18n.fetchError;
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['boardLists', 'error', 'addColumnForm']),
|
||||
addColumnFormVisible() {
|
||||
return this.addColumnForm?.visible;
|
||||
},
|
||||
queryVariables() {
|
||||
return {
|
||||
...(this.isIssueBoard && {
|
||||
isGroup: this.isGroupBoard,
|
||||
isProject: !this.isGroupBoard,
|
||||
}),
|
||||
fullPath: this.fullPath,
|
||||
boardId: this.boardId,
|
||||
filters: this.filterParams,
|
||||
};
|
||||
},
|
||||
boardListsToUse() {
|
||||
const lists = this.isApolloBoard ? this.boardListsApollo : this.boardLists;
|
||||
return sortBy([...Object.values(lists)], 'position');
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
<script>
|
||||
import { GlButton, GlDrawer, GlLabel, GlLoadingIcon, GlModal, GlModalDirective } from '@gitlab/ui';
|
||||
import produce from 'immer';
|
||||
import { GlButton, GlDrawer, GlLabel, GlModal, GlModalDirective } from '@gitlab/ui';
|
||||
import { MountingPortal } from 'portal-vue';
|
||||
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||
import { LIST, ListType, ListTypeTitles, listsQuery } from 'ee_else_ce/boards/constants';
|
||||
import {
|
||||
LIST,
|
||||
ListType,
|
||||
ListTypeTitles,
|
||||
listsQuery,
|
||||
deleteListQueries,
|
||||
} from 'ee_else_ce/boards/constants';
|
||||
import { isScopedLabel } from '~/lib/utils/common_utils';
|
||||
import { __ } from '~/locale';
|
||||
import eventHub from '~/sidebar/event_hub';
|
||||
|
|
@ -21,7 +28,6 @@ export default {
|
|||
GlModal,
|
||||
GlDrawer,
|
||||
GlLabel,
|
||||
GlLoadingIcon,
|
||||
MountingPortal,
|
||||
BoardSettingsSidebarWipLimit: () =>
|
||||
import('ee_component/boards/components/board_settings_wip_limit.vue'),
|
||||
|
|
@ -35,11 +41,9 @@ export default {
|
|||
inject: [
|
||||
'boardType',
|
||||
'canAdminList',
|
||||
'fullPath',
|
||||
'issuableType',
|
||||
'scopedLabelsAvailable',
|
||||
'isIssueBoard',
|
||||
'isGroupBoard',
|
||||
'isApolloBoard',
|
||||
],
|
||||
inheritAttrs: false,
|
||||
|
|
@ -52,57 +56,33 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
list: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => null,
|
||||
},
|
||||
queryVariables: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ListType,
|
||||
list: {},
|
||||
};
|
||||
},
|
||||
modalId: 'board-settings-sidebar-modal',
|
||||
apollo: {
|
||||
list: {
|
||||
query() {
|
||||
return listsQuery[this.issuableType].query;
|
||||
},
|
||||
variables() {
|
||||
return this.queryVariables;
|
||||
},
|
||||
update(data) {
|
||||
const { lists } = data[this.boardType].board;
|
||||
return lists.nodes[0];
|
||||
},
|
||||
skip() {
|
||||
return !this.isApolloBoard || !this.listId;
|
||||
},
|
||||
error() {
|
||||
this.error = this.$options.i18n.fetchError;
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isSidebarOpen']),
|
||||
...mapState(['activeId', 'sidebarType', 'boardLists']),
|
||||
isWipLimitsOn() {
|
||||
return this.glFeatures.wipLimits && this.isIssueBoard;
|
||||
},
|
||||
queryVariables() {
|
||||
return {
|
||||
...(this.isIssueBoard && {
|
||||
isGroup: this.isGroupBoard,
|
||||
isProject: !this.isGroupBoard,
|
||||
}),
|
||||
fullPath: this.fullPath,
|
||||
boardId: this.boardId,
|
||||
filters: this.filterParams,
|
||||
listId: this.activeListId,
|
||||
};
|
||||
},
|
||||
activeListId() {
|
||||
return this.isApolloBoard ? this.listId : this.activeId;
|
||||
},
|
||||
activeList() {
|
||||
return this.isApolloBoard ? this.list : this.boardLists[this.activeId] || {};
|
||||
return (this.isApolloBoard ? this.list : this.boardLists[this.activeId]) || {};
|
||||
},
|
||||
activeListLabel() {
|
||||
return this.activeList.label;
|
||||
|
|
@ -119,9 +99,6 @@ export default {
|
|||
}
|
||||
return this.sidebarType === LIST && this.isSidebarOpen;
|
||||
},
|
||||
isLoading() {
|
||||
return this.isApolloBoard && this.$apollo.queries.list.loading;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
eventHub.$on('sidebar.closeAll', this.unsetActiveListId);
|
||||
|
|
@ -132,14 +109,18 @@ export default {
|
|||
methods: {
|
||||
...mapActions(['unsetActiveId', 'removeList']),
|
||||
handleModalPrimary() {
|
||||
this.deleteBoard();
|
||||
this.deleteBoardList();
|
||||
},
|
||||
showScopedLabels(label) {
|
||||
return this.scopedLabelsAvailable && isScopedLabel(label);
|
||||
},
|
||||
deleteBoard() {
|
||||
async deleteBoardList() {
|
||||
this.track('click_button', { label: 'remove_list' });
|
||||
this.removeList(this.activeId);
|
||||
if (this.isApolloBoard) {
|
||||
await this.deleteList(this.activeListId);
|
||||
} else {
|
||||
this.removeList(this.activeId);
|
||||
}
|
||||
this.unsetActiveListId();
|
||||
},
|
||||
unsetActiveListId() {
|
||||
|
|
@ -149,6 +130,25 @@ export default {
|
|||
this.unsetActiveId();
|
||||
}
|
||||
},
|
||||
async deleteList(listId) {
|
||||
await this.$apollo.mutate({
|
||||
mutation: deleteListQueries[this.issuableType].mutation,
|
||||
variables: {
|
||||
listId,
|
||||
},
|
||||
update: (store) => {
|
||||
store.updateQuery(
|
||||
{ query: listsQuery[this.issuableType].query, variables: this.queryVariables },
|
||||
(sourceData) =>
|
||||
produce(sourceData, (draftData) => {
|
||||
draftData[this.boardType].board.lists.nodes = draftData[
|
||||
this.boardType
|
||||
].board.lists.nodes.filter((list) => list.id !== listId);
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -166,9 +166,8 @@ export default {
|
|||
<h2 class="gl-my-0 gl-font-size-h2 gl-line-height-24">
|
||||
{{ $options.listSettingsText }}
|
||||
</h2>
|
||||
<gl-loading-icon v-if="isLoading" />
|
||||
</template>
|
||||
<template v-if="!isLoading" #header>
|
||||
<template #header>
|
||||
<div v-if="canAdminList && activeList.id" class="gl-mt-3">
|
||||
<gl-button
|
||||
v-gl-modal="$options.modalId"
|
||||
|
|
@ -179,7 +178,7 @@ export default {
|
|||
</gl-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="showSidebar && !isLoading">
|
||||
<template v-if="showSidebar">
|
||||
<div v-if="boardListType === ListType.label">
|
||||
<label class="js-list-label gl-display-block">{{ listTypeTitle }}</label>
|
||||
<gl-label
|
||||
|
|
|
|||
|
|
@ -144,6 +144,9 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions(['setError', 'fetchBoard', 'unsetActiveId']),
|
||||
fullBoardId(boardId) {
|
||||
return fullBoardId(boardId);
|
||||
},
|
||||
showPage(page) {
|
||||
this.currentPage = page;
|
||||
},
|
||||
|
|
@ -254,7 +257,8 @@ export default {
|
|||
if (isMetaKey(e)) {
|
||||
window.open(`${this.boardBaseUrl}/${boardId}`, '_blank');
|
||||
} else if (this.isApolloBoard) {
|
||||
this.$emit('switchBoard', fullBoardId(boardId));
|
||||
// Epic board ID is supported in EE version of this file
|
||||
this.$emit('switchBoard', this.fullBoardId(boardId));
|
||||
updateHistory({ url: `${this.boardBaseUrl}/${boardId}` });
|
||||
} else {
|
||||
this.unsetActiveId();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
<script>
|
||||
import { GlButton, GlFormInput, GlModal, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { GlDisclosureDropdown, GlButton, GlFormInput, GlModal, GlSprintf } from '@gitlab/ui';
|
||||
import csrf from '~/lib/utils/csrf';
|
||||
import { sprintf, s__, __ } from '~/locale';
|
||||
|
||||
export const i18n = {
|
||||
deleteButtonText: s__('Branches|Delete merged branches'),
|
||||
buttonTooltipText: s__("Branches|Delete all branches that are merged into '%{defaultBranch}'"),
|
||||
modalTitle: s__('Branches|Delete all merged branches?'),
|
||||
modalMessage: s__(
|
||||
'Branches|You are about to %{strongStart}delete all branches%{strongEnd} that were merged into %{codeStart}%{defaultBranch}%{codeEnd}.',
|
||||
|
|
@ -28,14 +27,12 @@ export const i18n = {
|
|||
export default {
|
||||
csrf,
|
||||
components: {
|
||||
GlModal,
|
||||
GlDisclosureDropdown,
|
||||
GlButton,
|
||||
GlModal,
|
||||
GlFormInput,
|
||||
GlSprintf,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
formPath: {
|
||||
type: String,
|
||||
|
|
@ -53,9 +50,6 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
buttonTooltipText() {
|
||||
return sprintf(this.$options.i18n.buttonTooltipText, { defaultBranch: this.defaultBranch });
|
||||
},
|
||||
modalMessage() {
|
||||
return sprintf(this.$options.i18n.modalMessage, {
|
||||
defaultBranch: this.defaultBranch,
|
||||
|
|
@ -67,6 +61,20 @@ export default {
|
|||
isDeleteButtonDisabled() {
|
||||
return !this.isDeletingConfirmed;
|
||||
},
|
||||
dropdownItems() {
|
||||
return [
|
||||
{
|
||||
text: this.$options.i18n.deleteButtonText,
|
||||
action: () => {
|
||||
this.openModal();
|
||||
},
|
||||
extraAttrs: {
|
||||
'data-qa-selector': 'delete_merged_branches_button',
|
||||
class: 'gl-text-red-500!',
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
|
|
@ -87,15 +95,15 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<gl-button
|
||||
v-gl-tooltip="buttonTooltipText"
|
||||
class="gl-mr-3"
|
||||
data-qa-selector="delete_merged_branches_button"
|
||||
category="secondary"
|
||||
variant="danger"
|
||||
@click="openModal"
|
||||
>{{ $options.i18n.deleteButtonText }}
|
||||
</gl-button>
|
||||
<gl-disclosure-dropdown
|
||||
:toggle-text="$options.i18n.actionsToggleText"
|
||||
text-sr-only
|
||||
icon="ellipsis_v"
|
||||
category="tertiary"
|
||||
no-caret
|
||||
placement="right"
|
||||
:items="dropdownItems"
|
||||
/>
|
||||
<gl-modal
|
||||
ref="modal"
|
||||
size="sm"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
|
||||
include MetricsDashboard
|
||||
include ProductAnalyticsTracking
|
||||
include KasCookie
|
||||
|
||||
layout 'project'
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics, :cancel_auto_stop]
|
||||
before_action :verify_api_request!, only: :terminal_websocket_authorize
|
||||
before_action :expire_etag_cache, only: [:index], unless: -> { request.format.json? }
|
||||
before_action :set_kas_cookie, only: [:index], if: -> { current_user }
|
||||
after_action :expire_etag_cache, only: [:cancel_auto_stop]
|
||||
|
||||
track_event :index, :folder, :show, :new, :edit, :create, :update, :stop, :cancel_auto_stop, :terminal,
|
||||
|
|
|
|||
|
|
@ -24,11 +24,6 @@
|
|||
sorted_by: @sort }
|
||||
}
|
||||
|
||||
- if can_push_code
|
||||
.js-delete-merged-branches{ data: {
|
||||
default_branch: @project.repository.root_ref,
|
||||
form_path: project_merged_branches_path(@project) }
|
||||
}
|
||||
- if is_branch_rules_available
|
||||
= link_to project_settings_repository_path(@project, anchor: 'js-branch-rules'), class: 'gl-button btn btn-default' do
|
||||
= s_('Branches|View branch rules')
|
||||
|
|
@ -36,6 +31,10 @@
|
|||
- if can_push_code
|
||||
= link_to new_project_branch_path(@project), class: 'gl-button btn btn-confirm' do
|
||||
= s_('Branches|New branch')
|
||||
.js-delete-merged-branches{ data: {
|
||||
default_branch: @project.repository.root_ref,
|
||||
form_path: project_merged_branches_path(@project) }
|
||||
}
|
||||
|
||||
= render_if_exists 'projects/commits/mirror_status'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: environment_search_api_min_chars
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108277
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/387244
|
||||
milestone: '15.8'
|
||||
type: development
|
||||
group: group::configure
|
||||
default_enabled: false
|
||||
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381456
|
|||
milestone: '15.7'
|
||||
type: development
|
||||
group: group::foundations
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ data_category: optional
|
|||
key_path: redis_hll_counters.deploy_token_packages.i_package_terraform_module_deploy_token_monthly
|
||||
description: Number of distinct users authorized via deploy token creating Terraform Module packages in recent 28 days
|
||||
product_section: ops
|
||||
product_stage: configure
|
||||
product_group: configure
|
||||
product_stage: package
|
||||
product_group: package
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '13.11'
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ data_category: optional
|
|||
key_path: redis_hll_counters.user_packages.i_package_terraform_module_user_monthly
|
||||
description: Number of distinct users creating Terraform Module packages in recent 28 days
|
||||
product_section: ops
|
||||
product_stage: configure
|
||||
product_group: configure
|
||||
product_stage: package
|
||||
product_group: package
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '13.11'
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ data_category: optional
|
|||
key_path: counts.package_events_i_package_terraform_module_delete_package
|
||||
description: Total count of Terraform Module packages delete events
|
||||
product_section: ops
|
||||
product_stage: configure
|
||||
product_group: configure
|
||||
product_stage: package
|
||||
product_group: package
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '13.11'
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ data_category: optional
|
|||
key_path: counts.package_events_i_package_terraform_module_pull_package
|
||||
description: Total count of pull Terraform Module packages events
|
||||
product_section: ops
|
||||
product_stage: configure
|
||||
product_group: configure
|
||||
product_stage: package
|
||||
product_group: package
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '13.11'
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ data_category: optional
|
|||
key_path: counts.package_events_i_package_terraform_module_push_package
|
||||
description: Total count of push Terraform Module packages events
|
||||
product_section: ops
|
||||
product_stage: configure
|
||||
product_group: configure
|
||||
product_stage: package
|
||||
product_group: package
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '13.11'
|
||||
|
|
|
|||
|
|
@ -1031,6 +1031,7 @@ Input type: `AiActionInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationaiactionclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationaiactionerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationaiactionrequestid"></a>`requestId` | [`String`](#string) | ID of the request. |
|
||||
|
||||
### `Mutation.alertSetAssignees`
|
||||
|
||||
|
|
@ -11367,6 +11368,7 @@ Information about a connected Agent.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="airesponseerrors"></a>`errors` | [`[String!]`](#string) | Errors return by AI API as response. |
|
||||
| <a id="airesponserequestid"></a>`requestId` | [`String`](#string) | ID of the original request. |
|
||||
| <a id="airesponseresponsebody"></a>`responseBody` | [`String`](#string) | Response body from AI API. |
|
||||
|
||||
### `AlertManagementAlert`
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ sequenceDiagram
|
|||
Note right of Cloud: Create role with conditionals
|
||||
Note left of GitLab: CI/CD job with ID token
|
||||
GitLab->>+Cloud: Call cloud API with ID token
|
||||
Note right of Cloud: Decode & verify JWT with public key (https://gitlab/-/jwks)
|
||||
Note right of Cloud: Decode & verify JWT with public key (https://gitlab.com/oauth/discovery/keys)
|
||||
Note right of Cloud: Validate audience defined in OIDC
|
||||
Note right of Cloud: Validate conditional (sub, aud) role
|
||||
Note right of Cloud: Generate credential or fetch secret
|
||||
|
|
|
|||
|
|
@ -29,12 +29,11 @@ restore. This is because the system user performing the restore actions (`git`)
|
|||
is usually not allowed to create or delete the SQL database needed to import
|
||||
data into (`gitlabhq_production`). All existing data is either erased
|
||||
(SQL) or moved to a separate directory (such as repositories and uploads).
|
||||
Restoring SQL data skips views owned by PostgreSQL extensions.
|
||||
|
||||
To restore a backup, **you must also restore the GitLab secrets**.
|
||||
|
||||
These include the database encryption key, [CI/CD variables](../ci/variables/index.md), and
|
||||
variables used for [two-factor authentication](../user/profile/account/two_factor_authentication.md).
|
||||
|
||||
Without the keys, [multiple issues occur](backup_restore.md#when-the-secrets-file-is-lost),
|
||||
including loss of access by users with [two-factor authentication enabled](../user/profile/account/two_factor_authentication.md),
|
||||
and GitLab Runners cannot log in.
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ The following stage events are available:
|
|||
- Issue closed
|
||||
- Issue created
|
||||
- Issue first added to board
|
||||
- Issue first assigned
|
||||
- Issue first associated with milestone
|
||||
- Issue first mentioned
|
||||
- Issue label added
|
||||
|
|
@ -86,6 +87,7 @@ The following stage events are available:
|
|||
- MR merged
|
||||
- MR created
|
||||
- MR first commit time
|
||||
- MR first assigned
|
||||
- MR first deployed
|
||||
- MR label added
|
||||
- MR label removed
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ module API
|
|||
get ':id/environments' do
|
||||
authorize! :read_environment, user_project
|
||||
|
||||
if Feature.enabled?(:environment_search_api_min_chars, user_project) && params[:search].present? && params[:search].length < MIN_SEARCH_LENGTH
|
||||
if params[:search].present? && params[:search].length < MIN_SEARCH_LENGTH
|
||||
bad_request!("Search query is less than #{MIN_SEARCH_LENGTH} characters")
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ module Feature
|
|||
end
|
||||
|
||||
def l2_cache_backend
|
||||
Rails.cache
|
||||
::Gitlab::Redis::FeatureFlag.cache_store
|
||||
end
|
||||
|
||||
def log(key:, action:, **extra)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ module Gitlab
|
|||
ALL_CLASSES = [
|
||||
Gitlab::Redis::Cache,
|
||||
Gitlab::Redis::DbLoadBalancing,
|
||||
Gitlab::Redis::FeatureFlag,
|
||||
Gitlab::Redis::Queues,
|
||||
Gitlab::Redis::RateLimiting,
|
||||
Gitlab::Redis::RepositoryCache,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Redis
|
||||
class FeatureFlag < ::Gitlab::Redis::Wrapper
|
||||
FeatureFlagStore = Class.new(ActiveSupport::Cache::RedisCacheStore)
|
||||
|
||||
class << self
|
||||
# The data we store on FeatureFlag is currently stored on Cache.
|
||||
def config_fallback
|
||||
Cache
|
||||
end
|
||||
|
||||
def cache_store
|
||||
@cache_store ||= FeatureFlagStore.new(
|
||||
redis: pool,
|
||||
compress: Gitlab::Utils.to_boolean(ENV.fetch('ENABLE_REDIS_CACHE_COMPRESSION', '1')),
|
||||
namespace: Cache::CACHE_NAMESPACE,
|
||||
expires_in: 1.hour
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -7903,9 +7903,6 @@ msgstr ""
|
|||
msgid "Branches|Compare"
|
||||
msgstr ""
|
||||
|
||||
msgid "Branches|Delete all branches that are merged into '%{defaultBranch}'"
|
||||
msgstr ""
|
||||
|
||||
msgid "Branches|Delete all merged branches?"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -144,8 +144,8 @@ describe('MessageForm', () => {
|
|||
findForm().vm.$emit('submit', { preventDefault: () => {} });
|
||||
await waitForPromises();
|
||||
|
||||
expect(axiosMock.history.post).toHaveLength(1);
|
||||
expect(axiosMock.history.post[0]).toMatchObject({
|
||||
expect(axiosMock.history.post).toHaveLength(2);
|
||||
expect(axiosMock.history.post[1]).toMatchObject({
|
||||
url: messagesPath,
|
||||
data: JSON.stringify(defaultPayload),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ import Vuex from 'vuex';
|
|||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import BoardApp from '~/boards/components/board_app.vue';
|
||||
import activeBoardItemQuery from 'ee_else_ce/boards/graphql/client/active_board_item.query.graphql';
|
||||
import { rawIssue } from '../mock_data';
|
||||
import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
|
||||
import { rawIssue, boardListsQueryResponse } from '../mock_data';
|
||||
|
||||
describe('BoardApp', () => {
|
||||
let wrapper;
|
||||
let store;
|
||||
const mockApollo = createMockApollo();
|
||||
const boardListQueryHandler = jest.fn().mockResolvedValue(boardListsQueryResponse);
|
||||
const mockApollo = createMockApollo([[boardListsQuery, boardListQueryHandler]]);
|
||||
|
||||
Vue.use(Vuex);
|
||||
Vue.use(VueApollo);
|
||||
|
|
@ -41,9 +43,13 @@ describe('BoardApp', () => {
|
|||
apolloProvider: mockApollo,
|
||||
store,
|
||||
provide: {
|
||||
fullPath: 'gitlab-org',
|
||||
initialBoardId: 'gid://gitlab/Board/1',
|
||||
initialFilterParams: {},
|
||||
issuableType: 'issue',
|
||||
boardType: 'group',
|
||||
isIssueBoard: true,
|
||||
isGroupBoard: true,
|
||||
isApolloBoard,
|
||||
},
|
||||
});
|
||||
|
|
@ -73,6 +79,10 @@ describe('BoardApp', () => {
|
|||
await nextTick();
|
||||
});
|
||||
|
||||
it('fetches lists', () => {
|
||||
expect(boardListQueryHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have is-compact class when a card is selected', () => {
|
||||
expect(wrapper.classes()).toContain('is-compact');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,23 +1,19 @@
|
|||
import { GlAlert } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import Draggable from 'vuedraggable';
|
||||
import Vuex from 'vuex';
|
||||
|
||||
import eventHub from '~/boards/eventhub';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
|
||||
import getters from 'ee_else_ce/boards/stores/getters';
|
||||
import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
|
||||
import BoardColumn from '~/boards/components/board_column.vue';
|
||||
import BoardContent from '~/boards/components/board_content.vue';
|
||||
import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue';
|
||||
import { mockLists, boardListsQueryResponse } from '../mock_data';
|
||||
import { mockLists, mockListsById } from '../mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
Vue.use(Vuex);
|
||||
|
||||
const actions = {
|
||||
|
|
@ -26,7 +22,6 @@ const actions = {
|
|||
|
||||
describe('BoardContent', () => {
|
||||
let wrapper;
|
||||
let fakeApollo;
|
||||
|
||||
const defaultState = {
|
||||
isShowingEpicsSwimlanes: false,
|
||||
|
|
@ -51,26 +46,21 @@ describe('BoardContent', () => {
|
|||
issuableType = 'issue',
|
||||
isIssueBoard = true,
|
||||
isEpicBoard = false,
|
||||
boardListQueryHandler = jest.fn().mockResolvedValue(boardListsQueryResponse),
|
||||
} = {}) => {
|
||||
fakeApollo = createMockApollo([[boardListsQuery, boardListQueryHandler]]);
|
||||
|
||||
const store = createStore({
|
||||
...defaultState,
|
||||
...state,
|
||||
});
|
||||
wrapper = shallowMount(BoardContent, {
|
||||
apolloProvider: fakeApollo,
|
||||
propsData: {
|
||||
boardId: 'gid://gitlab/Board/1',
|
||||
filterParams: {},
|
||||
isSwimlanesOn: false,
|
||||
boardListsApollo: mockListsById,
|
||||
...props,
|
||||
},
|
||||
provide: {
|
||||
canAdminList,
|
||||
boardType: 'group',
|
||||
fullPath: 'gitlab-org/gitlab',
|
||||
issuableType,
|
||||
isIssueBoard,
|
||||
isEpicBoard,
|
||||
|
|
@ -110,10 +100,6 @@ describe('BoardContent', () => {
|
|||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fakeApollo = null;
|
||||
});
|
||||
|
||||
describe('default', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ import { stubComponent } from 'helpers/stub_component';
|
|||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
|
||||
import { inactiveId, LIST } from '~/boards/constants';
|
||||
import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql';
|
||||
import actions from '~/boards/stores/actions';
|
||||
import getters from '~/boards/stores/getters';
|
||||
import mutations from '~/boards/stores/mutations';
|
||||
import sidebarEventHub from '~/sidebar/event_hub';
|
||||
import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
|
||||
import { mockLabelList, issueBoardListsQueryResponse } from '../mock_data';
|
||||
import { mockLabelList, destroyBoardListMutationResponse } from '../mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
Vue.use(Vuex);
|
||||
|
|
@ -28,7 +28,9 @@ describe('BoardSettingsSidebar', () => {
|
|||
const listId = mockLabelList.id;
|
||||
const modalID = 'board-settings-sidebar-modal';
|
||||
|
||||
const boardListQueryHandler = jest.fn().mockResolvedValue(issueBoardListsQueryResponse);
|
||||
const destroyBoardListMutationHandlerSuccess = jest
|
||||
.fn()
|
||||
.mockResolvedValue(destroyBoardListMutationResponse);
|
||||
|
||||
const createComponent = ({
|
||||
canAdminList = false,
|
||||
|
|
@ -46,25 +48,28 @@ describe('BoardSettingsSidebar', () => {
|
|||
mutations,
|
||||
actions,
|
||||
});
|
||||
mockApollo = createMockApollo([[boardListsQuery, boardListQueryHandler]]);
|
||||
|
||||
mockApollo = createMockApollo([
|
||||
[destroyBoardListMutation, destroyBoardListMutationHandlerSuccess],
|
||||
]);
|
||||
|
||||
wrapper = extendedWrapper(
|
||||
shallowMount(BoardSettingsSidebar, {
|
||||
apolloProvider: mockApollo,
|
||||
store,
|
||||
apolloProvider: mockApollo,
|
||||
provide: {
|
||||
canAdminList,
|
||||
scopedLabelsAvailable: false,
|
||||
isIssueBoard: true,
|
||||
boardType: 'group',
|
||||
fullPath: 'gitlab-org',
|
||||
issuableType: 'issue',
|
||||
isGroupBoard: true,
|
||||
isApolloBoard,
|
||||
},
|
||||
propsData: {
|
||||
listId: 'gid://gitlab/List/1',
|
||||
listId: list.id || '',
|
||||
boardId: 'gid://gitlab/Board/1',
|
||||
list,
|
||||
queryVariables: {},
|
||||
},
|
||||
directives: {
|
||||
GlModal: createMockDirective('gl-modal'),
|
||||
|
|
@ -76,6 +81,9 @@ describe('BoardSettingsSidebar', () => {
|
|||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Necessary for cache update
|
||||
mockApollo.clients.defaultClient.cache.updateQuery = jest.fn();
|
||||
};
|
||||
const findLabel = () => wrapper.findComponent(GlLabel);
|
||||
const findDrawer = () => wrapper.findComponent(GlDrawer);
|
||||
|
|
@ -185,6 +193,21 @@ describe('BoardSettingsSidebar', () => {
|
|||
expect(findRemoveButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('removes the list', () => {
|
||||
createComponent({
|
||||
canAdminList: true,
|
||||
activeId: listId,
|
||||
list: mockLabelList,
|
||||
isApolloBoard: true,
|
||||
});
|
||||
|
||||
findRemoveButton().vm.$emit('click');
|
||||
|
||||
wrapper.findComponent(GlModal).vm.$emit('primary');
|
||||
|
||||
expect(destroyBoardListMutationHandlerSuccess).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('has the correct ID on the button', () => {
|
||||
createComponent({ canAdminList: true, activeId: listId, list: mockLabelList });
|
||||
const binding = getBinding(findRemoveButton().element, 'gl-modal');
|
||||
|
|
@ -196,12 +219,4 @@ describe('BoardSettingsSidebar', () => {
|
|||
expect(findModal().props('modalId')).toBe(modalID);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Apollo boards', () => {
|
||||
it('fetches list', () => {
|
||||
createComponent({ isApolloBoard: true });
|
||||
|
||||
expect(boardListQueryHandler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -997,4 +997,13 @@ export const updateBoardListResponse = {
|
|||
},
|
||||
};
|
||||
|
||||
export const destroyBoardListMutationResponse = {
|
||||
data: {
|
||||
destroyBoardList: {
|
||||
errors: [],
|
||||
__typename: 'DestroyBoardListPayload',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DEFAULT_COLOR = '#1068bf';
|
||||
|
|
|
|||
|
|
@ -2,25 +2,33 @@
|
|||
|
||||
exports[`Delete merged branches component Delete merged branches confirmation modal matches snapshot 1`] = `
|
||||
<div>
|
||||
<b-button-stub
|
||||
class="gl-mr-3 gl-button btn-danger-secondary"
|
||||
data-qa-selector="delete_merged_branches_button"
|
||||
size="md"
|
||||
tag="button"
|
||||
type="button"
|
||||
variant="danger"
|
||||
<gl-base-dropdown-stub
|
||||
category="tertiary"
|
||||
class="gl-disclosure-dropdown"
|
||||
icon="ellipsis_v"
|
||||
nocaret="true"
|
||||
placement="right"
|
||||
popperoptions="[object Object]"
|
||||
size="medium"
|
||||
textsronly="true"
|
||||
toggleid="dropdown-toggle-btn-25"
|
||||
toggletext=""
|
||||
variant="default"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="gl-button-text"
|
||||
<ul
|
||||
aria-labelledby="dropdown-toggle-btn-25"
|
||||
class="gl-new-dropdown-contents"
|
||||
data-testid="disclosure-content"
|
||||
id="disclosure-26"
|
||||
tabindex="-1"
|
||||
>
|
||||
Delete merged branches
|
||||
|
||||
</span>
|
||||
</b-button-stub>
|
||||
<gl-disclosure-dropdown-item-stub
|
||||
item="[object Object]"
|
||||
/>
|
||||
</ul>
|
||||
|
||||
</gl-base-dropdown-stub>
|
||||
|
||||
<div>
|
||||
<form
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { GlButton, GlFormInput, GlModal, GlSprintf } from '@gitlab/ui';
|
||||
import { GlDisclosureDropdown, GlButton, GlFormInput, GlModal, GlSprintf } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import DeleteMergedBranches, { i18n } from '~/branches/components/delete_merged_branches.vue';
|
||||
import { formPath, propsDataMock } from '../mock_data';
|
||||
|
||||
|
|
@ -22,6 +21,7 @@ const stubsData = {
|
|||
hide: modalHideSpy,
|
||||
},
|
||||
}),
|
||||
GlDisclosureDropdown,
|
||||
GlButton,
|
||||
GlFormInput,
|
||||
GlSprintf,
|
||||
|
|
@ -32,14 +32,12 @@ const createComponent = (mountFn = shallowMountExtended, stubs = {}) => {
|
|||
propsData: {
|
||||
...propsDataMock,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: createMockDirective('gl-tooltip'),
|
||||
},
|
||||
stubs,
|
||||
});
|
||||
};
|
||||
|
||||
const findDeleteButton = () => wrapper.findComponent(GlButton);
|
||||
const findDeleteButton = () =>
|
||||
wrapper.findComponent('[data-qa-selector="delete_merged_branches_button"]');
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findConfirmationButton = () =>
|
||||
wrapper.findByTestId('delete-merged-branches-confirmation-button');
|
||||
|
|
@ -54,22 +52,11 @@ describe('Delete merged branches component', () => {
|
|||
});
|
||||
|
||||
describe('Delete merged branches button', () => {
|
||||
it('has correct attributes, text and tooltip', () => {
|
||||
expect(findDeleteButton().attributes()).toMatchObject({
|
||||
category: 'secondary',
|
||||
variant: 'danger',
|
||||
});
|
||||
|
||||
it('has correct text', () => {
|
||||
createComponent(mount, stubsData);
|
||||
expect(findDeleteButton().text()).toBe(i18n.deleteButtonText);
|
||||
});
|
||||
|
||||
it('displays a tooltip', () => {
|
||||
const tooltip = getBinding(findDeleteButton().element, 'gl-tooltip');
|
||||
|
||||
expect(tooltip).toBeDefined();
|
||||
expect(tooltip.value).toBe(wrapper.vm.buttonTooltipText);
|
||||
});
|
||||
|
||||
it('opens modal when clicked', () => {
|
||||
createComponent(mount, stubsData);
|
||||
findDeleteButton().trigger('click');
|
||||
|
|
@ -130,7 +117,6 @@ describe('Delete merged branches component', () => {
|
|||
it('submits form when correct amount is provided and the confirm button is clicked', async () => {
|
||||
findFormInput().vm.$emit('input', 'delete');
|
||||
await waitForPromises();
|
||||
expect(findDeleteButton().props('disabled')).not.toBe(true);
|
||||
findConfirmationButton().trigger('click');
|
||||
expect(submitFormSpy()).toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Feature, stub_feature_flags: false, feature_category: :shared do
|
||||
RSpec.describe Feature, :clean_gitlab_redis_feature_flag, stub_feature_flags: false, feature_category: :shared do
|
||||
include StubVersion
|
||||
|
||||
before do
|
||||
|
|
@ -212,7 +212,7 @@ RSpec.describe Feature, stub_feature_flags: false, feature_category: :shared do
|
|||
end
|
||||
|
||||
it { expect(described_class.send(:l1_cache_backend)).to eq(Gitlab::ProcessMemoryCache.cache_backend) }
|
||||
it { expect(described_class.send(:l2_cache_backend)).to eq(Rails.cache) }
|
||||
it { expect(described_class.send(:l2_cache_backend)).to eq(Gitlab::Redis::FeatureFlag.cache_store) }
|
||||
|
||||
it 'caches the status in L1 and L2 caches',
|
||||
:request_store, :use_clean_rails_memory_store_caching do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Redis::FeatureFlag, feature_category: :redis do
|
||||
include_examples "redis_new_instance_shared_examples", 'feature_flag', Gitlab::Redis::Cache
|
||||
|
||||
describe '.cache_store' do
|
||||
it 'has a default ttl of 1 hour' do
|
||||
expect(described_class.cache_store.options[:expires_in]).to eq(1.hour)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -72,30 +72,11 @@ RSpec.describe API::Environments, feature_category: :continuous_delivery do
|
|||
end
|
||||
|
||||
context "when params[:search] is less than #{described_class::MIN_SEARCH_LENGTH} characters" do
|
||||
before do
|
||||
stub_feature_flags(environment_search_api_min_chars: false)
|
||||
end
|
||||
|
||||
it 'returns a normal response' do
|
||||
it 'returns with status 400' do
|
||||
get api("/projects/#{project.id}/environments?search=ab", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.size).to eq(0)
|
||||
end
|
||||
|
||||
context 'and environment_search_api_min_chars flag is enabled for the project' do
|
||||
before do
|
||||
stub_feature_flags(environment_search_api_min_chars: project)
|
||||
end
|
||||
|
||||
it 'returns with status 400' do
|
||||
get api("/projects/#{project.id}/environments?search=ab", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['message']).to include("Search query is less than #{described_class::MIN_SEARCH_LENGTH} characters")
|
||||
end
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['message']).to include("Search query is less than #{described_class::MIN_SEARCH_LENGTH} characters")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue