Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f5aa3d1cc5
commit
ef1f98e770
|
|
@ -1130,7 +1130,6 @@ RSpec/FeatureCategory:
|
|||
- 'ee/spec/views/admin/users/index.html.haml_spec.rb'
|
||||
- 'ee/spec/views/clusters/clusters/show.html.haml_spec.rb'
|
||||
- 'ee/spec/views/compliance_management/compliance_framework/_compliance_frameworks_info.html.haml_spec.rb'
|
||||
- 'ee/spec/views/devise/sessions/new.html.haml_spec.rb'
|
||||
- 'ee/spec/views/groups/hook_logs/show.html.haml_spec.rb'
|
||||
- 'ee/spec/views/groups/hooks/edit.html.haml_spec.rb'
|
||||
- 'ee/spec/views/groups/security/discover/show.html.haml_spec.rb'
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ export default {
|
|||
<crud-component
|
||||
ref="crudComponent"
|
||||
:title="$options.i18n.title"
|
||||
icon="messages"
|
||||
icon="bullhorn"
|
||||
:count="messagesCount"
|
||||
:toggle-text="$options.i18n.addButton"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<script>
|
||||
import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import EMPTY_VARIABLES_SVG from '@gitlab/svgs/dist/illustrations/empty-state/empty-variables-md.svg';
|
||||
import { s__ } from '~/locale';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlEmptyState,
|
||||
GlSprintf,
|
||||
GlLink,
|
||||
},
|
||||
EMPTY_VARIABLES_SVG,
|
||||
i18n: {
|
||||
title: s__('ManualVariables|There are no manually-specified variables for this pipeline'),
|
||||
description: s__(
|
||||
'ManualVariables|When you %{helpPageUrlStart}run a pipeline manually%{helpPageUrlEnd}, you can specify additional CI/CD variables to use in that pipeline run.',
|
||||
),
|
||||
},
|
||||
runPipelineManuallyDocUrl: helpPagePath('ci/pipelines/index', {
|
||||
anchor: 'run-a-pipeline-manually',
|
||||
}),
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-empty-state :svg-path="$options.EMPTY_VARIABLES_SVG" :title="$options.i18n.title">
|
||||
<template #description>
|
||||
<gl-sprintf :message="$options.i18n.description">
|
||||
<template #helpPageUrl="{ content }">
|
||||
<gl-link :href="$options.runPipelineManuallyDocUrl" target="_blank">{{
|
||||
content
|
||||
}}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</template>
|
||||
</gl-empty-state>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
query getManualVariables($projectPath: ID!, $iid: ID!) {
|
||||
project(fullPath: $projectPath) {
|
||||
__typename
|
||||
id
|
||||
pipeline(iid: $iid) {
|
||||
id
|
||||
manualVariables {
|
||||
__typename
|
||||
nodes {
|
||||
id
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import EmptyState from './empty_state.vue';
|
||||
import VariableTable from './variable_table.vue';
|
||||
import getManualVariablesQuery from './graphql/queries/get_manual_variables.query.graphql';
|
||||
|
||||
export default {
|
||||
name: 'ManualVariablesApp',
|
||||
components: {
|
||||
EmptyState,
|
||||
GlLoadingIcon,
|
||||
VariableTable,
|
||||
},
|
||||
inject: ['manualVariablesCount', 'projectPath', 'pipelineIid'],
|
||||
apollo: {
|
||||
variables: {
|
||||
query: getManualVariablesQuery,
|
||||
skip() {
|
||||
return !this.hasManualVariables;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
projectPath: this.projectPath,
|
||||
iid: this.pipelineIid,
|
||||
};
|
||||
},
|
||||
update({ project }) {
|
||||
return project?.pipeline?.manualVariables?.nodes || [];
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
loading() {
|
||||
return this.$apollo.queries.variables.loading;
|
||||
},
|
||||
hasManualVariables() {
|
||||
return Boolean(this.manualVariablesCount > 0);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="hasManualVariables" class="manual-variables-table">
|
||||
<gl-loading-icon v-if="loading" />
|
||||
<variable-table v-else :variables="variables" />
|
||||
</div>
|
||||
<empty-state v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<script>
|
||||
import { GlButton, GlPagination, GlTableLite } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
// The number of items per page is based on the design mockup.
|
||||
// Please refer to https://gitlab.com/gitlab-org/gitlab/-/issues/323097/designs/TabVariables.png
|
||||
const VARIABLES_PER_PAGE = 15;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
GlPagination,
|
||||
GlTableLite,
|
||||
},
|
||||
inject: ['manualVariablesCount', 'canReadVariables'],
|
||||
props: {
|
||||
variables: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
revealed: false,
|
||||
currentPage: 1,
|
||||
hasPermission: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
buttonText() {
|
||||
return this.revealed ? __('Hide values') : __('Reveal values');
|
||||
},
|
||||
showPager() {
|
||||
return this.manualVariablesCount > VARIABLES_PER_PAGE;
|
||||
},
|
||||
items() {
|
||||
const start = (this.currentPage - 1) * VARIABLES_PER_PAGE;
|
||||
const end = start + VARIABLES_PER_PAGE;
|
||||
return this.variables.slice(start, end);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleRevealed() {
|
||||
this.revealed = !this.revealed;
|
||||
},
|
||||
},
|
||||
TABLE_FIELDS: [
|
||||
{
|
||||
key: 'key',
|
||||
label: __('Key'),
|
||||
},
|
||||
{
|
||||
key: 'value',
|
||||
label: __('Value'),
|
||||
},
|
||||
],
|
||||
VARIABLES_PER_PAGE,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- This negative margin top is a hack for the purpose to eliminate default padding of tab container -->
|
||||
<!-- For context refer to: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/159206#note_1999122459 -->
|
||||
<div class="-gl-mt-3">
|
||||
<div v-if="canReadVariables" class="gl-p-3 gl-bg-gray-10">
|
||||
<gl-button :aria-label="buttonText" @click="toggleRevealed">{{ buttonText }}</gl-button>
|
||||
</div>
|
||||
<gl-table-lite :fields="$options.TABLE_FIELDS" :items="items">
|
||||
<template #cell(key)="{ value }">
|
||||
<span class="gl-text-secondary">
|
||||
{{ value }}
|
||||
</span>
|
||||
</template>
|
||||
<template #cell(value)="{ value }">
|
||||
<div class="gl-text-secondary" data-testid="manual-variable-value">
|
||||
<span v-if="revealed">{{ value }}</span>
|
||||
<span v-else>****</span>
|
||||
</div>
|
||||
</template>
|
||||
</gl-table-lite>
|
||||
<gl-pagination
|
||||
v-if="showPager"
|
||||
v-model="currentPage"
|
||||
class="gl-mt-6"
|
||||
:per-page="$options.VARIABLES_PER_PAGE"
|
||||
:total-items="manualVariablesCount"
|
||||
align="center"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -7,7 +7,6 @@ import {
|
|||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import { createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||
import { s__, __, sprintf } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
|
|
@ -15,6 +14,7 @@ import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
|
|||
import { confirmJobConfirmationMessage } from '~/ci/pipeline_details/graph/utils';
|
||||
import { TRACKING_CATEGORIES } from '../../constants';
|
||||
import getPipelineActionsQuery from '../graphql/queries/get_pipeline_actions.query.graphql';
|
||||
import jobPlayMutation from '../../jobs_page/graphql/mutations/job_play.mutation.graphql';
|
||||
|
||||
export default {
|
||||
name: 'PipelinesManualActions',
|
||||
|
|
@ -96,17 +96,13 @@ export default {
|
|||
}
|
||||
}
|
||||
this.isLoading = true;
|
||||
|
||||
/**
|
||||
* Ideally, the component would not make an api call directly.
|
||||
* However, in order to use the eventhub and know when to
|
||||
* toggle back the `isLoading` property we'd need an ID
|
||||
* to track the request with a watcher - since this component
|
||||
* is rendered at least 20 times in the same page, moving the
|
||||
* api call directly here is the most performant solution
|
||||
*/
|
||||
axios
|
||||
.post(`${action.playPath}.json`)
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: jobPlayMutation,
|
||||
variables: {
|
||||
id: action.id,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.isLoading = false;
|
||||
this.$emit('refresh-pipeline-table');
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import TaskList from '~/task_list';
|
|||
import { addHierarchyChild, removeHierarchyChild } from '~/work_items/graphql/cache_utils';
|
||||
import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
|
||||
import deleteWorkItemMutation from '~/work_items/graphql/delete_work_item.mutation.graphql';
|
||||
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
|
||||
import namespaceWorkItemTypesQuery from '~/work_items/graphql/namespace_work_item_types.query.graphql';
|
||||
import {
|
||||
sprintfWorkItem,
|
||||
I18N_WORK_ITEM_ERROR_CREATING,
|
||||
|
|
@ -110,7 +110,7 @@ export default {
|
|||
},
|
||||
},
|
||||
workItemTypes: {
|
||||
query: projectWorkItemTypesQuery,
|
||||
query: namespaceWorkItemTypesQuery,
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ import {
|
|||
WIDGET_TYPE_ROLLEDUP_DATES,
|
||||
} from '../constants';
|
||||
import createWorkItemMutation from '../graphql/create_work_item.mutation.graphql';
|
||||
import groupWorkItemTypesQuery from '../graphql/group_work_item_types.query.graphql';
|
||||
import projectWorkItemTypesQuery from '../graphql/project_work_item_types.query.graphql';
|
||||
import namespaceWorkItemTypesQuery from '../graphql/namespace_work_item_types.query.graphql';
|
||||
import groupWorkItemByIidQuery from '../graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
|
||||
import updateNewWorkItemMutation from '../graphql/update_new_work_item.mutation.graphql';
|
||||
|
|
@ -110,7 +109,7 @@ export default {
|
|||
},
|
||||
workItemTypes: {
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemTypesQuery : projectWorkItemTypesQuery;
|
||||
return namespaceWorkItemTypesQuery;
|
||||
},
|
||||
fetchPolicy() {
|
||||
return this.workItemTypeName ? fetchPolicies.CACHE_ONLY : fetchPolicies.CACHE_FIRST;
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ import {
|
|||
sprintfWorkItem,
|
||||
I18N_WORK_ITEM_ERROR_FETCHING_TYPES,
|
||||
} from '../constants';
|
||||
import projectWorkItemTypesQuery from '../graphql/project_work_item_types.query.graphql';
|
||||
import groupWorkItemTypesQuery from '../graphql/group_work_item_types.query.graphql';
|
||||
import namespaceWorkItemTypesQuery from '../graphql/namespace_work_item_types.query.graphql';
|
||||
import CreateWorkItem from './create_work_item.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -43,7 +42,7 @@ export default {
|
|||
apollo: {
|
||||
workItemTypes: {
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemTypesQuery : projectWorkItemTypesQuery;
|
||||
return namespaceWorkItemTypesQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import {
|
|||
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
|
||||
import updateWorkItemNotificationsMutation from '../graphql/update_work_item_notifications.mutation.graphql';
|
||||
import convertWorkItemMutation from '../graphql/work_item_convert.mutation.graphql';
|
||||
import projectWorkItemTypesQuery from '../graphql/project_work_item_types.query.graphql';
|
||||
import namespaceWorkItemTypesQuery from '../graphql/namespace_work_item_types.query.graphql';
|
||||
import WorkItemStateToggle from './work_item_state_toggle.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -173,7 +173,7 @@ export default {
|
|||
},
|
||||
apollo: {
|
||||
workItemTypes: {
|
||||
query: projectWorkItemTypesQuery,
|
||||
query: namespaceWorkItemTypesQuery,
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
|
|
|
|||
|
|
@ -87,11 +87,10 @@ export default {
|
|||
return this.isWidgetPresent(WIDGET_TYPE_ROLLEDUP_DATES);
|
||||
},
|
||||
workItemWeight() {
|
||||
/** TODO remove this check after https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158021 is merged */
|
||||
if (this.workItemType !== WORK_ITEM_TYPE_VALUE_EPIC) {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_WEIGHT);
|
||||
}
|
||||
return false;
|
||||
return this.isWidgetPresent(WIDGET_TYPE_WEIGHT);
|
||||
},
|
||||
isWorkItemWeightEditable() {
|
||||
return this.workItemWeight?.widgetDefinition?.editable;
|
||||
},
|
||||
workItemParticipants() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_PARTICIPANTS);
|
||||
|
|
@ -178,7 +177,7 @@ export default {
|
|||
@labelsUpdated="$emit('attributesUpdated', { type: $options.ListType.label, ids: $event })"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="workItemWeight">
|
||||
<template v-if="isWorkItemWeightEditable">
|
||||
<work-item-weight
|
||||
class="gl-mb-5"
|
||||
:can-update="canUpdate"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
WORK_ITEM_REFERENCE_CHAR,
|
||||
WORK_ITEM_TYPE_VALUE_TASK,
|
||||
WORK_ITEM_TYPE_VALUE_EPIC,
|
||||
WIDGET_TYPE_WEIGHT,
|
||||
} from '../constants';
|
||||
|
||||
import workItemUpdatedSubscription from '../graphql/work_item_updated.subscription.graphql';
|
||||
|
|
@ -284,6 +285,15 @@ export default {
|
|||
workItemNotes() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_NOTES);
|
||||
},
|
||||
workItemWeight() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_WEIGHT);
|
||||
},
|
||||
showRolledUpWeight() {
|
||||
return this.workItemWeight?.widgetDefinition?.rollUp;
|
||||
},
|
||||
rolledUpWeight() {
|
||||
return this.workItemWeight?.rolledUpWeight;
|
||||
},
|
||||
workItemBodyClass() {
|
||||
return {
|
||||
'gl-pt-5': !this.updateError && !this.isModal,
|
||||
|
|
@ -673,6 +683,8 @@ export default {
|
|||
:work-item-iid="workItemIid"
|
||||
:can-update="canUpdate"
|
||||
:can-update-children="canUpdateChildren"
|
||||
:rolled-up-weight="rolledUpWeight"
|
||||
:show-rolled-up-weight="showRolledUpWeight"
|
||||
:confidential="workItem.confidential"
|
||||
:allowed-child-types="allowedChildTypes"
|
||||
@show-modal="openInModal"
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ import { __, s__, sprintf } from '~/locale';
|
|||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import WorkItemTokenInput from '../shared/work_item_token_input.vue';
|
||||
import { addHierarchyChild } from '../../graphql/cache_utils';
|
||||
import groupWorkItemTypesQuery from '../../graphql/group_work_item_types.query.graphql';
|
||||
import projectWorkItemTypesQuery from '../../graphql/project_work_item_types.query.graphql';
|
||||
import namespaceWorkItemTypesQuery from '../../graphql/namespace_work_item_types.query.graphql';
|
||||
import updateWorkItemHierarchyMutation from '../../graphql/update_work_item_hierarchy.mutation.graphql';
|
||||
import createWorkItemMutation from '../../graphql/create_work_item.mutation.graphql';
|
||||
import {
|
||||
|
|
@ -88,7 +87,7 @@ export default {
|
|||
apollo: {
|
||||
workItemTypes: {
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemTypesQuery : projectWorkItemTypesQuery;
|
||||
return namespaceWorkItemTypesQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import { __, s__ } from '~/locale';
|
|||
import { STORAGE_KEY } from '~/super_sidebar/constants';
|
||||
import AccessorUtilities from '~/lib/utils/accessor';
|
||||
import { getTopFrequentItems } from '~/super_sidebar/utils';
|
||||
import groupProjectsForLinksWidgetQuery from '../../graphql/group_projects_for_links_widget.query.graphql';
|
||||
import relatedProjectsForLinksWidgetQuery from '../../graphql/related_projects_for_links_widget.query.graphql';
|
||||
import namespaceProjectsForLinksWidgetQuery from '../../graphql/namespace_projects_for_links_widget.query.graphql';
|
||||
import { SEARCH_DEBOUNCE, MAX_FREQUENT_PROJECTS } from '../../constants';
|
||||
|
||||
export default {
|
||||
|
|
@ -46,7 +45,7 @@ export default {
|
|||
apollo: {
|
||||
projects: {
|
||||
query() {
|
||||
return this.isGroup ? groupProjectsForLinksWidgetQuery : relatedProjectsForLinksWidgetQuery;
|
||||
return namespaceProjectsForLinksWidgetQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
|
|
@ -55,7 +54,7 @@ export default {
|
|||
};
|
||||
},
|
||||
update(data) {
|
||||
return this.isGroup ? data.group?.projects?.nodes : data.project?.group?.projects?.nodes;
|
||||
return data.namespace?.projects?.nodes;
|
||||
},
|
||||
result() {
|
||||
if (this.selectedProject === null) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlToggle, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { GlToggle, GlIcon, GlTooltip, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
import {
|
||||
FORM_TYPES,
|
||||
|
|
@ -35,6 +35,8 @@ export default {
|
|||
WorkItemTreeActions,
|
||||
GlToggle,
|
||||
GlLoadingIcon,
|
||||
GlIcon,
|
||||
GlTooltip,
|
||||
},
|
||||
inject: ['hasSubepicsFeature'],
|
||||
props: {
|
||||
|
|
@ -80,6 +82,16 @@ export default {
|
|||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
showRolledUpWeight: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
rolledUpWeight: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -213,6 +225,20 @@ export default {
|
|||
>
|
||||
<template #header>
|
||||
{{ $options.WORK_ITEMS_TREE_TEXT_MAP[workItemType].title }}
|
||||
<span
|
||||
v-if="showRolledUpWeight"
|
||||
ref="weightData"
|
||||
data-testid="rollup-weight"
|
||||
class="gl-font-normal gl-ml-3 gl-display-flex gl-align-items-center gl-cursor-help gl-gap-2 gl-text-secondary"
|
||||
>
|
||||
<gl-icon name="weight" class="gl-text-secondary" />
|
||||
<span data-testid="weight-value" class="gl-font-sm">{{ rolledUpWeight }}</span>
|
||||
<gl-tooltip :target="() => $refs.weightData">
|
||||
<span class="gl-font-bold">
|
||||
{{ __('Weight') }}
|
||||
</span>
|
||||
</gl-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<template #header-right>
|
||||
<gl-toggle
|
||||
|
|
|
|||
|
|
@ -318,9 +318,18 @@ export const setNewWorkItemCache = async (
|
|||
}
|
||||
|
||||
if (widgetName === WIDGET_TYPE_WEIGHT) {
|
||||
const weightWidgetData = widgetDefinitions.find(
|
||||
(definition) => definition.type === WIDGET_TYPE_WEIGHT,
|
||||
);
|
||||
|
||||
widgets.push({
|
||||
type: 'WEIGHT',
|
||||
weight: null,
|
||||
rolledUpWeight: 0,
|
||||
widgetDefinition: {
|
||||
editable: weightWidgetData?.editable,
|
||||
rollUp: weightWidgetData?.rollUp,
|
||||
},
|
||||
__typename: 'WorkItemWidgetWeight',
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
query groupProjectsForLinksWidget($fullPath: ID!, $projectSearch: String) {
|
||||
group(fullPath: $fullPath) {
|
||||
id
|
||||
projects(search: $projectSearch, includeSubgroups: true) {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
avatarUrl
|
||||
nameWithNamespace
|
||||
fullPath
|
||||
namespace {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
query namespaceProjectsForLinksWidget($fullPath: ID!, $projectSearch: String) {
|
||||
namespace(fullPath: $fullPath) {
|
||||
id
|
||||
projects(search: $projectSearch, includeSubgroups: true, includeSiblingProjects: true) {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
avatarUrl
|
||||
nameWithNamespace
|
||||
fullPath
|
||||
namespace {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#import "ee_else_ce/work_items/graphql/work_item_type.fragment.graphql"
|
||||
|
||||
query groupWorkItemTypes($fullPath: ID!, $name: IssueType) {
|
||||
workspace: group(fullPath: $fullPath) {
|
||||
query namespaceWorkItemTypes($fullPath: ID!, $name: IssueType) {
|
||||
workspace: namespace(fullPath: $fullPath) {
|
||||
id
|
||||
workItemTypes(name: $name) {
|
||||
nodes {
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
#import "ee_else_ce/work_items/graphql/work_item_type.fragment.graphql"
|
||||
|
||||
query projectWorkItemTypes($fullPath: ID!, $name: IssueType) {
|
||||
workspace: project(fullPath: $fullPath) {
|
||||
id
|
||||
workItemTypes(name: $name) {
|
||||
nodes {
|
||||
...WorkItemTypeFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
query relatedProjectsForLinksWidget($fullPath: ID!, $projectSearch: String) {
|
||||
project(fullPath: $fullPath) {
|
||||
id
|
||||
group {
|
||||
id
|
||||
projects(search: $projectSearch, includeSubgroups: true) {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
avatarUrl
|
||||
nameWithNamespace
|
||||
fullPath
|
||||
namespace {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
- expanded = local_assigns.fetch(:expanded)
|
||||
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Variables')
|
||||
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
|
||||
%p.gl-text-secondary
|
||||
= render "ci/variables/content", entity: @entity, variable_limit: @variable_limit
|
||||
|
|
@ -5,55 +5,49 @@
|
|||
- expanded = expanded_by_default?
|
||||
- general_expanded = @group.errors.empty? ? expanded : true
|
||||
|
||||
%h1.gl-sr-only= @breadcrumb_title
|
||||
|
||||
-# Given we only have one field in this form which is also admin-only,
|
||||
-# we don't want to show an empty section to non-admin users,
|
||||
- if can?(current_user, :update_max_artifacts_size, @group)
|
||||
%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _("General pipelines")
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
= _("Customize your pipeline configuration.")
|
||||
.settings-content
|
||||
= render ::Layouts::SettingsBlockComponent.new(_("General pipelines"),
|
||||
id: 'js-general-pipeline-settings',
|
||||
expanded: general_expanded) do |c|
|
||||
- c.with_description do
|
||||
= _("Customize your pipeline configuration.")
|
||||
- c.with_body do
|
||||
= render 'groups/settings/ci_cd/form', group: @group
|
||||
|
||||
- if can?(current_user, :admin_cicd_variables, @group)
|
||||
%section.settings#ci-variables.no-animate{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
= render 'ci/variables/header', expanded: expanded
|
||||
.settings-content
|
||||
= render ::Layouts::SettingsBlockComponent.new(_('Variables'),
|
||||
id: 'ci-variables',
|
||||
expanded: expanded) do |c|
|
||||
- c.with_description do
|
||||
= render "ci/variables/content", entity: @entity, variable_limit: @variable_limit
|
||||
- c.with_body do
|
||||
= render 'ci/variables/index', save_endpoint: group_variables_path
|
||||
|
||||
- if can?(current_user, :admin_runner, @group)
|
||||
%section.settings#runners-settings.no-animate{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Runners')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
= _("Runners are processes that pick up and execute CI/CD jobs for GitLab.")
|
||||
= link_to s_('What is GitLab Runner?'), 'https://docs.gitlab.com/runner/', target: '_blank', rel: 'noopener noreferrer'
|
||||
.settings-content
|
||||
= render ::Layouts::SettingsBlockComponent.new(_('Runners'),
|
||||
id: 'runners-settings',
|
||||
expanded: expanded) do |c|
|
||||
- c.with_description do
|
||||
= _("Runners are processes that pick up and execute CI/CD jobs for GitLab.")
|
||||
= link_to s_('What is GitLab Runner?'), 'https://docs.gitlab.com/runner/', target: '_blank', rel: 'noopener noreferrer'
|
||||
- c.with_body do
|
||||
= render 'groups/runners/settings'
|
||||
|
||||
- if can?(current_user, :admin_group, @group)
|
||||
%section.settings#auto-devops-settings.no-animate{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Auto DevOps')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
- auto_devops_url = help_page_path('topics/autodevops/index')
|
||||
- quickstart_url = help_page_path('topics/autodevops/cloud_deployments/auto_devops_with_gke')
|
||||
- auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
|
||||
- quickstart_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: quickstart_url }
|
||||
= html_escape(s_('AutoDevOps|%{auto_devops_start}Automate building, testing, and deploying%{auto_devops_end} your applications based on your continuous integration and delivery configuration. %{quickstart_start}How do I get started?%{quickstart_end}')) % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe, quickstart_start: quickstart_start, quickstart_end: '</a>'.html_safe }
|
||||
|
||||
.settings-content
|
||||
= render ::Layouts::SettingsBlockComponent.new(_('Auto DevOps'),
|
||||
id: 'auto-devops-settings',
|
||||
expanded: expanded) do |c|
|
||||
- c.with_description do
|
||||
- auto_devops_url = help_page_path('topics/autodevops/index')
|
||||
- quickstart_url = help_page_path('topics/autodevops/cloud_deployments/auto_devops_with_gke')
|
||||
- auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
|
||||
- quickstart_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: quickstart_url }
|
||||
= html_escape(s_('AutoDevOps|%{auto_devops_start}Automate building, testing, and deploying%{auto_devops_end} your applications based on your continuous integration and delivery configuration. %{quickstart_start}How do I get started?%{quickstart_end}')) % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe, quickstart_start: quickstart_start, quickstart_end: '</a>'.html_safe }
|
||||
- c.with_body do
|
||||
= render 'groups/settings/ci_cd/auto_devops_form', group: @group
|
||||
|
||||
= render_if_exists 'groups/settings/ci_cd/protected_environments', expanded: expanded
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
- @pipeline.yaml_errors.split("\n").each do |error|
|
||||
%li= error
|
||||
- if can_view_pipeline_editor?(@project)
|
||||
= render Pajamas::ButtonComponent.new(href: project_ci_pipeline_editor_path(@project), variant: :confirm) do
|
||||
= render Pajamas::ButtonComponent.new(href: project_ci_pipeline_editor_path(@project, branch_name: @pipeline.source_ref), variant: :confirm) do
|
||||
= s_("Pipelines|Go to the pipeline editor")
|
||||
|
||||
- else
|
||||
|
|
|
|||
|
|
@ -5,123 +5,105 @@
|
|||
- expanded = expanded_by_default?
|
||||
- general_expanded = @project.errors.empty? ? expanded : true
|
||||
|
||||
%h1.gl-sr-only= @breadcrumb_title
|
||||
|
||||
- if can?(current_user, :admin_pipeline, @project)
|
||||
%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _("General pipelines")
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
= _("Customize your pipeline configuration.")
|
||||
.settings-content
|
||||
= render ::Layouts::SettingsBlockComponent.new(_("General pipelines"),
|
||||
id: 'js-general-pipeline-settings',
|
||||
expanded: general_expanded) do |c|
|
||||
- c.with_description do
|
||||
= _("Customize your pipeline configuration.")
|
||||
- c.with_body do
|
||||
= render 'form'
|
||||
|
||||
%section.settings#autodevops-settings.no-animate{ class: ('expanded' if expanded), data: { testid: 'autodevops-settings-content' } }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= s_('CICD|Auto DevOps')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
- auto_devops_url = help_page_path('topics/autodevops/index')
|
||||
- quickstart_url = help_page_path('topics/autodevops/cloud_deployments/auto_devops_with_gke')
|
||||
- auto_devops_link = link_to('', auto_devops_url, target: '_blank', rel: 'noopener noreferrer')
|
||||
- quickstart_link = link_to('', quickstart_url, target: '_blank', rel: 'noopener noreferrer')
|
||||
= safe_format(s_('AutoDevOps|%{auto_devops_start}Automate building, testing, and deploying%{auto_devops_end} your applications based on your continuous integration and delivery configuration. %{quickstart_start}How do I get started?%{quickstart_end}'), tag_pair(auto_devops_link, :auto_devops_start, :auto_devops_end), tag_pair(quickstart_link, :quickstart_start, :quickstart_end))
|
||||
.settings-content
|
||||
= render ::Layouts::SettingsBlockComponent.new(s_('CICD|Auto DevOps'),
|
||||
id: 'autodevops-settings',
|
||||
testid: 'autodevops-settings-content',
|
||||
expanded: expanded) do |c|
|
||||
- c.with_description do
|
||||
- auto_devops_url = help_page_path('topics/autodevops/index')
|
||||
- quickstart_url = help_page_path('topics/autodevops/cloud_deployments/auto_devops_with_gke')
|
||||
- auto_devops_link = link_to('', auto_devops_url, target: '_blank', rel: 'noopener noreferrer')
|
||||
- quickstart_link = link_to('', quickstart_url, target: '_blank', rel: 'noopener noreferrer')
|
||||
= safe_format(s_('AutoDevOps|%{auto_devops_start}Automate building, testing, and deploying%{auto_devops_end} your applications based on your continuous integration and delivery configuration. %{quickstart_start}How do I get started?%{quickstart_end}'), tag_pair(auto_devops_link, :auto_devops_start, :auto_devops_end), tag_pair(quickstart_link, :quickstart_start, :quickstart_end))
|
||||
- c.with_body do
|
||||
= render 'autodevops_form', auto_devops_enabled: @project.auto_devops_enabled?
|
||||
|
||||
= render_if_exists 'projects/settings/ci_cd/protected_environments', expanded: expanded
|
||||
|
||||
- if can?(current_user, :admin_runner, @project)
|
||||
- expand_runners = expanded || params[:expand_runners]
|
||||
%section.settings.no-animate#js-runners-settings{ class: ('expanded' if expand_runners), data: { testid: 'runners-settings-content' } }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _("Runners")
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expand_runners ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
= _("Runners are processes that pick up and execute CI/CD jobs for GitLab.")
|
||||
= link_to s_('What is GitLab Runner?'), 'https://docs.gitlab.com/runner/', target: '_blank', rel: 'noopener noreferrer'
|
||||
.settings-content
|
||||
= render ::Layouts::SettingsBlockComponent.new(_('Runners'),
|
||||
id: 'js-runners-settings',
|
||||
testid: 'runners-settings-content',
|
||||
expanded: expand_runners) do |c|
|
||||
- c.with_description do
|
||||
= _("Runners are processes that pick up and execute CI/CD jobs for GitLab.")
|
||||
= link_to s_('What is GitLab Runner?'), 'https://docs.gitlab.com/runner/', target: '_blank', rel: 'noopener noreferrer'
|
||||
- c.with_body do
|
||||
= render 'projects/runners/settings'
|
||||
|
||||
- if can?(current_user, :admin_pipeline, @project)
|
||||
- if Gitlab::CurrentSettings.current_application_settings.keep_latest_artifact?
|
||||
%section.settings.no-animate#js-artifacts-settings{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _("Artifacts")
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
= _("A job artifact is an archive of files and directories saved by a job when it finishes.")
|
||||
.settings-content
|
||||
= render ::Layouts::SettingsBlockComponent.new(_("Artifacts"),
|
||||
id: 'js-artifacts-settings',
|
||||
expanded: expanded) do |c|
|
||||
- c.with_description do
|
||||
= _("A job artifact is an archive of files and directories saved by a job when it finishes.")
|
||||
- c.with_body do
|
||||
#js-artifacts-settings-app{ data: { full_path: @project.full_path, help_page_path: help_page_path('ci/jobs/job_artifacts', anchor: 'keep-artifacts-from-most-recent-successful-jobs') } }
|
||||
|
||||
- if can?(current_user, :admin_cicd_variables, @project)
|
||||
%section.settings.no-animate#js-cicd-variables-settings{ class: ('expanded' if expanded), data: { testid: 'variables-settings-content' } }
|
||||
.settings-header
|
||||
= render 'ci/variables/header', expanded: expanded
|
||||
.settings-content
|
||||
= render ::Layouts::SettingsBlockComponent.new(_('Variables'),
|
||||
id: 'js-cicd-variables-settings',
|
||||
testid: 'variables-settings-content',
|
||||
expanded: expanded) do |c|
|
||||
- c.with_description do
|
||||
= render "ci/variables/content", entity: @entity, variable_limit: @variable_limit
|
||||
- c.with_body do
|
||||
= render 'ci/variables/index', save_endpoint: project_variables_path(@project)
|
||||
|
||||
- if can?(current_user, :admin_pipeline, @project)
|
||||
%section.settings.no-animate#js-pipeline-triggers{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _("Pipeline trigger tokens")
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
= _("Trigger a pipeline for a branch or tag by generating a trigger token and using it with an API call. The token impersonates a user's project access and permissions.")
|
||||
= link_to _('Learn more.'), help_page_path('ci/triggers/index'), target: '_blank', rel: 'noopener noreferrer'
|
||||
.settings-content
|
||||
= render ::Layouts::SettingsBlockComponent.new(_('Pipeline trigger tokens'),
|
||||
id: 'js-pipeline-triggers',
|
||||
expanded: expanded) do |c|
|
||||
- c.with_description do
|
||||
= _("Trigger a pipeline for a branch or tag by generating a trigger token and using it with an API call. The token impersonates a user's project access and permissions.")
|
||||
= link_to _('Learn more.'), help_page_path('ci/triggers/index'), target: '_blank', rel: 'noopener noreferrer'
|
||||
- c.with_body do
|
||||
= render 'projects/triggers/index'
|
||||
|
||||
= render_if_exists 'projects/settings/ci_cd/auto_rollback', expanded: expanded
|
||||
|
||||
- if can?(current_user, :create_freeze_period, @project)
|
||||
%section.settings.no-animate#js-deploy-freeze-settings{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _("Deploy freezes")
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
- freeze_period_docs = help_page_path('user/project/releases/index', anchor: 'prevent-unintentional-releases-by-setting-a-deploy-freeze')
|
||||
- freeze_period_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: freeze_period_docs }
|
||||
= html_escape(s_('DeployFreeze|Add a freeze period to prevent unintended releases during a period of time for a given environment. You must update the deployment jobs in %{filename} according to the deploy freezes added here. %{freeze_period_link_start}Learn more.%{freeze_period_link_end}')) % { freeze_period_link_start: freeze_period_link_start, freeze_period_link_end: '</a>'.html_safe, filename: tag.code('.gitlab-ci.yml') }
|
||||
= render ::Layouts::SettingsBlockComponent.new(_('Deploy freezes'),
|
||||
id: 'js-deploy-freeze-settings',
|
||||
expanded: expanded) do |c|
|
||||
- c.with_description do
|
||||
- freeze_period_docs = help_page_path('user/project/releases/index', anchor: 'prevent-unintentional-releases-by-setting-a-deploy-freeze')
|
||||
- freeze_period_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: freeze_period_docs }
|
||||
= html_escape(s_('DeployFreeze|Add a freeze period to prevent unintended releases during a period of time for a given environment. You must update the deployment jobs in %{filename} according to the deploy freezes added here. %{freeze_period_link_start}Learn more.%{freeze_period_link_end}')) % { freeze_period_link_start: freeze_period_link_start, freeze_period_link_end: '</a>'.html_safe, filename: tag.code('.gitlab-ci.yml') }
|
||||
|
||||
- cron_syntax_url = 'https://crontab.guru/'
|
||||
- cron_syntax_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: cron_syntax_url }
|
||||
= s_('DeployFreeze|Specify deploy freezes using %{cron_syntax_link_start}cron syntax%{cron_syntax_link_end}.').html_safe % { cron_syntax_link_start: cron_syntax_link_start, cron_syntax_link_end: "</a>".html_safe }
|
||||
|
||||
.settings-content
|
||||
- cron_syntax_url = 'https://crontab.guru/'
|
||||
- cron_syntax_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: cron_syntax_url }
|
||||
= s_('DeployFreeze|Specify deploy freezes using %{cron_syntax_link_start}cron syntax%{cron_syntax_link_end}.').html_safe % { cron_syntax_link_start: cron_syntax_link_start, cron_syntax_link_end: "</a>".html_safe }
|
||||
- c.with_body do
|
||||
= render 'ci/deploy_freeze/index'
|
||||
|
||||
%section.settings.no-animate#js-token-access{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _("Job token permissions")
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
= _("Control which CI/CD job tokens can be used to authenticate with this project.")
|
||||
.settings-content
|
||||
= render ::Layouts::SettingsBlockComponent.new(_('Job token permissions'),
|
||||
id: 'js-token-access',
|
||||
expanded: expanded) do |c|
|
||||
- c.with_description do
|
||||
= _("Control which CI/CD job tokens can be used to authenticate with this project.")
|
||||
- c.with_body do
|
||||
= render 'ci/token_access/index'
|
||||
|
||||
- if show_secure_files_setting(@project, current_user)
|
||||
%section.settings.no-animate#js-secure-files{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _("Secure Files")
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
= _("Use Secure Files to store files used by your pipelines such as Android keystores, or Apple provisioning profiles and signing certificates.")
|
||||
= link_to _('Learn more'), help_page_path('ci/secure_files/index'), target: '_blank', rel: 'noopener noreferrer'
|
||||
.settings-content
|
||||
= render ::Layouts::SettingsBlockComponent.new(_('Secure Files'),
|
||||
id: 'js-secure-files',
|
||||
expanded: expanded) do |c|
|
||||
- c.with_description do
|
||||
= _("Use Secure Files to store files used by your pipelines such as Android keystores, or Apple provisioning profiles and signing certificates.")
|
||||
= link_to _('Learn more'), help_page_path('ci/secure_files/index'), target: '_blank', rel: 'noopener noreferrer'
|
||||
- c.with_body do
|
||||
#js-ci-secure-files{ data: { project_id: @project.id, admin: can?(current_user, :admin_secure_files, @project).to_s, file_size_limit: Ci::SecureFile::FILE_SIZE_LIMIT.to_mb } }
|
||||
|
|
|
|||
|
|
@ -382,6 +382,10 @@ sbom_occurrences:
|
|||
- table: ci_pipelines
|
||||
column: pipeline_id
|
||||
on_delete: async_nullify
|
||||
sbom_sources:
|
||||
- table: organizations
|
||||
column: organization_id
|
||||
on_delete: async_delete
|
||||
security_scans:
|
||||
- table: ci_builds
|
||||
column: build_id
|
||||
|
|
|
|||
|
|
@ -19,3 +19,4 @@ desired_sharding_key:
|
|||
table: approval_group_rules
|
||||
sharding_key: group_id
|
||||
belongs_to: approval_group_rule
|
||||
desired_sharding_key_migration_job_name: BackfillApprovalGroupRulesProtectedBranchesGroupId
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
migration_job_name: BackfillApprovalGroupRulesProtectedBranchesGroupId
|
||||
description: Backfills sharding key `approval_group_rules_protected_branches.group_id` from `approval_group_rules`.
|
||||
feature_category: source_code_management
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/159584
|
||||
milestone: '17.2'
|
||||
queued_migration_version: 20240716135032
|
||||
finalize_after: '2024-08-22'
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
|
|
@ -8,4 +8,5 @@ description: Stores information about where an SBoM component originated from
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90812
|
||||
milestone: '15.2'
|
||||
gitlab_schema: gitlab_sec
|
||||
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/457096
|
||||
sharding_key:
|
||||
organization_id: organizations
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddOrganizationToSbomSources < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
milestone '17.3'
|
||||
|
||||
INDEX_NAME = 'index_sbom_sources_on_organization_id'
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
add_column :sbom_sources, :organization_id, :bigint, null: false,
|
||||
default: Organizations::Organization::DEFAULT_ORGANIZATION_ID,
|
||||
if_not_exists: true
|
||||
end
|
||||
|
||||
add_concurrent_foreign_key :sbom_sources, :organizations, column: :organization_id, on_delete: :cascade
|
||||
add_concurrent_index :sbom_sources, :organization_id, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :sbom_sources, column: :organization_id
|
||||
end
|
||||
|
||||
remove_concurrent_index_by_name :sbom_sources, INDEX_NAME
|
||||
|
||||
with_lock_retries do
|
||||
remove_column :sbom_sources, :organization_id, if_exists: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ReplaceSbomSourcesUniqueIndex < Gitlab::Database::Migration[2.2]
|
||||
REMOVED_INDEX_NAME = "index_sbom_sources_on_source_type_and_source"
|
||||
ADDED_INDEX_NAME = "index_sbom_sources_on_source_type_and_source_and_org_id"
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
milestone '17.3'
|
||||
|
||||
def up
|
||||
add_concurrent_index :sbom_sources, %i[source_type source organization_id], unique: true, name: ADDED_INDEX_NAME
|
||||
remove_concurrent_index_by_name :sbom_sources, name: REMOVED_INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index :sbom_sources, %i[source_type source], unique: true, name: REMOVED_INDEX_NAME
|
||||
remove_concurrent_index_by_name :sbom_sources, name: ADDED_INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UpdateSbomSources < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_sec
|
||||
|
||||
milestone '17.3'
|
||||
|
||||
class SbomSource < MigrationRecord
|
||||
self.table_name = "sbom_sources"
|
||||
end
|
||||
|
||||
def up
|
||||
# no-op
|
||||
end
|
||||
|
||||
def down
|
||||
SbomSource.where.not(organization_id: 1).delete_all
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddGroupIdToApprovalGroupRulesProtectedBranches < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.2'
|
||||
|
||||
def change
|
||||
add_column :approval_group_rules_protected_branches, :group_id, :bigint
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class IndexApprovalGroupRulesProtectedBranchesOnGroupId < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.2'
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_approval_group_rules_protected_branches_on_group_id'
|
||||
|
||||
def up
|
||||
add_concurrent_index :approval_group_rules_protected_branches, :group_id, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :approval_group_rules_protected_branches, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddApprovalGroupRulesProtectedBranchesGroupIdFk < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.2'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :approval_group_rules_protected_branches, :namespaces, column: :group_id,
|
||||
on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :approval_group_rules_protected_branches, column: :group_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddApprovalGroupRulesProtectedBranchesGroupIdTrigger < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.2'
|
||||
|
||||
def up
|
||||
install_sharding_key_assignment_trigger(
|
||||
table: :approval_group_rules_protected_branches,
|
||||
sharding_key: :group_id,
|
||||
parent_table: :approval_group_rules,
|
||||
parent_sharding_key: :group_id,
|
||||
foreign_key: :approval_group_rule_id
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
remove_sharding_key_assignment_trigger(
|
||||
table: :approval_group_rules_protected_branches,
|
||||
sharding_key: :group_id,
|
||||
parent_table: :approval_group_rules,
|
||||
parent_sharding_key: :group_id,
|
||||
foreign_key: :approval_group_rule_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueBackfillApprovalGroupRulesProtectedBranchesGroupId < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.2'
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||
|
||||
MIGRATION = "BackfillApprovalGroupRulesProtectedBranchesGroupId"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 1000
|
||||
SUB_BATCH_SIZE = 100
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:approval_group_rules_protected_branches,
|
||||
:id,
|
||||
:group_id,
|
||||
:approval_group_rules,
|
||||
:group_id,
|
||||
:approval_group_rule_id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(
|
||||
MIGRATION,
|
||||
:approval_group_rules_protected_branches,
|
||||
:id,
|
||||
[
|
||||
:group_id,
|
||||
:approval_group_rules,
|
||||
:group_id,
|
||||
:approval_group_rule_id
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveOrganizationsSbomSourcesOrganizationIdFk < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.3'
|
||||
disable_ddl_transaction!
|
||||
|
||||
FOREIGN_KEY_NAME = "fk_8d0c60c7e9"
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(:sbom_sources, :organizations,
|
||||
name: FOREIGN_KEY_NAME, reverse_lock_order: true)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_foreign_key(:sbom_sources, :organizations,
|
||||
name: FOREIGN_KEY_NAME, column: :organization_id,
|
||||
target_column: :id, on_delete: :cascade)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PartitionedFkToCiPipelinesFromPCiStagesOnPartitionIdAndPipelineId < Gitlab::Database::Migration[2.2]
|
||||
include Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers
|
||||
|
||||
milestone '17.3'
|
||||
disable_ddl_transaction!
|
||||
|
||||
SOURCE_TABLE_NAME = :p_ci_stages
|
||||
TARGET_TABLE_NAME = :ci_pipelines
|
||||
COLUMN = :pipeline_id
|
||||
TARGET_COLUMN = :id
|
||||
FK_NAME = :fk_fb57e6cc56_p
|
||||
PARTITION_COLUMN = :partition_id
|
||||
|
||||
def up
|
||||
add_concurrent_partitioned_foreign_key(
|
||||
SOURCE_TABLE_NAME,
|
||||
TARGET_TABLE_NAME,
|
||||
column: [PARTITION_COLUMN, COLUMN],
|
||||
target_column: [PARTITION_COLUMN, TARGET_COLUMN],
|
||||
validate: true,
|
||||
reverse_lock_order: true,
|
||||
on_update: :cascade,
|
||||
on_delete: :cascade,
|
||||
name: FK_NAME
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(
|
||||
SOURCE_TABLE_NAME,
|
||||
TARGET_TABLE_NAME,
|
||||
name: FK_NAME,
|
||||
reverse_lock_order: true
|
||||
)
|
||||
end
|
||||
|
||||
add_concurrent_partitioned_foreign_key(
|
||||
SOURCE_TABLE_NAME,
|
||||
TARGET_TABLE_NAME,
|
||||
column: [PARTITION_COLUMN, COLUMN],
|
||||
target_column: [PARTITION_COLUMN, TARGET_COLUMN],
|
||||
validate: false,
|
||||
reverse_lock_order: true,
|
||||
on_update: :cascade,
|
||||
on_delete: :cascade,
|
||||
name: FK_NAME
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveFkToCiPipelinesPCiStagesOnPipelineId < Gitlab::Database::Migration[2.2]
|
||||
include Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers
|
||||
|
||||
milestone '17.3'
|
||||
disable_ddl_transaction!
|
||||
|
||||
SOURCE_TABLE_NAME = :p_ci_stages
|
||||
TARGET_TABLE_NAME = :ci_pipelines
|
||||
COLUMN = :pipeline_id
|
||||
TARGET_COLUMN = :id
|
||||
FK_NAME = :fk_fb57e6cc56
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(
|
||||
SOURCE_TABLE_NAME,
|
||||
TARGET_TABLE_NAME,
|
||||
name: FK_NAME,
|
||||
reverse_lock_order: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_partitioned_foreign_key(
|
||||
SOURCE_TABLE_NAME,
|
||||
TARGET_TABLE_NAME,
|
||||
column: COLUMN,
|
||||
target_column: TARGET_COLUMN,
|
||||
validate: true,
|
||||
reverse_lock_order: true,
|
||||
on_delete: :cascade,
|
||||
name: FK_NAME
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PartitionedFkToCiPipelinesFromPCiPipelineVariables < Gitlab::Database::Migration[2.2]
|
||||
include Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers
|
||||
|
||||
milestone '17.3'
|
||||
disable_ddl_transaction!
|
||||
|
||||
SOURCE_TABLE_NAME = :p_ci_pipeline_variables
|
||||
TARGET_TABLE_NAME = :ci_pipelines
|
||||
COLUMN = :pipeline_id
|
||||
TARGET_COLUMN = :id
|
||||
FK_NAME = :fk_f29c5f4380_p
|
||||
PARTITION_COLUMN = :partition_id
|
||||
|
||||
def up
|
||||
add_concurrent_partitioned_foreign_key(
|
||||
SOURCE_TABLE_NAME,
|
||||
TARGET_TABLE_NAME,
|
||||
column: [PARTITION_COLUMN, COLUMN],
|
||||
target_column: [PARTITION_COLUMN, TARGET_COLUMN],
|
||||
validate: true,
|
||||
reverse_lock_order: true,
|
||||
on_update: :cascade,
|
||||
on_delete: :cascade,
|
||||
name: FK_NAME
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(
|
||||
SOURCE_TABLE_NAME,
|
||||
TARGET_TABLE_NAME,
|
||||
name: FK_NAME,
|
||||
reverse_lock_order: true
|
||||
)
|
||||
end
|
||||
|
||||
add_concurrent_partitioned_foreign_key(
|
||||
SOURCE_TABLE_NAME,
|
||||
TARGET_TABLE_NAME,
|
||||
column: [PARTITION_COLUMN, COLUMN],
|
||||
target_column: [PARTITION_COLUMN, TARGET_COLUMN],
|
||||
validate: false,
|
||||
reverse_lock_order: true,
|
||||
on_update: :cascade,
|
||||
on_delete: :cascade,
|
||||
name: FK_NAME
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveFkToCiPipelinesPCiPipelineVariablesOnPipelineId < Gitlab::Database::Migration[2.2]
|
||||
include Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers
|
||||
|
||||
milestone '17.3'
|
||||
disable_ddl_transaction!
|
||||
|
||||
SOURCE_TABLE_NAME = :p_ci_pipeline_variables
|
||||
TARGET_TABLE_NAME = :ci_pipelines
|
||||
COLUMN = :pipeline_id
|
||||
TARGET_COLUMN = :id
|
||||
FK_NAME = :fk_f29c5f4380
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(
|
||||
SOURCE_TABLE_NAME,
|
||||
TARGET_TABLE_NAME,
|
||||
name: FK_NAME,
|
||||
reverse_lock_order: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_partitioned_foreign_key(
|
||||
SOURCE_TABLE_NAME,
|
||||
TARGET_TABLE_NAME,
|
||||
column: COLUMN,
|
||||
target_column: TARGET_COLUMN,
|
||||
validate: true,
|
||||
reverse_lock_order: true,
|
||||
on_delete: :cascade,
|
||||
name: FK_NAME
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
9985f33c8b5ab8018a3f743fb300306f77eb58466fbe132d8e6dbdaebfcb3e8a
|
||||
|
|
@ -0,0 +1 @@
|
|||
cf9f90769fac3e309d5e416eec8b8d23a52cbaa0b72bb5e2d9de6efd2292c537
|
||||
|
|
@ -0,0 +1 @@
|
|||
75b640c4e33089a8779a561f55ee50b9ee3d8929558016cbdb34d05ccd44e05c
|
||||
|
|
@ -0,0 +1 @@
|
|||
40aaa66701b7c516703b682986213292015f01b5c8229f63623fb2686bb4850d
|
||||
|
|
@ -0,0 +1 @@
|
|||
93787f4a9865498a2ccb09f7930ba695beac3a4acea8c696425cd206d95a592e
|
||||
|
|
@ -0,0 +1 @@
|
|||
3e5b221dd034fc2374687b2869331a64bc464eb142848cef2184e21befde924f
|
||||
|
|
@ -0,0 +1 @@
|
|||
59e03fafc35387d5e8a3f947d595215bb8db7d6c438e6391f65077128f0c9277
|
||||
|
|
@ -0,0 +1 @@
|
|||
8892e07003e3aba2517253068209e578b2693df8ed84e4c966a49345a0c8eabb
|
||||
|
|
@ -0,0 +1 @@
|
|||
97298cb5ba78afd0b6a8cc85f140f43f0d7893f28a9afccc6b8f48dfaca80fbc
|
||||
|
|
@ -0,0 +1 @@
|
|||
b8fc1daefd2576064659f0715925470cba0eb408c42b7d213f1ee2f8f76d6036
|
||||
|
|
@ -0,0 +1 @@
|
|||
98bf6b969e0aa5564b6482885b508aca297b507407d59793196e976030fc73a2
|
||||
|
|
@ -0,0 +1 @@
|
|||
c32eb523d9a7abff2ee3252ddda9b7b7fb557a6aef62f61d2d8337482db03d5f
|
||||
|
|
@ -0,0 +1 @@
|
|||
455aec6cffd85fbc004624767589c41c9db2c9c1d9fa2e54a2e75fe9a32cbb41
|
||||
|
|
@ -1149,6 +1149,22 @@ RETURN NEW;
|
|||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_49862b4b3035() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW."group_id" IS NULL THEN
|
||||
SELECT "group_id"
|
||||
INTO NEW."group_id"
|
||||
FROM "approval_group_rules"
|
||||
WHERE "approval_group_rules"."id" = NEW."approval_group_rule_id";
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_49e070da6320() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
|
|
@ -5924,7 +5940,8 @@ ALTER SEQUENCE approval_group_rules_id_seq OWNED BY approval_group_rules.id;
|
|||
CREATE TABLE approval_group_rules_protected_branches (
|
||||
id bigint NOT NULL,
|
||||
approval_group_rule_id bigint NOT NULL,
|
||||
protected_branch_id bigint NOT NULL
|
||||
protected_branch_id bigint NOT NULL,
|
||||
group_id bigint
|
||||
);
|
||||
|
||||
CREATE SEQUENCE approval_group_rules_protected_branches_id_seq
|
||||
|
|
@ -17138,7 +17155,8 @@ CREATE TABLE sbom_sources (
|
|||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
source_type smallint NOT NULL,
|
||||
source jsonb DEFAULT '{}'::jsonb NOT NULL
|
||||
source jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
organization_id bigint DEFAULT 1 NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE sbom_sources_id_seq
|
||||
|
|
@ -26171,6 +26189,8 @@ CREATE INDEX index_approval_group_rules_on_approval_policy_rule_id ON approval_g
|
|||
|
||||
CREATE INDEX index_approval_group_rules_on_scan_result_policy_id ON approval_group_rules USING btree (scan_result_policy_id);
|
||||
|
||||
CREATE INDEX index_approval_group_rules_protected_branches_on_group_id ON approval_group_rules_protected_branches USING btree (group_id);
|
||||
|
||||
CREATE INDEX index_approval_group_rules_users_on_group_id ON approval_group_rules_users USING btree (group_id);
|
||||
|
||||
CREATE INDEX index_approval_group_rules_users_on_user_id ON approval_group_rules_users USING btree (user_id);
|
||||
|
|
@ -29099,7 +29119,9 @@ CREATE INDEX index_sbom_occurrences_vulnerabilities_on_vulnerability_id ON sbom_
|
|||
|
||||
CREATE INDEX index_sbom_source_packages_on_source_package_id_and_id ON sbom_occurrences USING btree (source_package_id, id);
|
||||
|
||||
CREATE UNIQUE INDEX index_sbom_sources_on_source_type_and_source ON sbom_sources USING btree (source_type, source);
|
||||
CREATE INDEX index_sbom_sources_on_organization_id ON sbom_sources USING btree (organization_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_sbom_sources_on_source_type_and_source_and_org_id ON sbom_sources USING btree (source_type, source, organization_id);
|
||||
|
||||
CREATE INDEX index_scan_execution_policy_rules_on_policy_mgmt_project_id ON scan_execution_policy_rules USING btree (security_policy_management_project_id);
|
||||
|
||||
|
|
@ -31813,6 +31835,8 @@ CREATE TRIGGER trigger_43484cb41aca BEFORE INSERT OR UPDATE ON wiki_repository_s
|
|||
|
||||
CREATE TRIGGER trigger_44558add1625 BEFORE INSERT OR UPDATE ON merge_request_assignees FOR EACH ROW EXECUTE FUNCTION trigger_44558add1625();
|
||||
|
||||
CREATE TRIGGER trigger_49862b4b3035 BEFORE INSERT OR UPDATE ON approval_group_rules_protected_branches FOR EACH ROW EXECUTE FUNCTION trigger_49862b4b3035();
|
||||
|
||||
CREATE TRIGGER trigger_49e070da6320 BEFORE INSERT OR UPDATE ON packages_dependency_links FOR EACH ROW EXECUTE FUNCTION trigger_49e070da6320();
|
||||
|
||||
CREATE TRIGGER trigger_4ad9a52a6614 BEFORE INSERT OR UPDATE ON sbom_occurrences_vulnerabilities FOR EACH ROW EXECUTE FUNCTION trigger_4ad9a52a6614();
|
||||
|
|
@ -32409,6 +32433,9 @@ ALTER TABLE ONLY vulnerability_reads
|
|||
ALTER TABLE ONLY approval_group_rules_groups
|
||||
ADD CONSTRAINT fk_50edc8134e FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY approval_group_rules_protected_branches
|
||||
ADD CONSTRAINT fk_514003db08 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY alert_management_alerts
|
||||
ADD CONSTRAINT fk_51ab4b6089 FOREIGN KEY (prometheus_alert_id) REFERENCES prometheus_alerts(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -33361,10 +33388,7 @@ ALTER TABLE ONLY epic_user_mentions
|
|||
ADD CONSTRAINT fk_f1ab52883e FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE p_ci_pipeline_variables
|
||||
ADD CONSTRAINT fk_f29c5f4380 FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ci_pipeline_variables
|
||||
ADD CONSTRAINT fk_f29c5f4380_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
|
||||
ADD CONSTRAINT fk_f29c5f4380_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY zoekt_indices
|
||||
ADD CONSTRAINT fk_f34800a202 FOREIGN KEY (zoekt_node_id) REFERENCES zoekt_nodes(id) ON DELETE CASCADE;
|
||||
|
|
@ -33409,10 +33433,7 @@ ALTER TABLE ONLY vulnerability_finding_evidences
|
|||
ADD CONSTRAINT fk_fa3efd4e94 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE p_ci_stages
|
||||
ADD CONSTRAINT fk_fb57e6cc56 FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ci_stages
|
||||
ADD CONSTRAINT fk_fb57e6cc56_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
|
||||
ADD CONSTRAINT fk_fb57e6cc56_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY agent_group_authorizations
|
||||
ADD CONSTRAINT fk_fb70782616 FOREIGN KEY (agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE;
|
||||
|
|
|
|||
|
|
@ -275,9 +275,9 @@ Microsoft has documented how its platform works with [the OIDC protocol](https:/
|
|||
|
||||
You can migrate to the Generic OpenID Connect configuration from both `azure_activedirectory_v2` and `azure_oauth2`.
|
||||
|
||||
First, set the `uid_field`, which differs between providers:
|
||||
First, set the `uid_field`. Both the `uid_field` and the `sub` claim that you can select as a `uid_field` vary depending on the provider. Signing in without setting the `uid_field` results in additional identities being created within GitLab that have to be manually modified:
|
||||
|
||||
| Provider | `uid` | Supporting information |
|
||||
| Provider | `uid_field` | Supporting information |
|
||||
|-----------------------------------------------------------------------------------------------------------------|-------|-----------------------------------------------------------------------|
|
||||
| [`omniauth-azure-oauth2`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/vendor/gems/omniauth-azure-oauth2) | `sub` | Additional attributes `oid` and `tid` are offered within the `info` object. |
|
||||
| [`omniauth-azure-activedirectory-v2`](https://github.com/RIPAGlobal/omniauth-azure-activedirectory-v2/) | `oid` | You must configure `oid` as `uid_field` when migrating. |
|
||||
|
|
@ -295,7 +295,7 @@ gitlab_rails['omniauth_providers'] = [
|
|||
name: "azure_oauth2",
|
||||
label: "Azure OIDC", # optional label for login button, defaults to "Openid Connect"
|
||||
args: {
|
||||
name: "azure_oauth2",
|
||||
name: "azure_oauth2", # this matches the existing azure_oauth2 provider name, and only the strategy_class immediately below configures OpenID Connect
|
||||
strategy_class: "OmniAuth::Strategies::OpenIDConnect",
|
||||
scope: ["openid", "profile", "email"],
|
||||
response_type: "code",
|
||||
|
|
@ -343,6 +343,20 @@ gitlab_rails['omniauth_providers'] = [
|
|||
|
||||
::EndTabs
|
||||
|
||||
As you migrate from `azure_oauth2` to `omniauth_openid_connect` as part of upgrading to GitLab 17.0 or above, the `sub` claim value set for your organization can vary. `azure_oauth2` uses Microsoft V1 endpoint while `azure_activedirectory_v2` and `omniauth_openid_connect` both use Microsoft V2 endpoint with a common `sub` value.
|
||||
|
||||
- For users with an email address in Entra ID, configure [`omniauth_auto_link_user`](../../integration/omniauth.md#link-existing-users-to-omniauth-users) to allow falling back to email address and updating the user's identity.
|
||||
|
||||
- For users with no email address, administrators must take one of the following actions:
|
||||
|
||||
- Set up another authentication method or enable sign-in using GitLab username and password. The user can then sign in and link their Azure identity manually using their profile.
|
||||
- Implement OpenID Connect as a new provider alongside the existing `azure_oauth2` so the user can sign in through OAuth2, and link their OpenID Connect identity (similar to the previous method). This method would also work for users with email addresses, as long as `auto_link_user` is enabled.
|
||||
- Update `extern_uid` manually. To do this, use the [API or Rails console](../../integration/omniauth.md#change-apps-or-configuration) to update the `extern_uid` for each user.
|
||||
This method may be required if the instance has already been upgraded to 17.0 or later, and users have attempted to sign in.
|
||||
|
||||
NOTE:
|
||||
`azure_oauth2` might have used Entra ID's `upn` claim as the email address, if the `email` claim was missing or blank when provisioning GitLab accounts.
|
||||
|
||||
### Configure Microsoft Azure Active Directory B2C
|
||||
|
||||
GitLab requires special
|
||||
|
|
|
|||
|
|
@ -2607,6 +2607,8 @@ job_with_id_tokens:
|
|||
|
||||
**Related topics**:
|
||||
|
||||
- [ID token authentication](../secrets/id_token_authentication.md).
|
||||
- [Connect to cloud services](../cloud_services/index.md).
|
||||
- [Keyless signing with Sigstore](signing_examples.md).
|
||||
|
||||
### `image`
|
||||
|
|
|
|||
|
|
@ -216,8 +216,8 @@ Use sentence case for topic titles. For example:
|
|||
|
||||
#### UI text
|
||||
|
||||
When referring to specific user interface text, like a button label or menu
|
||||
item, use the same capitalization that's displayed in the user interface.
|
||||
When referring to specific user interface text, like a button label, page, tab,
|
||||
or menu item, use the same capitalization that's displayed in the user interface.
|
||||
|
||||
If you think the user interface text contains style mistakes,
|
||||
create an issue or an MR to propose a change to the user interface text.
|
||||
|
|
@ -453,11 +453,15 @@ When the docs are generated, the output is:
|
|||
|
||||
To stop the command, press <kbd>Control</kbd>+<kbd>C</kbd>.
|
||||
|
||||
### Buttons in the UI
|
||||
### Buttons, tabs, and pages in the UI
|
||||
|
||||
For elements with a visible label, use the label in bold with matching case.
|
||||
|
||||
For example: `Select **Cancel**.`
|
||||
For example:
|
||||
|
||||
- `Select **Cancel**.`
|
||||
- `On the **Issues** page...`
|
||||
- `On the **Pipelines** tab...`
|
||||
|
||||
### Text entered in the UI
|
||||
|
||||
|
|
|
|||
|
|
@ -1173,6 +1173,10 @@ Instead of:
|
|||
Do not use **list** when referring to a [**dropdown list**](#dropdown-list).
|
||||
Use the full phrase **dropdown list** instead.
|
||||
|
||||
Also, do not use **list** when referring to a page. For example, the **Issues** page
|
||||
is populated with a list of issues. However, you should call it the **Issues** page,
|
||||
and not the **Issues** list.
|
||||
|
||||
## license
|
||||
|
||||
Licenses are different than subscriptions.
|
||||
|
|
@ -1966,6 +1970,13 @@ Examples:
|
|||
- Suggested Reviewers can recommend a person to review your merge request. (This phrase describes the feature.)
|
||||
- As you type, Suggested Reviewers are displayed. (This phrase is generic but still uses capital letters.)
|
||||
|
||||
## tab
|
||||
|
||||
Use bold for tab names. For example:
|
||||
|
||||
- The **Pipelines** tab
|
||||
- The **Overview** tab
|
||||
|
||||
## that
|
||||
|
||||
Do not use **that** when describing a noun. For example:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@ DETAILS:
|
|||
You can enable the Microsoft Azure OAuth 2.0 OmniAuth provider and sign in to
|
||||
GitLab with your Microsoft Azure credentials.
|
||||
|
||||
NOTE:
|
||||
If you're integrating GitLab with Azure/Entra ID for the first time,
|
||||
configure the [OpenID Connect protocol](../administration/auth/oidc.md#configure-microsoft-azure),
|
||||
which uses the Microsoft identity platform (v2.0) endpoint.
|
||||
|
||||
## Migrate to Generic OpenID Connect configuration
|
||||
|
||||
In GitLab 17.0 and later, instances using `azure_oauth2` must migrate to the Generic OpenID Connect configuration. For more information, see [Migrating to the OpenID Connect protocol](../administration/auth/oidc.md#migrate-to-generic-openid-connect-configuration).
|
||||
|
||||
## Register an Azure application
|
||||
|
||||
To enable the Microsoft Azure OAuth 2.0 OmniAuth provider, you must register
|
||||
|
|
|
|||
|
|
@ -48,3 +48,16 @@ For example, to change to the `main` branch:
|
|||
```shell
|
||||
git checkout main
|
||||
```
|
||||
|
||||
## Keep a branch up-to-date
|
||||
|
||||
Your branch does not automatically include changes merged to the default branch from other branches.
|
||||
To include changes merged after you created your branch, you must update your branch manually.
|
||||
|
||||
To update your branch with the latest changes in the default branch, either:
|
||||
|
||||
- Run `git rebase` to [rebase](git_rebase.md) your branch against the default branch. Use this command when you want
|
||||
your changes to be listed in Git logs after the changes from the default branch.
|
||||
- Run `git pull <remote-name> <default-branch-name>`. Use this command when you want your changes to appear in Git logs
|
||||
in chronological order with the changes from the default branch, or if you're sharing your branch with others. If
|
||||
you're unsure of the correct value for `<remote-name>`, run: `git remote`.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ DETAILS:
|
|||
FLAG:
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
This feature is available for testing, but not ready for production use.
|
||||
|
||||
Use Pipeline execution policies to enforce CI/CD jobs for all applicable projects.
|
||||
|
||||
|
|
@ -60,6 +59,7 @@ Note the following:
|
|||
- The `override_project_ci` strategy will not override other security policy configurations.
|
||||
- The `override_project_ci` strategy takes precedence over other policies using the `inject` strategy. If any policy with `override_project_ci` applies, the project CI configuration will be ignored.
|
||||
- You should choose unique job names for pipeline execution policies. Some CI/CD configurations are based on job names and it can lead to unwanted results if a job exists multiple times in the same pipeline. The `needs` keyword, for example makes one job dependent on another. In case of multiple jobs with the same name, it will randomly depend on one of them.
|
||||
- The ability to enforce a scan execution policy and pipeline execution policy concurrently against the same project is not currently supported. You can use pipeline execution policies in isolation, or you can create scan execution policies and pipeline execution policies that target a different set of projects within the scope. Support for enforcing both a scan execution policy and pipeline execution policy on the same project is proposed in [issue 473112](https://gitlab.com/gitlab-org/gitlab/-/issues/473112).
|
||||
|
||||
### Job naming best practice
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
class BackfillApprovalGroupRulesProtectedBranchesGroupId < BackfillDesiredShardingKeyJob
|
||||
operation_name :backfill_approval_group_rules_protected_branches_group_id
|
||||
feature_category :source_code_management
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -16,7 +16,7 @@ module Sidebars
|
|||
|
||||
override :sprite_icon
|
||||
def sprite_icon
|
||||
'messages'
|
||||
'bullhorn'
|
||||
end
|
||||
|
||||
override :active_routes
|
||||
|
|
|
|||
|
|
@ -13665,6 +13665,9 @@ msgstr ""
|
|||
msgid "ComplianceReport|Create a new framework"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceReport|Create policy"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceReport|Dismiss"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31672,6 +31675,12 @@ msgstr ""
|
|||
msgid "ManualOrdering|Couldn't save the order of the issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "ManualVariables|There are no manually-specified variables for this pipeline"
|
||||
msgstr ""
|
||||
|
||||
msgid "ManualVariables|When you %{helpPageUrlStart}run a pipeline manually%{helpPageUrlEnd}, you can specify additional CI/CD variables to use in that pipeline run."
|
||||
msgstr ""
|
||||
|
||||
msgid "Manually link this issue by adding it to the linked issue section of the %{linkStart}originating vulnerability%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
"@gitlab/cluster-client": "^2.2.0",
|
||||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/fonts": "^1.3.0",
|
||||
"@gitlab/svgs": "3.106.0",
|
||||
"@gitlab/svgs": "3.107.0",
|
||||
"@gitlab/ui": "86.13.0",
|
||||
"@gitlab/web-ide": "^0.0.1-dev-20240613133550",
|
||||
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
|
||||
|
|
|
|||
|
|
@ -23,11 +23,13 @@ RSpec.describe 'Database schema', feature_category: :database do
|
|||
ci_daily_build_group_report_results: [%w[partition_id last_pipeline_id]], # index on last_pipeline_id is sufficient
|
||||
ci_builds: [%w[partition_id stage_id], %w[partition_id execution_config_id], %w[partition_id upstream_pipeline_id], %w[auto_canceled_by_partition_id auto_canceled_by_id], %w[partition_id commit_id]], # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142804#note_1745483081
|
||||
ci_pipeline_variables: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient
|
||||
p_ci_pipeline_variables: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient
|
||||
ci_pipelines_config: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient
|
||||
ci_pipeline_metadata: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient
|
||||
ci_pipeline_messages: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient
|
||||
p_ci_builds: [%w[partition_id stage_id], %w[partition_id execution_config_id]], # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142804#note_1745483081
|
||||
ci_stages: [%w[partition_id pipeline_id]], # the index on pipeline_id is sufficient
|
||||
p_ci_stages: [%w[partition_id pipeline_id]], # the index on pipeline_id is sufficient
|
||||
ai_testing_terms_acceptances: %w[user_id], # testing terms only have 1 entry, and if the user is deleted the record should remain
|
||||
p_ci_builds_execution_configs: [%w[partition_id pipeline_id]], # the index on pipeline_id is enough
|
||||
ci_sources_pipelines: [%w[source_partition_id source_pipeline_id], %w[partition_id pipeline_id]],
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ RSpec.describe 'Runners', feature_category: :fleet_visibility do
|
|||
context 'when a project has enabled shared_runners' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
before do
|
||||
before_all do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
|
|
@ -142,6 +142,48 @@ RSpec.describe 'Runners', feature_category: :fleet_visibility do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the project_runner_edit_form_vue feature is enabled', :js do
|
||||
before do
|
||||
stub_feature_flags(project_runner_edit_form_vue: true)
|
||||
end
|
||||
|
||||
it 'user edits runner to set it as protected' do
|
||||
visit project_runners_path(project)
|
||||
|
||||
within_testid 'assigned_project_runners' do
|
||||
first('[data-testid="edit-runner-link"]').click
|
||||
end
|
||||
|
||||
expect(page.find_field('protected')).not_to be_checked
|
||||
|
||||
check 'protected'
|
||||
click_button 'Save changes'
|
||||
|
||||
expect(page).to have_content 'Protected Yes'
|
||||
end
|
||||
|
||||
context 'when a runner has a tag' do
|
||||
before do
|
||||
project_runner.update!(tag_list: ['tag'])
|
||||
end
|
||||
|
||||
it 'user edits runner to not run untagged jobs' do
|
||||
visit project_runners_path(project)
|
||||
|
||||
within_testid 'assigned_project_runners' do
|
||||
first('[data-testid="edit-runner-link"]').click
|
||||
end
|
||||
|
||||
expect(page.find_field('run-untagged')).to be_checked
|
||||
|
||||
uncheck 'run-untagged'
|
||||
click_button 'Save changes'
|
||||
|
||||
expect(page).to have_content 'Can run untagged jobs No'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a shared runner is activated on the project' do
|
||||
let!(:shared_runner) { create(:ci_runner, :instance) }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlEmptyState } from '@gitlab/ui';
|
||||
import EmptyState from '~/ci/pipeline_details/manual_variables/empty_state.vue';
|
||||
|
||||
describe('ManualVariablesEmptyState', () => {
|
||||
describe('when component is created', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(EmptyState);
|
||||
};
|
||||
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
|
||||
|
||||
it('should render empty state with message', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findEmptyState().props()).toMatchObject({
|
||||
svgPath: EmptyState.EMPTY_VARIABLES_SVG,
|
||||
title: 'There are no manually-specified variables for this pipeline',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import Vue from 'vue';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import ManualVariablesApp from '~/ci/pipeline_details/manual_variables/manual_variables.vue';
|
||||
import EmptyState from '~/ci/pipeline_details/manual_variables/empty_state.vue';
|
||||
import VariableTable from '~/ci/pipeline_details/manual_variables/variable_table.vue';
|
||||
import GetManualVariablesQuery from '~/ci/pipeline_details/manual_variables/graphql/queries/get_manual_variables.query.graphql';
|
||||
import { generateVariablePairs, mockManualVariableConnection } from './mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('ManualVariableApp', () => {
|
||||
let wrapper;
|
||||
const mockResolver = jest.fn();
|
||||
const createMockApolloProvider = (resolver) => {
|
||||
const requestHandlers = [[GetManualVariablesQuery, resolver]];
|
||||
|
||||
return createMockApollo(requestHandlers);
|
||||
};
|
||||
|
||||
const createComponent = (variables = []) => {
|
||||
mockResolver.mockResolvedValue(mockManualVariableConnection(variables));
|
||||
wrapper = shallowMount(ManualVariablesApp, {
|
||||
provide: {
|
||||
manualVariablesCount: variables.length,
|
||||
projectPath: 'root/ci-project',
|
||||
pipelineIid: '1',
|
||||
},
|
||||
apolloProvider: createMockApolloProvider(mockResolver),
|
||||
});
|
||||
};
|
||||
|
||||
const findEmptyState = () => wrapper.findComponent(EmptyState);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findVariableTable = () => wrapper.findComponent(VariableTable);
|
||||
|
||||
afterEach(() => {
|
||||
mockResolver.mockClear();
|
||||
});
|
||||
|
||||
describe('when component is created', () => {
|
||||
it('renders empty state when no variables were found', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findEmptyState().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders loading state when variables were found', () => {
|
||||
createComponent(generateVariablePairs(1));
|
||||
|
||||
expect(findEmptyState().exists()).toBe(false);
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
expect(findVariableTable().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders variable table when variables were retrieved', async () => {
|
||||
createComponent(generateVariablePairs(1));
|
||||
await waitForPromises();
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
expect(findVariableTable().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
export const generateVariablePairs = (count) => {
|
||||
return Array.from({ length: count }).map((_, index) => ({
|
||||
key: `key_${index}`,
|
||||
value: `value_${index}`,
|
||||
}));
|
||||
};
|
||||
|
||||
export const mockManualVariableConnection = (variables = []) => ({
|
||||
data: {
|
||||
project: {
|
||||
__typename: 'Project',
|
||||
id: 'root/ci-project/1',
|
||||
pipeline: {
|
||||
id: '1',
|
||||
manualVariables: {
|
||||
__typename: 'CiManualVariableConnection',
|
||||
nodes: variables.map((variable) => ({
|
||||
...variable,
|
||||
id: variable.key,
|
||||
})),
|
||||
},
|
||||
__typename: 'Pipeline',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
import { nextTick } from 'vue';
|
||||
import { GlPagination, GlButton } from '@gitlab/ui';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
|
||||
import VariableTable from '~/ci/pipeline_details/manual_variables/variable_table.vue';
|
||||
import { generateVariablePairs } from './mock_data';
|
||||
|
||||
const defaultCanReadVariables = true;
|
||||
const defaultManualVariablesCount = 0;
|
||||
|
||||
describe('ManualVariableTable', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (provides = {}, variables = []) => {
|
||||
wrapper = mountExtended(VariableTable, {
|
||||
provide: {
|
||||
manualVariablesCount: defaultManualVariablesCount,
|
||||
canReadVariables: defaultCanReadVariables,
|
||||
...provides,
|
||||
},
|
||||
propsData: {
|
||||
variables,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findButton = () => wrapper.findComponent(GlButton);
|
||||
const findPaginator = () => wrapper.findComponent(GlPagination);
|
||||
const findValues = () => wrapper.findAllByTestId('manual-variable-value');
|
||||
|
||||
describe('when component is created', () => {
|
||||
describe('reveal/hide button', () => {
|
||||
it('should render the button when has permissions', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should not render the button when does not have permissions', () => {
|
||||
createComponent({
|
||||
canReadVariables: false,
|
||||
});
|
||||
|
||||
expect(findButton().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('paginator', () => {
|
||||
it('should not render paginator without any data', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findPaginator().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should not render paginator with data less or equal to 15', () => {
|
||||
const mockData = generateVariablePairs(15);
|
||||
createComponent(
|
||||
{
|
||||
manualVariablesCount: mockData.length,
|
||||
},
|
||||
mockData,
|
||||
);
|
||||
|
||||
expect(findPaginator().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should render paginator when data is greater than 15', () => {
|
||||
const mockData = generateVariablePairs(16);
|
||||
|
||||
createComponent(
|
||||
{
|
||||
manualVariablesCount: mockData.length,
|
||||
},
|
||||
mockData,
|
||||
);
|
||||
|
||||
expect(findPaginator().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when click on the reveal/hide button', () => {
|
||||
it('should toggle button text', async () => {
|
||||
createComponent();
|
||||
const button = findButton();
|
||||
|
||||
expect(button.text()).toBe('Reveal values');
|
||||
button.vm.$emit('click');
|
||||
await nextTick();
|
||||
expect(button.text()).toBe('Hide values');
|
||||
});
|
||||
|
||||
it('should reveal the values when click on the button', async () => {
|
||||
const mockData = generateVariablePairs(15);
|
||||
|
||||
createComponent(
|
||||
{
|
||||
manualVariablesCount: mockData.length,
|
||||
},
|
||||
mockData,
|
||||
);
|
||||
|
||||
const values = findValues();
|
||||
expect(values).toHaveLength(mockData.length);
|
||||
|
||||
expect(values.wrappers.every((w) => w.text() === '****')).toBe(true);
|
||||
|
||||
await findButton().trigger('click');
|
||||
|
||||
expect(values.wrappers.map((w) => w.text())).toStrictEqual(mockData.map((d) => d.value));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { GlDisclosureDropdown, GlDisclosureDropdownItem, GlLoadingIcon } from '@gitlab/ui';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import mockPipelineActionsQueryResponse from 'test_fixtures/graphql/pipelines/get_pipeline_actions.query.graphql.json';
|
||||
|
|
@ -8,11 +7,10 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
|||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||
import PipelinesManualActions from '~/ci/pipelines_page/components/pipelines_manual_actions.vue';
|
||||
import getPipelineActionsQuery from '~/ci/pipelines_page/graphql/queries/get_pipeline_actions.query.graphql';
|
||||
import jobPlayMutation from '~/ci/jobs_page/graphql/mutations/job_play.mutation.graphql';
|
||||
import { TRACKING_CATEGORIES } from '~/ci/constants';
|
||||
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
|
||||
|
||||
|
|
@ -23,9 +21,10 @@ jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
|
|||
|
||||
describe('Pipeline manual actions', () => {
|
||||
let wrapper;
|
||||
let mock;
|
||||
|
||||
const queryHandler = jest.fn().mockResolvedValue(mockPipelineActionsQueryResponse);
|
||||
const jobPlayMutationHandler = jest.fn();
|
||||
|
||||
const {
|
||||
data: {
|
||||
project: {
|
||||
|
|
@ -36,9 +35,12 @@ describe('Pipeline manual actions', () => {
|
|||
},
|
||||
} = mockPipelineActionsQueryResponse;
|
||||
|
||||
const mockPath = nodes[2].playPath;
|
||||
|
||||
const createComponent = (limit = 50) => {
|
||||
const apolloProvider = createMockApollo([
|
||||
[getPipelineActionsQuery, queryHandler],
|
||||
[jobPlayMutation, jobPlayMutationHandler],
|
||||
]);
|
||||
|
||||
wrapper = shallowMountExtended(PipelinesManualActions, {
|
||||
provide: {
|
||||
fullPath: 'root/ci-project',
|
||||
|
|
@ -50,7 +52,7 @@ describe('Pipeline manual actions', () => {
|
|||
stubs: {
|
||||
GlDisclosureDropdown,
|
||||
},
|
||||
apolloProvider: createMockApollo([[getPipelineActionsQuery, queryHandler]]),
|
||||
apolloProvider,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -82,8 +84,6 @@ describe('Pipeline manual actions', () => {
|
|||
|
||||
describe('loaded', () => {
|
||||
beforeEach(async () => {
|
||||
mock = new MockAdapter(axios);
|
||||
|
||||
createComponent();
|
||||
|
||||
findDropdown().vm.$emit('shown');
|
||||
|
|
@ -92,7 +92,6 @@ describe('Pipeline manual actions', () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
confirmAction.mockReset();
|
||||
});
|
||||
|
||||
|
|
@ -108,8 +107,6 @@ describe('Pipeline manual actions', () => {
|
|||
|
||||
describe('on action click', () => {
|
||||
it('makes a request and toggles the loading state', async () => {
|
||||
mock.onPost(mockPath).reply(HTTP_STATUS_OK);
|
||||
|
||||
findAllDropdownItems().at(1).vm.$emit('action');
|
||||
|
||||
await nextTick();
|
||||
|
|
@ -119,10 +116,11 @@ describe('Pipeline manual actions', () => {
|
|||
await waitForPromises();
|
||||
|
||||
expect(findDropdown().props('loading')).toBe(false);
|
||||
expect(jobPlayMutationHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('makes a failed request and toggles the loading state', async () => {
|
||||
mock.onPost(mockPath).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
|
||||
jobPlayMutationHandler.mockRejectedValueOnce(new Error('GraphQL error'));
|
||||
|
||||
findAllDropdownItems().at(1).vm.$emit('action');
|
||||
|
||||
|
|
@ -160,9 +158,7 @@ describe('Pipeline manual actions', () => {
|
|||
.mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime());
|
||||
});
|
||||
|
||||
it('makes post request after confirming', async () => {
|
||||
mock.onPost(mockPath).reply(HTTP_STATUS_OK);
|
||||
|
||||
it('makes calls GraphQl mutation after confirming', async () => {
|
||||
confirmAction.mockResolvedValueOnce(true);
|
||||
|
||||
findAllDropdownItems().at(2).vm.$emit('action');
|
||||
|
|
@ -171,12 +167,10 @@ describe('Pipeline manual actions', () => {
|
|||
|
||||
await waitForPromises();
|
||||
|
||||
expect(mock.history.post).toHaveLength(1);
|
||||
expect(jobPlayMutationHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not make post request if confirmation is cancelled', async () => {
|
||||
mock.onPost(mockPath).reply(HTTP_STATUS_OK);
|
||||
|
||||
it('does not call GraphQl mutation if confirmation is cancelled', async () => {
|
||||
confirmAction.mockResolvedValueOnce(false);
|
||||
|
||||
findAllDropdownItems().at(2).vm.$emit('action');
|
||||
|
|
@ -185,7 +179,7 @@ describe('Pipeline manual actions', () => {
|
|||
|
||||
await waitForPromises();
|
||||
|
||||
expect(mock.history.post).toHaveLength(0);
|
||||
expect(jobPlayMutationHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('displays the remaining time in the dropdown', () => {
|
||||
|
|
|
|||
|
|
@ -8,25 +8,15 @@ RSpec.describe "Work items", '(JavaScript fixtures)', type: :request, feature_ca
|
|||
include JavaScriptFixturesHelpers
|
||||
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:group_work_item_types_query_path) { 'work_items/graphql/group_work_item_types.query.graphql' }
|
||||
let(:project_work_item_types_query_path) { 'work_items/graphql/project_work_item_types.query.graphql' }
|
||||
let(:namespace_work_item_types_query_path) { 'work_items/graphql/namespace_work_item_types.query.graphql' }
|
||||
|
||||
it 'graphql/work_items/group_work_item_types.query.graphql.json' do
|
||||
query = get_graphql_query_as_string(group_work_item_types_query_path)
|
||||
it 'graphql/work_items/namespace_work_item_types.query.graphql.json' do
|
||||
query = get_graphql_query_as_string(namespace_work_item_types_query_path)
|
||||
|
||||
post_graphql(query, current_user: user, variables: { fullPath: group.full_path })
|
||||
|
||||
expect_graphql_errors_to_be_empty
|
||||
end
|
||||
|
||||
it 'graphql/work_items/project_work_item_types.query.graphql.json' do
|
||||
query = get_graphql_query_as_string(project_work_item_types_query_path)
|
||||
|
||||
post_graphql(query, current_user: user, variables: { fullPath: project.full_path })
|
||||
|
||||
expect_graphql_errors_to_be_empty
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import projectWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/project_work_item_types.query.graphql.json';
|
||||
import namespaceWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/namespace_work_item_types.query.graphql.json';
|
||||
import getIssueDetailsQuery from 'ee_else_ce/work_items/graphql/get_issue_details.query.graphql';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
|
|
@ -10,7 +10,7 @@ import { createAlert } from '~/alert';
|
|||
import Description from '~/issues/show/components/description.vue';
|
||||
import eventHub from '~/issues/show/event_hub';
|
||||
import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
|
||||
import workItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
|
||||
import workItemTypesQuery from '~/work_items/graphql/namespace_work_item_types.query.graphql';
|
||||
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
|
||||
import TaskList from '~/task_list';
|
||||
import { renderGFM } from '~/behaviors/markdown/render_gfm';
|
||||
|
|
@ -36,7 +36,7 @@ const $toast = {
|
|||
};
|
||||
|
||||
const issueDetailsResponse = getIssueDetailsResponse();
|
||||
const workItemTypesQueryHandler = jest.fn().mockResolvedValue(projectWorkItemTypesQueryResponse);
|
||||
const workItemTypesQueryHandler = jest.fn().mockResolvedValue(namespaceWorkItemTypesQueryResponse);
|
||||
|
||||
describe('Description component', () => {
|
||||
let wrapper;
|
||||
|
|
@ -285,7 +285,7 @@ describe('Description component', () => {
|
|||
|
||||
it('calls a mutation to create a task', () => {
|
||||
const workItemTypeIdForTask =
|
||||
projectWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes.find(
|
||||
namespaceWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes.find(
|
||||
(node) => node.name === 'Task',
|
||||
).id;
|
||||
const { confidential, iteration, milestone } = issueDetailsResponse.data.issue;
|
||||
|
|
|
|||
|
|
@ -2,15 +2,13 @@ import Vue, { nextTick } from 'vue';
|
|||
import VueApollo from 'vue-apollo';
|
||||
import { GlDisclosureDropdownItem, GlModal } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import projectWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/project_work_item_types.query.graphql.json';
|
||||
import groupWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/group_work_item_types.query.graphql.json';
|
||||
import namespaceWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/namespace_work_item_types.query.graphql.json';
|
||||
import { setNewWorkItemCache } from '~/work_items/graphql/cache_utils';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import CreateWorkItem from '~/work_items/components/create_work_item.vue';
|
||||
import CreateWorkItemModal from '~/work_items/components/create_work_item_modal.vue';
|
||||
import groupWorkItemTypesQuery from '~/work_items/graphql/group_work_item_types.query.graphql';
|
||||
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
|
||||
import namespaceWorkItemTypesQuery from '~/work_items/graphql/namespace_work_item_types.query.graphql';
|
||||
|
||||
const showToast = jest.fn();
|
||||
jest.mock('~/work_items/graphql/cache_utils', () => ({
|
||||
|
|
@ -28,34 +26,19 @@ describe('CreateWorkItemModal', () => {
|
|||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findForm = () => wrapper.findComponent(CreateWorkItem);
|
||||
|
||||
const projectSingleWorkItemTypeQueryResponse = {
|
||||
const namespaceSingleWorkItemTypeQueryResponse = {
|
||||
data: {
|
||||
workspace: {
|
||||
...projectWorkItemTypesQueryResponse.data.workspace,
|
||||
...namespaceWorkItemTypesQueryResponse.data.workspace,
|
||||
workItemTypes: {
|
||||
nodes: [projectWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes[0]],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const groupSingleWorkItemQueryResponse = {
|
||||
data: {
|
||||
workspace: {
|
||||
...groupWorkItemTypesQueryResponse.data.workspace,
|
||||
workItemTypes: {
|
||||
nodes: [groupWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes[0]],
|
||||
nodes: [namespaceWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes[0]],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const workItemTypesQueryHandler = jest.fn().mockResolvedValue({
|
||||
data: projectSingleWorkItemTypeQueryResponse.data,
|
||||
});
|
||||
|
||||
const groupWorkItemTypesQueryHandler = jest.fn().mockResolvedValue({
|
||||
data: groupSingleWorkItemQueryResponse.data,
|
||||
data: namespaceSingleWorkItemTypeQueryResponse.data,
|
||||
});
|
||||
|
||||
const workItemTypesEmptyQueryHandler = jest.fn().mockResolvedValue({
|
||||
|
|
@ -71,12 +54,11 @@ describe('CreateWorkItemModal', () => {
|
|||
|
||||
const createComponent = ({
|
||||
workItemTypeName = 'EPIC',
|
||||
projectWorkItemTypesQueryHandler = workItemTypesQueryHandler,
|
||||
namespaceWorkItemTypesQueryHandler = workItemTypesQueryHandler,
|
||||
asDropdownItem = false,
|
||||
} = {}) => {
|
||||
apolloProvider = createMockApollo([
|
||||
[projectWorkItemTypesQuery, projectWorkItemTypesQueryHandler],
|
||||
[groupWorkItemTypesQuery, groupWorkItemTypesQueryHandler],
|
||||
[namespaceWorkItemTypesQuery, namespaceWorkItemTypesQueryHandler],
|
||||
]);
|
||||
|
||||
wrapper = shallowMount(CreateWorkItemModal, {
|
||||
|
|
@ -151,7 +133,7 @@ describe('CreateWorkItemModal', () => {
|
|||
});
|
||||
|
||||
it('when there are no work item types it does not set the cache', async () => {
|
||||
createComponent({ projectWorkItemTypesQueryHandler: workItemTypesEmptyQueryHandler });
|
||||
createComponent({ namespaceWorkItemTypesQueryHandler: workItemTypesEmptyQueryHandler });
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ import Vue, { nextTick } from 'vue';
|
|||
import VueApollo from 'vue-apollo';
|
||||
import { GlAlert, GlFormSelect } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import projectWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/project_work_item_types.query.graphql.json';
|
||||
import groupWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/group_work_item_types.query.graphql.json';
|
||||
import namespaceWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/namespace_work_item_types.query.graphql.json';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import CreateWorkItem from '~/work_items/components/create_work_item.vue';
|
||||
|
|
@ -12,20 +11,19 @@ import WorkItemDescription from '~/work_items/components/work_item_description.v
|
|||
import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
|
||||
import WorkItemLabels from '~/work_items/components/work_item_labels.vue';
|
||||
import { WORK_ITEM_TYPE_ENUM_EPIC } from '~/work_items/constants';
|
||||
import groupWorkItemTypesQuery from '~/work_items/graphql/group_work_item_types.query.graphql';
|
||||
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
|
||||
import namespaceWorkItemTypesQuery from '~/work_items/graphql/namespace_work_item_types.query.graphql';
|
||||
import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
|
||||
import groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
|
||||
import { resolvers } from '~/graphql_shared/issuable_client';
|
||||
import { createWorkItemMutationResponse, createWorkItemQueryResponse } from '../mock_data';
|
||||
|
||||
const projectSingleWorkItemTypeQueryResponse = {
|
||||
const namespaceSingleWorkItemTypeQueryResponse = {
|
||||
data: {
|
||||
workspace: {
|
||||
...projectWorkItemTypesQueryResponse.data.workspace,
|
||||
...namespaceWorkItemTypesQueryResponse.data.workspace,
|
||||
workItemTypes: {
|
||||
nodes: [projectWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes[0]],
|
||||
nodes: [namespaceWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes[0]],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -45,7 +43,9 @@ describe('Create work item component', () => {
|
|||
.fn()
|
||||
.mockResolvedValue(createWorkItemQueryResponse);
|
||||
const groupWorkItemQuerySuccessHandler = jest.fn().mockResolvedValue(createWorkItemQueryResponse);
|
||||
|
||||
const namespaceWorkItemTypesHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(namespaceWorkItemTypesQueryResponse);
|
||||
const findFormTitle = () => wrapper.find('h1');
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findTitleInput = () => wrapper.findComponent(WorkItemTitle);
|
||||
|
|
@ -71,24 +71,19 @@ describe('Create work item component', () => {
|
|||
[groupWorkItemByIidQuery, groupWorkItemQuerySuccessHandler],
|
||||
[workItemByIidQuery, projectWorkItemQuerySuccessHandler],
|
||||
[createWorkItemMutation, mutationHandler],
|
||||
[namespaceWorkItemTypesQuery, namespaceWorkItemTypesHandler],
|
||||
],
|
||||
resolvers,
|
||||
{ typePolicies: { Project: { merge: true } } },
|
||||
);
|
||||
|
||||
const projectWorkItemTypeResponse = singleWorkItemType
|
||||
? projectSingleWorkItemTypeQueryResponse
|
||||
: projectWorkItemTypesQueryResponse;
|
||||
const namespaceWorkItemTypeResponse = singleWorkItemType
|
||||
? namespaceSingleWorkItemTypeQueryResponse
|
||||
: namespaceWorkItemTypesQueryResponse;
|
||||
mockApollo.clients.defaultClient.cache.writeQuery({
|
||||
query: isGroup ? groupWorkItemTypesQuery : projectWorkItemTypesQuery,
|
||||
query: namespaceWorkItemTypesQuery,
|
||||
variables: { fullPath: 'full-path', name: workItemTypeName },
|
||||
data: isGroup
|
||||
? {
|
||||
...groupWorkItemTypesQueryResponse.data,
|
||||
}
|
||||
: {
|
||||
...projectWorkItemTypeResponse.data,
|
||||
},
|
||||
data: namespaceWorkItemTypeResponse.data,
|
||||
});
|
||||
|
||||
wrapper = shallowMount(CreateWorkItem, {
|
||||
|
|
@ -173,27 +168,13 @@ describe('Create work item component', () => {
|
|||
});
|
||||
|
||||
describe('Work item types dropdown', () => {
|
||||
it('displays a list of project work item types', async () => {
|
||||
it('displays a list of namespace work item types', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
||||
// +1 for the "None" option
|
||||
const expectedOptions =
|
||||
projectWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes.length + 1;
|
||||
|
||||
expect(findSelect().attributes('options').split(',')).toHaveLength(expectedOptions);
|
||||
});
|
||||
|
||||
it('fetches group work item types when isGroup is true', async () => {
|
||||
createComponent({
|
||||
isGroup: true,
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
const expectedOptions =
|
||||
groupWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes.length + 1;
|
||||
|
||||
namespaceWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes.length + 1;
|
||||
expect(findSelect().attributes('options').split(',')).toHaveLength(expectedOptions);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
|
||||
import projectWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/project_work_item_types.query.graphql.json';
|
||||
import namespaceWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/namespace_work_item_types.query.graphql.json';
|
||||
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
|
|
@ -33,7 +33,7 @@ import {
|
|||
} from '~/work_items/constants';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
|
||||
import updateWorkItemNotificationsMutation from '~/work_items/graphql/update_work_item_notifications.mutation.graphql';
|
||||
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
|
||||
import namespaceWorkItemTypesQuery from '~/work_items/graphql/namespace_work_item_types.query.graphql';
|
||||
import convertWorkItemMutation from '~/work_items/graphql/work_item_convert.mutation.graphql';
|
||||
|
||||
import {
|
||||
|
|
@ -86,7 +86,7 @@ describe('WorkItemActions component', () => {
|
|||
hide: jest.fn(),
|
||||
};
|
||||
|
||||
const typesQuerySuccessHandler = jest.fn().mockResolvedValue(projectWorkItemTypesQueryResponse);
|
||||
const typesQuerySuccessHandler = jest.fn().mockResolvedValue(namespaceWorkItemTypesQueryResponse);
|
||||
const convertWorkItemMutationSuccessHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(convertWorkItemMutationResponse);
|
||||
|
|
@ -125,7 +125,7 @@ describe('WorkItemActions component', () => {
|
|||
wrapper = shallowMountExtended(WorkItemActions, {
|
||||
isLoggedIn: isLoggedIn(),
|
||||
apolloProvider: createMockApollo([
|
||||
[projectWorkItemTypesQuery, typesQuerySuccessHandler],
|
||||
[namespaceWorkItemTypesQuery, typesQuerySuccessHandler],
|
||||
[convertWorkItemMutation, convertWorkItemMutationHandler],
|
||||
[updateWorkItemNotificationsMutation, notificationsMutationHandler],
|
||||
[updateWorkItemMutation, lockDiscussionMutationHandler],
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import Vue, { nextTick } from 'vue';
|
||||
import { GlForm, GlFormGroup, GlFormInput, GlFormCheckbox, GlTooltip } from '@gitlab/ui';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import projectWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/project_work_item_types.query.graphql.json';
|
||||
import groupWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/group_work_item_types.query.graphql.json';
|
||||
import namespaceWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/namespace_work_item_types.query.graphql.json';
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
|
|
@ -23,27 +22,24 @@ import {
|
|||
WORK_ITEM_TYPE_ENUM_EPIC,
|
||||
} from '~/work_items/constants';
|
||||
import projectWorkItemsQuery from '~/work_items/graphql/project_work_items.query.graphql';
|
||||
import groupWorkItemTypesQuery from '~/work_items/graphql/group_work_item_types.query.graphql';
|
||||
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
|
||||
import namespaceWorkItemTypesQuery from '~/work_items/graphql/namespace_work_item_types.query.graphql';
|
||||
import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
|
||||
import updateWorkItemHierarchyMutation from '~/work_items/graphql/update_work_item_hierarchy.mutation.graphql';
|
||||
import groupProjectsForLinksWidgetQuery from '~/work_items/graphql/group_projects_for_links_widget.query.graphql';
|
||||
import relatedProjectsForLinksWidgetQuery from '~/work_items/graphql/related_projects_for_links_widget.query.graphql';
|
||||
import namespaceProjectsForLinksWidgetQuery from '~/work_items/graphql/namespace_projects_for_links_widget.query.graphql';
|
||||
import {
|
||||
availableWorkItemsResponse,
|
||||
createWorkItemMutationResponse,
|
||||
updateWorkItemMutationResponse,
|
||||
mockIterationWidgetResponse,
|
||||
groupProjectsList,
|
||||
relatedProjectsList,
|
||||
namespaceProjectsList,
|
||||
} from '../../mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const projectData = groupProjectsList.data.group.projects.nodes;
|
||||
const projectData = namespaceProjectsList.data.namespace.projects.nodes;
|
||||
|
||||
const findWorkItemTypeId = (typeName) => {
|
||||
return projectWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes.find(
|
||||
return namespaceWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes.find(
|
||||
(node) => node.name === typeName,
|
||||
).id;
|
||||
};
|
||||
|
|
@ -63,12 +59,12 @@ describe('WorkItemLinksForm', () => {
|
|||
const createMutationResolver = jest.fn().mockResolvedValue(createWorkItemMutationResponse);
|
||||
const createMutationRejection = jest.fn().mockRejectedValue(new Error('error'));
|
||||
const availableWorkItemsResolver = jest.fn().mockResolvedValue(availableWorkItemsResponse);
|
||||
const projectWorkItemTypesResolver = jest
|
||||
const namespaceWorkItemTypesResolver = jest
|
||||
.fn()
|
||||
.mockResolvedValue(projectWorkItemTypesQueryResponse);
|
||||
const groupWorkItemTypesResolver = jest.fn().mockResolvedValue(groupWorkItemTypesQueryResponse);
|
||||
const groupProjectsFormLinksWidgetResolver = jest.fn().mockResolvedValue(groupProjectsList);
|
||||
const relatedProjectsForLinksWidgetResolver = jest.fn().mockResolvedValue(relatedProjectsList);
|
||||
.mockResolvedValue(namespaceWorkItemTypesQueryResponse);
|
||||
const namespaceProjectsFormLinksWidgetResolver = jest
|
||||
.fn()
|
||||
.mockResolvedValue(namespaceProjectsList);
|
||||
|
||||
const mockParentIteration = mockIterationWidgetResponse;
|
||||
|
||||
|
|
@ -87,10 +83,8 @@ describe('WorkItemLinksForm', () => {
|
|||
wrapper = shallowMountExtended(WorkItemLinksForm, {
|
||||
apolloProvider: createMockApollo([
|
||||
[projectWorkItemsQuery, availableWorkItemsResolver],
|
||||
[projectWorkItemTypesQuery, projectWorkItemTypesResolver],
|
||||
[groupWorkItemTypesQuery, groupWorkItemTypesResolver],
|
||||
[groupProjectsForLinksWidgetQuery, groupProjectsFormLinksWidgetResolver],
|
||||
[relatedProjectsForLinksWidgetQuery, relatedProjectsForLinksWidgetResolver],
|
||||
[namespaceWorkItemTypesQuery, namespaceWorkItemTypesResolver],
|
||||
[namespaceProjectsForLinksWidgetQuery, namespaceProjectsFormLinksWidgetResolver],
|
||||
[updateWorkItemHierarchyMutation, updateMutation],
|
||||
[createWorkItemMutation, createMutation],
|
||||
]),
|
||||
|
|
@ -142,8 +136,8 @@ describe('WorkItemLinksForm', () => {
|
|||
|
||||
it.each`
|
||||
workspace | isGroup | queryResolver
|
||||
${'project'} | ${false} | ${projectWorkItemTypesResolver}
|
||||
${'group'} | ${true} | ${groupWorkItemTypesResolver}
|
||||
${'project'} | ${false} | ${namespaceWorkItemTypesResolver}
|
||||
${'group'} | ${true} | ${namespaceWorkItemTypesResolver}
|
||||
`(
|
||||
'fetches $workspace work item types when isGroup is $isGroup',
|
||||
async ({ isGroup, queryResolver }) => {
|
||||
|
|
|
|||
|
|
@ -5,19 +5,13 @@ import { mountExtended } from 'helpers/vue_test_utils_helper';
|
|||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import WorkItemProjectsListbox from '~/work_items/components/work_item_links/work_item_projects_listbox.vue';
|
||||
import groupProjectsForLinksWidgetQuery from '~/work_items/graphql/group_projects_for_links_widget.query.graphql';
|
||||
import relatedProjectsForLinksWidgetQuery from '~/work_items/graphql/related_projects_for_links_widget.query.graphql';
|
||||
import namespaceProjectsForLinksWidgetQuery from '~/work_items/graphql/namespace_projects_for_links_widget.query.graphql';
|
||||
import { SEARCH_DEBOUNCE } from '~/work_items/constants';
|
||||
import {
|
||||
groupProjectsList,
|
||||
relatedProjectsList,
|
||||
mockFrequentlyUsedProjects,
|
||||
} from '../../mock_data';
|
||||
import { namespaceProjectsList, mockFrequentlyUsedProjects } from '../../mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const groupProjectsData = groupProjectsList.data.group.projects.nodes;
|
||||
const relatedProjectsData = relatedProjectsList.data.project.group.projects.nodes;
|
||||
const namespaceProjectsData = namespaceProjectsList.data.namespace.projects.nodes;
|
||||
|
||||
describe('WorkItemProjectsListbox', () => {
|
||||
/**
|
||||
|
|
@ -37,8 +31,9 @@ describe('WorkItemProjectsListbox', () => {
|
|||
localStorage.removeItem(getLocalstorageKey());
|
||||
};
|
||||
|
||||
const groupProjectsFormLinksWidgetResolver = jest.fn().mockResolvedValue(groupProjectsList);
|
||||
const relatedProjectsFormLinksWidgetResolver = jest.fn().mockResolvedValue(relatedProjectsList);
|
||||
const namespaceProjectsFormLinksWidgetResolver = jest
|
||||
.fn()
|
||||
.mockResolvedValue(namespaceProjectsList);
|
||||
|
||||
const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
|
||||
const findDropdownItemFor = (fullPath) => wrapper.findByTestId(`listbox-item-${fullPath}`);
|
||||
|
|
@ -48,8 +43,7 @@ describe('WorkItemProjectsListbox', () => {
|
|||
const createComponent = async (isGroup = true, fullPath = 'group-a') => {
|
||||
wrapper = mountExtended(WorkItemProjectsListbox, {
|
||||
apolloProvider: createMockApollo([
|
||||
[groupProjectsForLinksWidgetQuery, groupProjectsFormLinksWidgetResolver],
|
||||
[relatedProjectsForLinksWidgetQuery, relatedProjectsFormLinksWidgetResolver],
|
||||
[namespaceProjectsForLinksWidgetQuery, namespaceProjectsFormLinksWidgetResolver],
|
||||
]),
|
||||
propsData: {
|
||||
fullPath,
|
||||
|
|
@ -72,10 +66,10 @@ describe('WorkItemProjectsListbox', () => {
|
|||
|
||||
expect(findDropdown().text()).not.toContain('Recently used');
|
||||
|
||||
const dropdownItem = findDropdownItemFor(groupProjectsData[0].fullPath);
|
||||
const dropdownItem = findDropdownItemFor(namespaceProjectsData[0].fullPath);
|
||||
|
||||
expect(dropdownItem.text()).toContain(groupProjectsData[0].name);
|
||||
expect(dropdownItem.text()).toContain(groupProjectsData[0].namespace.name);
|
||||
expect(dropdownItem.text()).toContain(namespaceProjectsData[0].name);
|
||||
expect(dropdownItem.text()).toContain(namespaceProjectsData[0].namespace.name);
|
||||
});
|
||||
|
||||
it('supports selecting a project', async () => {
|
||||
|
|
@ -85,13 +79,13 @@ describe('WorkItemProjectsListbox', () => {
|
|||
|
||||
await nextTick();
|
||||
|
||||
await findDropdownItemFor(groupProjectsData[0].fullPath).trigger('click');
|
||||
await findDropdownItemFor(namespaceProjectsData[0].fullPath).trigger('click');
|
||||
|
||||
await nextTick();
|
||||
|
||||
const emitted = wrapper.emitted('selectProject');
|
||||
|
||||
expect(emitted[1][0]).toEqual(groupProjectsData[0]);
|
||||
expect(emitted[1][0]).toEqual(namespaceProjectsData[0]);
|
||||
});
|
||||
|
||||
it('renders recent projects if present', async () => {
|
||||
|
|
@ -122,7 +116,7 @@ describe('WorkItemProjectsListbox', () => {
|
|||
|
||||
const emitted = wrapper.emitted('selectProject');
|
||||
|
||||
expect(emitted[1][0]).toEqual(groupProjectsData[1]);
|
||||
expect(emitted[1][0]).toEqual(namespaceProjectsData[1]);
|
||||
});
|
||||
|
||||
it('supports filtering recent projects via search input', async () => {
|
||||
|
|
@ -143,7 +137,7 @@ describe('WorkItemProjectsListbox', () => {
|
|||
content = findRecentDropdownItems();
|
||||
|
||||
expect(content).toHaveLength(1);
|
||||
expect(content.at(0).text()).toContain(groupProjectsData[0].name);
|
||||
expect(content.at(0).text()).toContain(namespaceProjectsData[0].name);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -158,10 +152,10 @@ describe('WorkItemProjectsListbox', () => {
|
|||
|
||||
expect(findDropdown().text()).not.toContain('Recently used');
|
||||
|
||||
const dropdownItem = findDropdownItemFor(relatedProjectsData[0].fullPath);
|
||||
const dropdownItem = findDropdownItemFor(namespaceProjectsData[0].fullPath);
|
||||
|
||||
expect(dropdownItem.text()).toContain(relatedProjectsData[0].name);
|
||||
expect(dropdownItem.text()).toContain(relatedProjectsData[0].namespace.name);
|
||||
expect(dropdownItem.text()).toContain(namespaceProjectsData[0].name);
|
||||
expect(dropdownItem.text()).toContain(namespaceProjectsData[0].namespace.name);
|
||||
});
|
||||
|
||||
it('auto-selects the current project', async () => {
|
||||
|
|
@ -169,7 +163,7 @@ describe('WorkItemProjectsListbox', () => {
|
|||
|
||||
const emitted = wrapper.emitted('selectProject');
|
||||
|
||||
expect(emitted[0][0]).toEqual(relatedProjectsData[0]);
|
||||
expect(emitted[0][0]).toEqual(namespaceProjectsData[0]);
|
||||
});
|
||||
|
||||
it('supports selecting a project', async () => {
|
||||
|
|
@ -179,13 +173,13 @@ describe('WorkItemProjectsListbox', () => {
|
|||
|
||||
await nextTick();
|
||||
|
||||
await findDropdownItemFor(relatedProjectsData[1].fullPath).trigger('click');
|
||||
await findDropdownItemFor(namespaceProjectsData[1].fullPath).trigger('click');
|
||||
|
||||
await nextTick();
|
||||
|
||||
const emitted = wrapper.emitted('selectProject');
|
||||
|
||||
expect(emitted[1][0]).toEqual(relatedProjectsData[1]);
|
||||
expect(emitted[1][0]).toEqual(namespaceProjectsData[1]);
|
||||
});
|
||||
|
||||
it('renders recent projects if present', async () => {
|
||||
|
|
@ -216,7 +210,7 @@ describe('WorkItemProjectsListbox', () => {
|
|||
|
||||
const emitted = wrapper.emitted('selectProject');
|
||||
|
||||
expect(emitted[1][0]).toEqual(relatedProjectsData[1]);
|
||||
expect(emitted[1][0]).toEqual(namespaceProjectsData[1]);
|
||||
});
|
||||
|
||||
it('supports filtering recent projects via search input', async () => {
|
||||
|
|
@ -237,7 +231,7 @@ describe('WorkItemProjectsListbox', () => {
|
|||
content = findRecentDropdownItems();
|
||||
|
||||
expect(content).toHaveLength(1);
|
||||
expect(content.at(0).text()).toContain(relatedProjectsData[0].name);
|
||||
expect(content.at(0).text()).toContain(namespaceProjectsData[0].name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlLoadingIcon, GlToggle } from '@gitlab/ui';
|
||||
import { GlLoadingIcon, GlToggle, GlIcon } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
|
@ -43,6 +43,8 @@ describe('WorkItemTree', () => {
|
|||
const findWorkItemLinkChildrenWrapper = () => wrapper.findComponent(WorkItemChildrenWrapper);
|
||||
const findShowLabelsToggle = () => wrapper.findComponent(GlToggle);
|
||||
const findTreeActions = () => wrapper.findComponent(WorkItemTreeActions);
|
||||
const findRolledUpWeight = () => wrapper.findByTestId('rollup-weight');
|
||||
const findRolledUpWeightValue = () => wrapper.findByTestId('weight-value');
|
||||
|
||||
const createComponent = async ({
|
||||
workItemType = 'Objective',
|
||||
|
|
@ -53,6 +55,8 @@ describe('WorkItemTree', () => {
|
|||
canUpdateChildren = true,
|
||||
hasSubepicsFeature = true,
|
||||
workItemHierarchyTreeHandler = workItemHierarchyTreeResponseHandler,
|
||||
showRolledUpWeight = false,
|
||||
rolledUpWeight = 0,
|
||||
} = {}) => {
|
||||
wrapper = shallowMountExtended(WorkItemTree, {
|
||||
propsData: {
|
||||
|
|
@ -64,6 +68,8 @@ describe('WorkItemTree', () => {
|
|||
confidential,
|
||||
canUpdate,
|
||||
canUpdateChildren,
|
||||
showRolledUpWeight,
|
||||
rolledUpWeight,
|
||||
},
|
||||
apolloProvider: createMockApollo([[getWorkItemTreeQuery, workItemHierarchyTreeHandler]]),
|
||||
provide: {
|
||||
|
|
@ -248,4 +254,31 @@ describe('WorkItemTree', () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('rollup data', () => {
|
||||
describe('rolledUp weight', () => {
|
||||
it.each`
|
||||
showRolledUpWeight | rolledUpWeight | expected
|
||||
${false} | ${0} | ${'rollup weight is not displayed'}
|
||||
${false} | ${10} | ${'rollup weight is not displayed'}
|
||||
${true} | ${0} | ${'rollup weight is displayed'}
|
||||
${true} | ${10} | ${'rollup weight is displayed'}
|
||||
`(
|
||||
'When showRolledUpWeight is $showRolledUpWeight and rolledUpWeight is $rolledUpWeight, $expected',
|
||||
({ showRolledUpWeight, rolledUpWeight }) => {
|
||||
createComponent({ showRolledUpWeight, rolledUpWeight });
|
||||
|
||||
expect(findRolledUpWeight().exists()).toBe(showRolledUpWeight);
|
||||
},
|
||||
);
|
||||
|
||||
it('should show the correct value when rolledUpWeight is visible', () => {
|
||||
createComponent({ showRolledUpWeight: true, rolledUpWeight: 10 });
|
||||
|
||||
expect(findRolledUpWeight().exists()).toBe(true);
|
||||
expect(findRolledUpWeight().findComponent(GlIcon).props('name')).toBe('weight');
|
||||
expect(findRolledUpWeightValue().text()).toBe('10');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { GlButton, GlIcon } from '@gitlab/ui';
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
|
||||
import projectWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/project_work_item_types.query.graphql.json';
|
||||
import namespaceWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/namespace_work_item_types.query.graphql.json';
|
||||
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
|
@ -12,7 +12,7 @@ import { isLoggedIn } from '~/lib/utils/common_utils';
|
|||
import toast from '~/vue_shared/plugins/global_toast';
|
||||
import WorkItemNotificationsWidget from '~/work_items/components/work_item_notifications_widget.vue';
|
||||
import updateWorkItemNotificationsMutation from '~/work_items/graphql/update_work_item_notifications.mutation.graphql';
|
||||
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
|
||||
import namespaceWorkItemTypesQuery from '~/work_items/graphql/namespace_work_item_types.query.graphql';
|
||||
|
||||
import { updateWorkItemNotificationsMutationResponse } from '../mock_data';
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ describe('WorkItemActions component', () => {
|
|||
hide: jest.fn(),
|
||||
};
|
||||
|
||||
const typesQuerySuccessHandler = jest.fn().mockResolvedValue(projectWorkItemTypesQueryResponse);
|
||||
const typesQuerySuccessHandler = jest.fn().mockResolvedValue(namespaceWorkItemTypesQueryResponse);
|
||||
const toggleNotificationsOffHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(updateWorkItemNotificationsMutationResponse(false));
|
||||
|
|
@ -53,7 +53,7 @@ describe('WorkItemActions component', () => {
|
|||
wrapper = shallowMountExtended(WorkItemNotificationsWidget, {
|
||||
isLoggedIn: isLoggedIn(),
|
||||
apolloProvider: createMockApollo([
|
||||
[projectWorkItemTypesQuery, typesQuerySuccessHandler],
|
||||
[namespaceWorkItemTypesQuery, typesQuerySuccessHandler],
|
||||
[updateWorkItemNotificationsMutation, notificationsMutationHandler],
|
||||
]),
|
||||
propsData: {
|
||||
|
|
|
|||
|
|
@ -930,6 +930,7 @@ export const workItemResponseFactory = ({
|
|||
linkedItems = mockEmptyLinkedItems,
|
||||
developmentItems = workItemDevelopmentFragmentResponse(),
|
||||
color = '#1068bf',
|
||||
editableWeightWidget = true,
|
||||
} = {}) => ({
|
||||
data: {
|
||||
workItem: {
|
||||
|
|
@ -1020,9 +1021,15 @@ export const workItemResponseFactory = ({
|
|||
: { type: 'MOCK TYPE' },
|
||||
weightWidgetPresent
|
||||
? {
|
||||
__typename: 'WorkItemWidgetWeight',
|
||||
type: 'WEIGHT',
|
||||
weight: 0,
|
||||
weight: null,
|
||||
rolledUpWeight: 0,
|
||||
widgetDefinition: {
|
||||
editable: editableWeightWidget,
|
||||
rollUp: !editableWeightWidget,
|
||||
__typename: 'WorkItemWidgetDefinitionWeight',
|
||||
},
|
||||
__typename: 'WorkItemWidgetWeight',
|
||||
}
|
||||
: { type: 'MOCK TYPE' },
|
||||
iterationWidgetPresent
|
||||
|
|
@ -4352,9 +4359,9 @@ export const allowedChildrenTypesResponse = {
|
|||
export const generateWorkItemsListWithId = (count) =>
|
||||
Array.from({ length: count }, (_, i) => ({ id: `gid://gitlab/WorkItem/${i + 1}` }));
|
||||
|
||||
export const groupProjectsList = {
|
||||
export const namespaceProjectsList = {
|
||||
data: {
|
||||
group: {
|
||||
namespace: {
|
||||
id: 'gid://gitlab/Group/1',
|
||||
projects: {
|
||||
nodes: [
|
||||
|
|
@ -4392,50 +4399,6 @@ export const groupProjectsList = {
|
|||
},
|
||||
};
|
||||
|
||||
export const relatedProjectsList = {
|
||||
data: {
|
||||
project: {
|
||||
id: 'gid://gitlab/Project/1',
|
||||
group: {
|
||||
id: 'gid://gitlab/Group/33',
|
||||
projects: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/Project/1',
|
||||
name: 'Example project A',
|
||||
avatarUrl: null,
|
||||
nameWithNamespace: 'Group A / Example project A',
|
||||
fullPath: 'group-a/example-project-a',
|
||||
namespace: {
|
||||
id: 'gid://gitlab/Group/1',
|
||||
name: 'Group A',
|
||||
__typename: 'Namespace',
|
||||
},
|
||||
__typename: 'Project',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/Project/2',
|
||||
name: 'Example project B',
|
||||
avatarUrl: null,
|
||||
nameWithNamespace: 'Group A / Example project B',
|
||||
fullPath: 'group-a/example-project-b',
|
||||
namespace: {
|
||||
id: 'gid://gitlab/Group/1',
|
||||
name: 'Group A',
|
||||
__typename: 'Namespace',
|
||||
},
|
||||
__typename: 'Project',
|
||||
},
|
||||
],
|
||||
__typename: 'ProjectConnection',
|
||||
},
|
||||
__typename: 'Group',
|
||||
},
|
||||
__typename: 'Project',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockFrequentlyUsedProjects = [
|
||||
{
|
||||
id: 1,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackfillApprovalGroupRulesProtectedBranchesGroupId,
|
||||
feature_category: :source_code_management,
|
||||
schema: 20240716135028 do
|
||||
include_examples 'desired sharding key backfill job' do
|
||||
let(:batch_table) { :approval_group_rules_protected_branches }
|
||||
let(:backfill_column) { :group_id }
|
||||
let(:backfill_via_table) { :approval_group_rules }
|
||||
let(:backfill_via_column) { :group_id }
|
||||
let(:backfill_via_foreign_key) { :approval_group_rule_id }
|
||||
end
|
||||
end
|
||||
|
|
@ -6,7 +6,7 @@ RSpec.describe Sidebars::Admin::Menus::MessagesMenu, feature_category: :navigati
|
|||
it_behaves_like 'Admin menu',
|
||||
link: '/admin/broadcast_messages',
|
||||
title: s_('Admin|Messages'),
|
||||
icon: 'messages'
|
||||
icon: 'bullhorn'
|
||||
|
||||
it_behaves_like 'Admin menu without sub menus', active_routes: { controller: :broadcast_messages }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueBackfillApprovalGroupRulesProtectedBranchesGroupId, feature_category: :source_code_management do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
it 'schedules a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
table_name: :approval_group_rules_protected_branches,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE,
|
||||
gitlab_schema: :gitlab_main_cell,
|
||||
job_arguments: [
|
||||
:group_id,
|
||||
:approval_group_rules,
|
||||
:group_id,
|
||||
:approval_group_rule_id
|
||||
]
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -40,14 +40,14 @@ RSpec.describe 'projects/pipelines/show', feature_category: :pipeline_compositio
|
|||
render
|
||||
|
||||
expect(rendered).to have_link s_('Go to the pipeline editor'),
|
||||
href: project_ci_pipeline_editor_path(project)
|
||||
href: project_ci_pipeline_editor_path(project, branch_name: pipeline.source_ref)
|
||||
end
|
||||
|
||||
it 'renders the pipeline editor button with correct link for users who can not view' do
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_link s_('Go to the pipeline editor'),
|
||||
href: project_ci_pipeline_editor_path(project)
|
||||
href: project_ci_pipeline_editor_path(project, branch_name: pipeline.source_ref)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1354,10 +1354,10 @@
|
|||
stylelint-declaration-strict-value "1.10.4"
|
||||
stylelint-scss "6.0.0"
|
||||
|
||||
"@gitlab/svgs@3.106.0":
|
||||
version "3.106.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.106.0.tgz#eb5a2331f59c5c258c54dd6f905dda57b4c68c0e"
|
||||
integrity sha512-NOU2eW7aQaOtll2DxYeLR/IIphMP8l7WKHUqB1tbIAVt/QOPio9pJym17FzpBc1Deibxtf7dS0wEl4DoNLTxoA==
|
||||
"@gitlab/svgs@3.107.0":
|
||||
version "3.107.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.107.0.tgz#dfc55de9ae0af42255fe9acca00f3b0d55fe11fd"
|
||||
integrity sha512-kKlCJsxdt3GGus60tkfb31fzdmH9P8eVeCqzaopyA05ojzcLjmD5LzU7AVQGrkELsGLRi9A0+5NLVR7v0gjL6A==
|
||||
|
||||
"@gitlab/ui@86.13.0":
|
||||
version "86.13.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue