Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e7d07db84d
commit
5e0b1a97dc
|
|
@ -80,6 +80,15 @@ export const config = {
|
|||
},
|
||||
},
|
||||
},
|
||||
WorkItemWidgetHierarchy: {
|
||||
fields: {
|
||||
// If we add any key args, the children field becomes children({"first":10}) and
|
||||
// kills any possibility to handle it on the widget level without hardcoding a string.
|
||||
children: {
|
||||
keyArgs: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
WorkItem: {
|
||||
fields: {
|
||||
// widgets policy because otherwise the subscriptions invalidate the cache
|
||||
|
|
|
|||
|
|
@ -336,8 +336,7 @@ export default {
|
|||
update: (cache, { data: { workItemCreate } }) =>
|
||||
addHierarchyChild({
|
||||
cache,
|
||||
fullPath: this.fullPath,
|
||||
iid: this.issueIid,
|
||||
id: convertToGraphQLId(TYPENAME_WORK_ITEM, this.issueId),
|
||||
workItem: workItemCreate.workItem,
|
||||
}),
|
||||
});
|
||||
|
|
@ -371,8 +370,7 @@ export default {
|
|||
update: (cache) =>
|
||||
removeHierarchyChild({
|
||||
cache,
|
||||
fullPath: this.fullPath,
|
||||
iid: this.issueIid,
|
||||
id: convertToGraphQLId(TYPENAME_WORK_ITEM, this.issueId),
|
||||
workItem: { id },
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
import SharedDeleteButton from './shared/delete_button.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -11,11 +10,6 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: __('Delete project'),
|
||||
},
|
||||
formPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -47,7 +41,6 @@ export default {
|
|||
<template>
|
||||
<shared-delete-button
|
||||
:confirm-phrase="confirmPhrase"
|
||||
:button-text="buttonText"
|
||||
:form-path="formPath"
|
||||
:is-fork="isFork"
|
||||
:issues-count="issuesCount"
|
||||
|
|
|
|||
|
|
@ -15,11 +15,6 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: __('Delete project'),
|
||||
},
|
||||
formPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -63,6 +58,9 @@ export default {
|
|||
this.isModalVisible = true;
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
deleteProject: __('Delete project'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -91,7 +89,7 @@ export default {
|
|||
variant="danger"
|
||||
data-testid="delete-button"
|
||||
@click="onButtonClick"
|
||||
>{{ buttonText }}</gl-button
|
||||
>{{ $options.i18n.deleteProject }}</gl-button
|
||||
>
|
||||
</gl-form>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ export default (selector = '#js-project-delete-button') => {
|
|||
|
||||
const {
|
||||
confirmPhrase,
|
||||
buttonText,
|
||||
formPath,
|
||||
isFork,
|
||||
issuesCount,
|
||||
|
|
@ -25,7 +24,6 @@ export default (selector = '#js-project-delete-button') => {
|
|||
return createElement(ProjectDeleteButton, {
|
||||
props: {
|
||||
confirmPhrase,
|
||||
buttonText,
|
||||
formPath,
|
||||
isFork: parseBoolean(isFork),
|
||||
issuesCount: parseInt(issuesCount, 10),
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql
|
|||
import groupWorkItemByIidQuery from '../graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
|
||||
import getAllowedWorkItemChildTypes from '../graphql/work_item_allowed_children.query.graphql';
|
||||
import { findHierarchyWidgetChildren, findHierarchyWidgetDefinition } from '../utils';
|
||||
import { findHierarchyWidgetDefinition } from '../utils';
|
||||
|
||||
import WorkItemTree from './work_item_links/work_item_tree.vue';
|
||||
import WorkItemActions from './work_item_actions.vue';
|
||||
|
|
@ -119,6 +119,7 @@ export default {
|
|||
isStickyHeaderShowing: false,
|
||||
editMode: false,
|
||||
draftData: {},
|
||||
hasChildren: false,
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
|
|
@ -282,12 +283,6 @@ export default {
|
|||
workItemNotes() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_NOTES);
|
||||
},
|
||||
children() {
|
||||
return this.workItem ? findHierarchyWidgetChildren(this.workItem) : [];
|
||||
},
|
||||
hasChildren() {
|
||||
return !isEmpty(this.children);
|
||||
},
|
||||
workItemBodyClass() {
|
||||
return {
|
||||
'gl-pt-5': !this.updateError && !this.isModal,
|
||||
|
|
@ -672,13 +667,13 @@ export default {
|
|||
:parent-work-item-type="workItem.workItemType.name"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-iid="workItemIid"
|
||||
:children="children"
|
||||
:can-update="canUpdate"
|
||||
:can-update-children="canUpdateChildren"
|
||||
:confidential="workItem.confidential"
|
||||
:allowed-child-types="allowedChildTypes"
|
||||
@show-modal="openInModal"
|
||||
@addChild="$emit('addChild')"
|
||||
@childrenLoaded="hasChildren = $event"
|
||||
/>
|
||||
<work-item-relationships
|
||||
v-if="workItemLinkedItems"
|
||||
|
|
|
|||
|
|
@ -8,13 +8,14 @@ import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
|||
import { s__ } from '~/locale';
|
||||
import { defaultSortableOptions, DRAG_DELAY } from '~/sortable/constants';
|
||||
|
||||
import { WORK_ITEM_TYPE_VALUE_OBJECTIVE } from '../../constants';
|
||||
import { WORK_ITEM_TYPE_VALUE_OBJECTIVE, DEFAULT_PAGE_SIZE_CHILD_ITEMS } from '../../constants';
|
||||
import { findHierarchyWidgets } from '../../utils';
|
||||
import { addHierarchyChild, removeHierarchyChild } from '../../graphql/cache_utils';
|
||||
import reorderWorkItem from '../../graphql/reorder_work_item.mutation.graphql';
|
||||
import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
|
||||
import groupWorkItemByIidQuery from '../../graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '../../graphql/work_item_by_iid.query.graphql';
|
||||
import getWorkItemTreeQuery from '../../graphql/work_item_tree.query.graphql';
|
||||
import WorkItemLinkChild from './work_item_link_child.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -97,9 +98,7 @@ export default {
|
|||
update: (cache) =>
|
||||
removeHierarchyChild({
|
||||
cache,
|
||||
fullPath: this.fullPath,
|
||||
iid: this.workItemIid,
|
||||
isGroup: this.isGroup,
|
||||
id: this.workItemId,
|
||||
workItem: child,
|
||||
}),
|
||||
});
|
||||
|
|
@ -130,9 +129,7 @@ export default {
|
|||
update: (cache) =>
|
||||
addHierarchyChild({
|
||||
cache,
|
||||
fullPath: this.fullPath,
|
||||
iid: this.workItemIid,
|
||||
isGroup: this.isGroup,
|
||||
id: this.workItemId,
|
||||
workItem: child,
|
||||
}),
|
||||
});
|
||||
|
|
@ -228,12 +225,12 @@ export default {
|
|||
update: (store) => {
|
||||
store.updateQuery(
|
||||
{
|
||||
query: this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery,
|
||||
variables: { fullPath: this.fullPath, iid: this.workItemIid },
|
||||
query: getWorkItemTreeQuery,
|
||||
variables: { id: this.workItemId, pageSize: DEFAULT_PAGE_SIZE_CHILD_ITEMS },
|
||||
},
|
||||
(sourceData) =>
|
||||
produce(sourceData, (draftData) => {
|
||||
const { widgets } = draftData.workspace.workItem;
|
||||
const { widgets } = draftData.workItem;
|
||||
const hierarchyWidget = findHierarchyWidgets(widgets);
|
||||
hierarchyWidget.children.nodes = updatedChildren;
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
WIDGET_TYPE_HIERARCHY,
|
||||
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
|
||||
WORK_ITEM_TYPE_VALUE_TASK,
|
||||
DEFAULT_PAGE_SIZE_CHILD_ITEMS,
|
||||
} from '../../constants';
|
||||
import getWorkItemTreeQuery from '../../graphql/work_item_tree.query.graphql';
|
||||
import WorkItemLinkChildContents from '../shared/work_item_link_child_contents.vue';
|
||||
|
|
@ -138,6 +139,7 @@ export default {
|
|||
query: getWorkItemTreeQuery,
|
||||
variables: {
|
||||
id: this.childItem.id,
|
||||
pageSize: DEFAULT_PAGE_SIZE_CHILD_ITEMS,
|
||||
},
|
||||
});
|
||||
this.children = this.getWidgetByType(data?.workItem, WIDGET_TYPE_HIERARCHY).children.nodes;
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ import {
|
|||
WORK_ITEM_STATUS_TEXT,
|
||||
I18N_WORK_ITEM_SHOW_LABELS,
|
||||
TASKS_ANCHOR,
|
||||
DEFAULT_PAGE_SIZE_CHILD_ITEMS,
|
||||
} from '../../constants';
|
||||
import { findHierarchyWidgetChildren } from '../../utils';
|
||||
import { removeHierarchyChild } from '../../graphql/cache_utils';
|
||||
import groupWorkItemByIidQuery from '../../graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '../../graphql/work_item_by_iid.query.graphql';
|
||||
import getWorkItemTreeQuery from '../../graphql/work_item_tree.query.graphql';
|
||||
import WidgetWrapper from '../widget_wrapper.vue';
|
||||
import WorkItemDetailModal from '../work_item_detail_modal.vue';
|
||||
import WorkItemLinksForm from './work_item_links_form.vue';
|
||||
|
|
@ -62,19 +62,19 @@ export default {
|
|||
apollo: {
|
||||
workItem: {
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery;
|
||||
return getWorkItemTreeQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
iid: this.iid,
|
||||
id: this.issuableGid,
|
||||
pageSize: DEFAULT_PAGE_SIZE_CHILD_ITEMS,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return data.workspace.workItem ?? {};
|
||||
return data.workItem ?? {};
|
||||
},
|
||||
skip() {
|
||||
return !this.iid;
|
||||
return !this.issuableId;
|
||||
},
|
||||
error(e) {
|
||||
this.error = e.message || this.$options.i18n.fetchError;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ 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 updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
|
||||
import updateWorkItemHierarchyMutation from '../../graphql/update_work_item_hierarchy.mutation.graphql';
|
||||
import createWorkItemMutation from '../../graphql/create_work_item.mutation.graphql';
|
||||
import {
|
||||
FORM_TYPES,
|
||||
|
|
@ -271,7 +271,7 @@ export default {
|
|||
this.submitInProgress = true;
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: updateWorkItemMutation,
|
||||
mutation: updateWorkItemHierarchyMutation,
|
||||
variables: {
|
||||
input: {
|
||||
id: this.issuableGid,
|
||||
|
|
@ -311,9 +311,7 @@ export default {
|
|||
update: (cache, { data }) =>
|
||||
addHierarchyChild({
|
||||
cache,
|
||||
fullPath: this.fullPath,
|
||||
iid: this.workItemIid,
|
||||
isGroup: this.isGroup,
|
||||
id: this.issuableGid,
|
||||
workItem: data.workItemCreate.workItem,
|
||||
}),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlToggle } from '@gitlab/ui';
|
||||
import { GlToggle, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
import {
|
||||
FORM_TYPES,
|
||||
|
|
@ -12,7 +12,10 @@ import {
|
|||
WORK_ITEM_TYPE_ENUM_EPIC,
|
||||
I18N_WORK_ITEM_SHOW_LABELS,
|
||||
CHILD_ITEMS_ANCHOR,
|
||||
DEFAULT_PAGE_SIZE_CHILD_ITEMS,
|
||||
} from '../../constants';
|
||||
import { findHierarchyWidgets } from '../../utils';
|
||||
import getWorkItemTreeQuery from '../../graphql/work_item_tree.query.graphql';
|
||||
import WidgetWrapper from '../widget_wrapper.vue';
|
||||
import WorkItemActionsSplitButton from './work_item_actions_split_button.vue';
|
||||
import WorkItemLinksForm from './work_item_links_form.vue';
|
||||
|
|
@ -31,6 +34,7 @@ export default {
|
|||
WorkItemChildrenWrapper,
|
||||
WorkItemTreeActions,
|
||||
GlToggle,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
inject: ['hasSubepicsFeature'],
|
||||
props: {
|
||||
|
|
@ -61,11 +65,6 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
children: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
canUpdate: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
|
@ -92,6 +91,28 @@ export default {
|
|||
showLabels: true,
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
hierarchyWidget: {
|
||||
query: getWorkItemTreeQuery,
|
||||
variables() {
|
||||
return {
|
||||
id: this.workItemId,
|
||||
pageSize: DEFAULT_PAGE_SIZE_CHILD_ITEMS,
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
return !this.workItemId;
|
||||
},
|
||||
update({ workItem = {} }) {
|
||||
const { children } = findHierarchyWidgets(workItem.widgets);
|
||||
this.$emit('childrenLoaded', Boolean(children?.count));
|
||||
return children || {};
|
||||
},
|
||||
error() {
|
||||
this.error = s__('WorkItems|An error occurred while fetching children');
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
childrenIds() {
|
||||
return this.children.map((c) => c.id);
|
||||
|
|
@ -135,6 +156,15 @@ export default {
|
|||
canShowActionsMenu() {
|
||||
return this.workItemType.toUpperCase() === WORK_ITEM_TYPE_ENUM_EPIC && this.workItemIid;
|
||||
},
|
||||
children() {
|
||||
return this.hierarchyWidget?.nodes || [];
|
||||
},
|
||||
isLoadingChildren() {
|
||||
return this.$apollo.queries.hierarchyWidget.loading;
|
||||
},
|
||||
showEmptyMessage() {
|
||||
return !this.isShownAddForm && this.children.length === 0 && !this.isLoadingChildren;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
genericActionItems(workItem) {
|
||||
|
|
@ -206,7 +236,7 @@ export default {
|
|||
</template>
|
||||
<template #body>
|
||||
<div class="gl-new-card-content gl-px-0">
|
||||
<div v-if="!isShownAddForm && children.length === 0" data-testid="tree-empty">
|
||||
<div v-if="showEmptyMessage" data-testid="tree-empty">
|
||||
<p class="gl-new-card-empty">
|
||||
{{ $options.WORK_ITEMS_TREE_TEXT_MAP[workItemType].empty }}
|
||||
</p>
|
||||
|
|
@ -227,6 +257,7 @@ export default {
|
|||
@addChild="$emit('addChild')"
|
||||
/>
|
||||
<work-item-children-wrapper
|
||||
v-if="!isLoadingChildren"
|
||||
:children="children"
|
||||
:can-update="canUpdateChildren"
|
||||
:full-path="fullPath"
|
||||
|
|
@ -237,6 +268,7 @@ export default {
|
|||
@error="error = $event"
|
||||
@show-modal="showModal"
|
||||
/>
|
||||
<gl-loading-icon v-else size="md" />
|
||||
</div>
|
||||
</template>
|
||||
</widget-wrapper>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
|||
import { s__ } from '~/locale';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
|
||||
|
||||
import { removeHierarchyChild } from '../graphql/cache_utils';
|
||||
import { updateParent } from '../graphql/cache_utils';
|
||||
import groupWorkItemsQuery from '../graphql/group_work_items.query.graphql';
|
||||
import projectWorkItemsQuery from '../graphql/project_work_items.query.graphql';
|
||||
import {
|
||||
|
|
@ -174,7 +174,7 @@ export default {
|
|||
},
|
||||
},
|
||||
update: (cache) =>
|
||||
removeHierarchyChild({
|
||||
updateParent({
|
||||
cache,
|
||||
fullPath: this.fullPath,
|
||||
iid: this.oldParent?.iid,
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@ export const FORM_TYPES = {
|
|||
export const DEFAULT_PAGE_SIZE_ASSIGNEES = 10;
|
||||
export const DEFAULT_PAGE_SIZE_NOTES = 30;
|
||||
export const DEFAULT_PAGE_SIZE_EMOJIS = 100;
|
||||
export const DEFAULT_PAGE_SIZE_CHILD_ITEMS = 1000;
|
||||
|
||||
export const WORK_ITEM_NOTES_SORT_ORDER_KEY = 'sort_direction_work_item';
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import {
|
|||
} from '../constants';
|
||||
import groupWorkItemByIidQuery from './group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from './work_item_by_iid.query.graphql';
|
||||
import getWorkItemTreeQuery from './work_item_tree.query.graphql';
|
||||
|
||||
const getNotesWidgetFromSourceData = (draftData) =>
|
||||
draftData?.workspace?.workItem?.widgets.find(isNotesWidget);
|
||||
|
|
@ -154,10 +155,10 @@ export const updateCacheAfterRemovingAwardEmojiFromNote = (currentNotes, note) =
|
|||
});
|
||||
};
|
||||
|
||||
export const addHierarchyChild = ({ cache, fullPath, iid, isGroup, workItem }) => {
|
||||
export const addHierarchyChild = ({ cache, id, workItem }) => {
|
||||
const queryArgs = {
|
||||
query: isGroup ? groupWorkItemByIidQuery : workItemByIidQuery,
|
||||
variables: { fullPath, iid },
|
||||
query: getWorkItemTreeQuery,
|
||||
variables: { id },
|
||||
};
|
||||
const sourceData = cache.readQuery(queryArgs);
|
||||
|
||||
|
|
@ -168,19 +169,40 @@ export const addHierarchyChild = ({ cache, fullPath, iid, isGroup, workItem }) =
|
|||
cache.writeQuery({
|
||||
...queryArgs,
|
||||
data: produce(sourceData, (draftState) => {
|
||||
const existingChild = findHierarchyWidgetChildren(draftState.workspace?.workItem).find(
|
||||
const existingChild = findHierarchyWidgetChildren(draftState?.workItem).find(
|
||||
(child) => child.id === workItem?.id,
|
||||
);
|
||||
if (!existingChild) {
|
||||
findHierarchyWidgetChildren(draftState.workspace?.workItem).push(workItem);
|
||||
findHierarchyWidgetChildren(draftState?.workItem).push(workItem);
|
||||
}
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export const removeHierarchyChild = ({ cache, fullPath, iid, isGroup, workItem }) => {
|
||||
export const removeHierarchyChild = ({ cache, id, workItem }) => {
|
||||
const queryArgs = {
|
||||
query: isGroup ? groupWorkItemByIidQuery : workItemByIidQuery,
|
||||
query: getWorkItemTreeQuery,
|
||||
variables: { id },
|
||||
};
|
||||
const sourceData = cache.readQuery(queryArgs);
|
||||
|
||||
if (!sourceData) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache.writeQuery({
|
||||
...queryArgs,
|
||||
data: produce(sourceData, (draftState) => {
|
||||
const children = findHierarchyWidgetChildren(draftState?.workItem);
|
||||
const index = children.findIndex((child) => child.id === workItem.id);
|
||||
if (index >= 0) children.splice(index, 1);
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export const updateParent = ({ cache, query, fullPath, iid, workItem }) => {
|
||||
const queryArgs = {
|
||||
query,
|
||||
variables: { fullPath, iid },
|
||||
};
|
||||
const sourceData = cache.readQuery(queryArgs);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
#import "./work_item_hierarchy.fragment.graphql"
|
||||
|
||||
mutation workItemHierarchyUpdate($input: WorkItemUpdateInput!, $pageSize: Int = 1000) {
|
||||
workItemUpdate(input: $input) {
|
||||
workItem {
|
||||
...WorkItemHierarchy
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
|
||||
#import "ee_else_ce/work_items/graphql/work_item_metadata_widgets.fragment.graphql"
|
||||
|
||||
fragment WorkItemHierarchy on WorkItem {
|
||||
id
|
||||
workItemType {
|
||||
id
|
||||
name
|
||||
iconName
|
||||
}
|
||||
title
|
||||
confidential
|
||||
userPermissions {
|
||||
deleteWorkItem
|
||||
updateWorkItem
|
||||
adminParentLink
|
||||
setWorkItemMetadata
|
||||
createNote
|
||||
adminWorkItemLink
|
||||
}
|
||||
widgets {
|
||||
type
|
||||
... on WorkItemWidgetHierarchy {
|
||||
type
|
||||
parent {
|
||||
id
|
||||
}
|
||||
children(first: $pageSize) {
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
}
|
||||
count
|
||||
nodes {
|
||||
id
|
||||
iid
|
||||
confidential
|
||||
workItemType {
|
||||
id
|
||||
name
|
||||
iconName
|
||||
}
|
||||
title
|
||||
state
|
||||
createdAt
|
||||
closedAt
|
||||
webUrl
|
||||
reference(full: true)
|
||||
widgets {
|
||||
... on WorkItemWidgetHierarchy {
|
||||
type
|
||||
hasChildren
|
||||
}
|
||||
...WorkItemMetadataWidgets
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
...WorkItemMetadataWidgets
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +1,7 @@
|
|||
#import "~/graphql_shared/fragments/label.fragment.graphql"
|
||||
#import "~/graphql_shared/fragments/user.fragment.graphql"
|
||||
#import "ee_else_ce/work_items/graphql/work_item_metadata_widgets.fragment.graphql"
|
||||
#import "./work_item_hierarchy.fragment.graphql"
|
||||
|
||||
query workItemTreeQuery($id: WorkItemID!) {
|
||||
query workItemTreeQuery($id: WorkItemID!, $pageSize: Int = 1000) {
|
||||
workItem(id: $id) {
|
||||
id
|
||||
workItemType {
|
||||
id
|
||||
name
|
||||
iconName
|
||||
}
|
||||
title
|
||||
userPermissions {
|
||||
deleteWorkItem
|
||||
updateWorkItem
|
||||
}
|
||||
confidential
|
||||
widgets {
|
||||
type
|
||||
... on WorkItemWidgetHierarchy {
|
||||
type
|
||||
parent {
|
||||
id
|
||||
}
|
||||
children {
|
||||
nodes {
|
||||
id
|
||||
iid
|
||||
confidential
|
||||
workItemType {
|
||||
id
|
||||
name
|
||||
iconName
|
||||
}
|
||||
title
|
||||
state
|
||||
createdAt
|
||||
closedAt
|
||||
webUrl
|
||||
reference(full: true)
|
||||
widgets {
|
||||
... on WorkItemWidgetHierarchy {
|
||||
type
|
||||
hasChildren
|
||||
}
|
||||
...WorkItemMetadataWidgets
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
...WorkItemMetadataWidgets
|
||||
}
|
||||
...WorkItemHierarchy
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,34 +69,6 @@ fragment WorkItemWidgets on WorkItemWidget {
|
|||
iconName
|
||||
}
|
||||
}
|
||||
children {
|
||||
nodes {
|
||||
id
|
||||
iid
|
||||
confidential
|
||||
workItemType {
|
||||
id
|
||||
name
|
||||
iconName
|
||||
}
|
||||
title
|
||||
state
|
||||
createdAt
|
||||
closedAt
|
||||
webUrl
|
||||
reference(full: true)
|
||||
namespace {
|
||||
fullPath
|
||||
}
|
||||
widgets {
|
||||
... on WorkItemWidgetHierarchy {
|
||||
type
|
||||
hasChildren
|
||||
}
|
||||
...WorkItemMetadataWidgets
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on WorkItemWidgetMilestone {
|
||||
milestone {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
- c.with_body do
|
||||
= form_tag(group, method: :delete, id: remove_form_id) do
|
||||
%p
|
||||
= _('This action will permanently delete this group, including its subgroups and projects.')
|
||||
= _('Deleting this group also deletes all child projects, including archived projects, and their resources.')
|
||||
%br
|
||||
%strong= _('Deleted group can not be restored!')
|
||||
|
||||
= render 'groups/settings/remove_button', group: group, remove_form_id: remove_form_id
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
- remove_form_id = local_assigns.fetch(:remove_form_id, nil)
|
||||
- button_text = local_assigns.fetch(:button_text, nil)
|
||||
|
||||
- if group.prevent_delete?
|
||||
= render Pajamas::AlertComponent.new(variant: :tip, dismissible: false, alert_options: { class: 'gl-mb-5', data: { testid: 'group-has-linked-subscription-alert' }}) do |c|
|
||||
- c.with_body do
|
||||
= html_escape(_("This group can't be removed because it is linked to a subscription. To remove this group, %{linkStart}link the subscription%{linkEnd} with a different group.")) % { linkStart: "<a href=\"#{help_page_path('subscriptions/gitlab_com/index', anchor: 'change-the-linked-namespace')}\">".html_safe, linkEnd: '</a>'.html_safe }
|
||||
|
||||
.js-confirm-danger{ data: group_confirm_modal_data(group: group, remove_form_id: remove_form_id, button_text: button_text) }
|
||||
.js-confirm-danger{ data: group_confirm_modal_data(group: group, remove_form_id: remove_form_id) }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class DropTmpIndexFromVulnerabilityOccurrences < Gitlab::Database::Migration[2.2]
|
||||
TABLE_NAME = :vulnerability_occurrences
|
||||
INITIAL_PIPELINE_INDEX = 'tmp_index_vulnerability_occurrences_id_and_initial_pipline_id'
|
||||
LATEST_PIPELINE_INDEX = 'tmp_index_vulnerability_occurrences_id_and_latest_pipeline_id'
|
||||
|
||||
INITIAL_PIPELINE_COLUMNS = [:id, :initial_pipeline_id]
|
||||
LATEST_PIPELINE_COLUMNS = [:id, :latest_pipeline_id]
|
||||
|
||||
milestone '17.3'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
remove_concurrent_index_by_name TABLE_NAME, name: INITIAL_PIPELINE_INDEX
|
||||
remove_concurrent_index_by_name TABLE_NAME, name: LATEST_PIPELINE_INDEX
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index TABLE_NAME, INITIAL_PIPELINE_COLUMNS, name: INITIAL_PIPELINE_INDEX,
|
||||
where: 'initial_pipeline_id IS NULL'
|
||||
add_concurrent_index TABLE_NAME, LATEST_PIPELINE_COLUMNS, name: LATEST_PIPELINE_INDEX,
|
||||
where: 'latest_pipeline_id IS NULL'
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
1ceb75c51414ece44f8d25e14756d9926e7cf6f34b5b730706386fdbc769e1bd
|
||||
|
|
@ -30024,10 +30024,6 @@ CREATE INDEX tmp_index_on_vulnerabilities_non_dismissed ON vulnerabilities USING
|
|||
|
||||
CREATE INDEX tmp_index_project_statistics_cont_registry_size ON project_statistics USING btree (project_id) WHERE (container_registry_size = 0);
|
||||
|
||||
CREATE INDEX tmp_index_vulnerability_occurrences_id_and_initial_pipline_id ON vulnerability_occurrences USING btree (id, initial_pipeline_id) WHERE (initial_pipeline_id IS NULL);
|
||||
|
||||
CREATE INDEX tmp_index_vulnerability_occurrences_id_and_latest_pipeline_id ON vulnerability_occurrences USING btree (id, latest_pipeline_id) WHERE (latest_pipeline_id IS NULL);
|
||||
|
||||
CREATE INDEX tmp_index_vulnerability_overlong_title_html ON vulnerabilities USING btree (id) WHERE (length(title_html) > 800);
|
||||
|
||||
CREATE UNIQUE INDEX u_project_compliance_standards_adherence_for_reporting ON project_compliance_standards_adherence USING btree (project_id, check_name, standard);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ and can no longer be changed:
|
|||
To enable merge request approval settings for an instance:
|
||||
|
||||
1. On the left sidebar, at the bottom, select **Admin area**.
|
||||
1. Select **Push Rules**.
|
||||
1. Select **Push rules**.
|
||||
1. Expand **Merge request approvals**.
|
||||
1. Choose the required options.
|
||||
1. Select **Save changes**.
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@ Source Code Management shares ownership of Code Owners with the Code Review grou
|
|||
|
||||
- [Approval Rules](../../merge_request_concepts/approval_rules.md)
|
||||
|
||||
### Push Rules
|
||||
### Push rules
|
||||
|
||||
- [Push Rules development guidelines](../../push_rules/index.md)
|
||||
- [Push rules development guidelines](../../push_rules/index.md)
|
||||
|
||||
### Protected Branches
|
||||
|
||||
|
|
|
|||
|
|
@ -52,12 +52,14 @@ in the catalog.
|
|||
or ask one of the group owners to create an empty project for you.
|
||||
1. Follow the [standard guide for creating components](../../ci/components/index.md).
|
||||
1. Add a concise project description that clearly describes the capabilities offered by the component project.
|
||||
1. Ensure to follow the general guidance to [write a component](../../ci/components/index.md#write-a-component) as well as
|
||||
[those for the official components](#best-practices-for-official-components).
|
||||
1. Add a `LICENSE.md` file with the MIT license.
|
||||
1. Make sure to follow the general guidance given to [write a component](../../ci/components/index.md#write-a-component) as well as
|
||||
the guidance [for official components](#best-practices-for-official-components).
|
||||
1. Add a `LICENSE.md` file with the MIT license ([example](https://gitlab.com/components/ruby/-/blob/d8db5288b01947e8a931d8d1a410befed69325a7/LICENSE.md)).
|
||||
1. The project must have a `.gitlab-ci.yml` file that:
|
||||
- Validates all the components in the project correctly.
|
||||
- Contains a `release` job to publish newly released tags to the catalog.
|
||||
- Validates all the components in the project correctly
|
||||
([example](https://gitlab.com/components/secret-detection/-/blob/646d0fcbbf3c2a3e4b576f1884543c874041c633/.gitlab-ci.yml#L11-23)).
|
||||
- Contains a `release` job to publish newly released tags to the catalog
|
||||
([example](https://gitlab.com/components/secret-detection/-/blob/646d0fcbbf3c2a3e4b576f1884543c874041c633/.gitlab-ci.yml#L50-58)).
|
||||
1. For official component projects, upload the [official avatar image](img/avatar_component_project.png) to the component project.
|
||||
|
||||
### Best practices for official components
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ batch size may be increased or decreased, based on the performance of the last 2
|
|||
hide empty description
|
||||
skinparam ConditionEndStyle hline
|
||||
left to right direction
|
||||
rectangle "Batched Background Migration Queue" as migrations {
|
||||
rectangle "Batched background migration queue" as migrations {
|
||||
rectangle "Migration N (active)" as migrationn
|
||||
rectangle "Migration 1 (completed)" as migration1
|
||||
rectangle "Migration 2 (active)" as migration2
|
||||
|
|
@ -410,7 +410,7 @@ In the example above we need an index on `(type, id)` to support the filters. Se
|
|||
|
||||
### Access data for multiple databases
|
||||
|
||||
Background Migration contrary to regular migrations does have access to multiple databases
|
||||
Background migration contrary to regular migrations does have access to multiple databases
|
||||
and can be used to efficiently access and update data across them. To properly indicate
|
||||
a database to be used it is desired to create ActiveRecord model inline the migration code.
|
||||
Such model should use a correct [`ApplicationRecord`](multiple_databases.md#gitlab-schema)
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ Developers have two options for how set up a development environment for the Git
|
|||
|
||||
## Set up with Jira
|
||||
|
||||
### Install the app in Jira
|
||||
|
||||
The following are required to install and test the app:
|
||||
The following are required to install the app:
|
||||
|
||||
- A Jira Cloud instance. Atlassian provides [free instances for development and testing](https://developer.atlassian.com/platform/marketplace/getting-started/#free-developer-instances-to-build-and-test-your-app).
|
||||
- A GitLab instance available over the internet. For the app to work, Jira Cloud should
|
||||
|
|
@ -38,6 +36,13 @@ The following are required to install and test the app:
|
|||
Jira requires all connections to the app host to be over SSL. If you set up
|
||||
your own environment, remember to enable SSL and an appropriate certificate.
|
||||
|
||||
### Setting up GitPod
|
||||
|
||||
If you are using [Gitpod](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/howto/gitpod.md)
|
||||
you must [make port `3000` public](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/howto/gitpod.md#make-the-rails-web-server-publicly-accessible).
|
||||
|
||||
### Install the app in Jira
|
||||
|
||||
To install the app in Jira:
|
||||
|
||||
1. Enable Jira development mode to install apps that are not from the Atlassian
|
||||
|
|
@ -65,17 +70,20 @@ To install the app in Jira:
|
|||
You can also select **Getting Started** to open the configuration page rendered from your GitLab instance.
|
||||
|
||||
_Note that any changes to the app descriptor requires you to uninstall then reinstall the app._
|
||||
1. You can now [set up the OAuth authentication flow](#set-up-the-gitlab-oauth-authentication-flow).
|
||||
1. If the _Installed and ready to go!_ dialog opens asking you to **Get started**, do not get started yet
|
||||
and instead select **Close**.
|
||||
1. You must now [set up the OAuth authentication flow](#set-up-the-gitlab-oauth-authentication-flow).
|
||||
|
||||
### Set up the GitLab OAuth authentication flow
|
||||
|
||||
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117648) in GitLab 16.0. Feature flag `jira_connect_oauth` removed.
|
||||
GitLab for Jira users authenticate with GitLab using GitLab OAuth.
|
||||
|
||||
GitLab for Jira users can authenticate with GitLab using GitLab OAuth.
|
||||
Ensure you have [installed the app in Jira](#install-the-app-in-jira) first before doing these steps,
|
||||
otherwise the app installation in Jira fails.
|
||||
|
||||
The following steps describe setting up an environment to test the GitLab OAuth flow:
|
||||
|
||||
1. Start a Gitpod session.
|
||||
1. Start a [Gitpod session](#setting-up-gitpod).
|
||||
1. On your GitLab instance, go to **Admin > Applications**.
|
||||
1. Create a new application with the following settings:
|
||||
- Name: `GitLab for Jira`
|
||||
|
|
@ -88,12 +96,19 @@ The following steps describe setting up an environment to test the GitLab OAuth
|
|||
1. Expand **GitLab for Jira App**.
|
||||
1. Paste the **Application ID** value into **Jira Connect Application ID**.
|
||||
1. In **Jira Connect Proxy URL**, enter `YOUR_GITPOD_INSTANCE` (for example, `https://xxxx.gitpod.io`).
|
||||
1. Select **Enable public key storage**.
|
||||
1. Enable public key storage: **Leave unchecked**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
### Setting up GitPod
|
||||
### Set up the app in Jira
|
||||
|
||||
If you are using [Gitpod](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/howto/gitpod.md) you must [make port `3000` public](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/howto/gitpod.md#make-the-rails-web-server-publicly-accessible).
|
||||
Ensure you have [set up OAuth first](#set-up-the-gitlab-oauth-authentication-flow) first before doing these steps,
|
||||
otherwise these steps fail.
|
||||
|
||||
1. In Jira, go to **Jira settings > Apps > Manage apps**.
|
||||
1. Scroll to **User-installed apps**, find your GitLab for Jira app and expand it.
|
||||
1. Select **Get started**.
|
||||
|
||||
You should be able to authenticate with your GitLab instance and begin linking groups.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@ group: Source Code
|
|||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
|
||||
---
|
||||
|
||||
# Push Rules development guidelines
|
||||
# Push rules development guidelines
|
||||
|
||||
This document was created to help contributors understand the code design of
|
||||
[Push Rules](../../user/project/repository/push_rules.md). You should read this
|
||||
[push rules](../../user/project/repository/push_rules.md). You should read this
|
||||
document before making changes to the code for this feature.
|
||||
|
||||
This document is intentionally limited to an overview of how the code is
|
||||
designed, as code can change often. To understand how a specific part of the
|
||||
feature works, view the code and the specs. The details here explain how the
|
||||
major components of the Push Rules feature work.
|
||||
major components of the push rules feature work.
|
||||
|
||||
NOTE:
|
||||
This document should be updated when parts of the codebase referenced in this
|
||||
|
|
|
|||
|
|
@ -359,9 +359,36 @@ reproduction.
|
|||
|
||||
#### Hanging specs
|
||||
|
||||
If a spec hangs, it might be caused by a [bug in Rails](https://github.com/rails/rails/issues/45994):
|
||||
If a spec hangs, or times out in CI, it might be caused by a
|
||||
[LoadInterlockAwareMonitor deadlock bug in Rails](https://github.com/rails/rails/issues/45994).
|
||||
|
||||
To diagnose, you can use
|
||||
[sigdump](https://github.com/fluent/sigdump/blob/master/README.md#usage)
|
||||
to print the Ruby thread dump :
|
||||
|
||||
1. Run the hanging spec locally.
|
||||
1. Trigger the Ruby thread dump by running this command:
|
||||
|
||||
```shell
|
||||
kill -CONT <pid>
|
||||
```
|
||||
|
||||
1. The thread dump will be saved to the `/tmp/sigdump-<pid>.log` file.
|
||||
|
||||
If you see lines with `load_interlock_aware_monitor.rb`, this is likely related:
|
||||
|
||||
```shell
|
||||
/builds/gitlab-org/gitlab/vendor/ruby/3.2.0/gems/activesupport-7.0.8.4/lib/active_support/concurrency/load_interlock_aware_monitor.rb:17:in `mon_enter'
|
||||
/builds/gitlab-org/gitlab/vendor/ruby/3.2.0/gems/activesupport-7.0.8.4/lib/active_support/concurrency/load_interlock_aware_monitor.rb:22:in `block in synchronize'
|
||||
/builds/gitlab-org/gitlab/vendor/ruby/3.2.0/gems/activesupport-7.0.8.4/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt'
|
||||
/builds/gitlab-org/gitlab/vendor/ruby/3.2.0/gems/activesupport-7.0.8.4/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize'
|
||||
```
|
||||
|
||||
See examples where we worked around by creating the factories before making
|
||||
requests:
|
||||
|
||||
- <https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81112>
|
||||
- <https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158890>
|
||||
- <https://gitlab.com/gitlab-org/gitlab/-/issues/337039>
|
||||
|
||||
### Suggestions
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ encouraged for communications through system hooks.
|
|||
## Push rules
|
||||
|
||||
1. On the left sidebar, at the bottom, select **Admin area**.
|
||||
1. Select **Push Rules**.
|
||||
1. Select **Push rules**.
|
||||
|
||||
Ensure that the following items are selected:
|
||||
|
||||
|
|
|
|||
|
|
@ -490,9 +490,9 @@ To support the following package managers, the GitLab analyzers proceed in two s
|
|||
</tr>
|
||||
<tr>
|
||||
<td>maven</td>
|
||||
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/v5.2.14/build/gemnasium-maven/debian/config/.tool-versions#L3">3.8.8</a></td>
|
||||
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/v5.3.1/build/gemnasium-maven/debian/config/.tool-versions#L3">3.9.8</a></td>
|
||||
<td>
|
||||
<a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/v5.2.14/spec/gemnasium-maven_image_spec.rb#L92-94">3.8.8</a><sup><b><a href="#exported-dependency-information-notes-1">1</a></b></sup>
|
||||
<a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/v5.3.1/spec/gemnasium-maven_image_spec.rb#L92-94">3.9.8</a><sup><b><a href="#exported-dependency-information-notes-1">1</a></b></sup>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ In GitLab 15.4 and later, to configure push rules for a group:
|
|||
1. On the left sidebar, select **Settings > Repository**.
|
||||
1. Expand the **Pre-defined push rules** section.
|
||||
1. Select the settings you want.
|
||||
1. Select **Save Push Rules**.
|
||||
1. Select **Save push rules**.
|
||||
|
||||
In GitLab 15.3 and earlier, to configure push rules for a group:
|
||||
|
||||
1. On the left sidebar, select **Push rules**.
|
||||
1. Select the settings you want.
|
||||
1. Select **Save Push Rules**.
|
||||
1. Select **Save push rules**.
|
||||
|
||||
The group's new subgroups have push rules set for them based on either:
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ To set up a protected branch flow:
|
|||
You can specify the format and security measures such as requiring SSH key signing for changes
|
||||
coming into your code base with push rules:
|
||||
|
||||
- [Push Rules](../repository/push_rules.md)
|
||||
- [Push rules](../repository/push_rules.md)
|
||||
|
||||
1. To ensure that the code is reviewed and checked by the right people in your team, use:
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ Prerequisites:
|
|||
To create global push rules:
|
||||
|
||||
1. On the left sidebar, at the bottom, select **Admin area**.
|
||||
1. Select **Push Rules**.
|
||||
1. Select **Push rules**.
|
||||
1. Expand **Push rules**.
|
||||
1. Set the rule you want.
|
||||
1. Select **Save push rules**.
|
||||
|
|
@ -319,7 +319,7 @@ read [issue #19185](https://gitlab.com/gitlab-org/gitlab/-/issues/19185).
|
|||
|
||||
To update the push rules to be the same for all projects,
|
||||
you need to use [the rails console](../../../administration/operations/rails_console.md#starting-a-rails-console-session),
|
||||
or write a script to update each project using the [Push Rules API endpoint](../../../api/projects.md#push-rules).
|
||||
or write a script to update each project using the [push rules API endpoint](../../../api/projects.md#push-rules).
|
||||
|
||||
For example, to enable **Check whether the commit author is a GitLab user** and **Do not allow users to remove Git tags with `git push`** checkboxes,
|
||||
and create a filter for allowing commits from a specific email domain only through rails console:
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ module Gitlab
|
|||
unfinished_count = Gitlab::Database::BackgroundMigration::BatchedMigration.unfinished.count
|
||||
if unfinished_count > 0
|
||||
raise MigrateError,
|
||||
"Found #{unfinished_count} unfinished Background Migration(s). Please wait until they are finished."
|
||||
"Found #{unfinished_count} unfinished background migration(s). Please wait until they are finished."
|
||||
end
|
||||
|
||||
true
|
||||
|
|
|
|||
|
|
@ -4732,7 +4732,7 @@ msgstr ""
|
|||
msgid "Admin|Overview"
|
||||
msgstr ""
|
||||
|
||||
msgid "Admin|Push Rules"
|
||||
msgid "Admin|Push rules"
|
||||
msgstr ""
|
||||
|
||||
msgid "Admin|Quarterly reconciliation will occur on %{qrtlyDate}"
|
||||
|
|
@ -6934,6 +6934,9 @@ msgstr ""
|
|||
msgid "Archiving the project makes it entirely read-only. It is hidden from the dashboard and doesn't display in searches. %{strong_start}The repository cannot be committed to, and no issues, comments, or other entities can be created.%{strong_end} %{link_start}Learn more.%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you ABSOLUTELY SURE you wish to delete this group?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you absolutely sure?"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17462,9 +17465,6 @@ msgstr ""
|
|||
msgid "Delete group"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete group immediately"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete group immediately?"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17489,9 +17489,6 @@ msgstr ""
|
|||
msgid "Delete project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete project immediately"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete release"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17536,6 +17533,9 @@ msgstr ""
|
|||
msgid "Delete this epic and release all child items?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete this project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete user list"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17596,6 +17596,9 @@ msgstr ""
|
|||
msgid "Deleted commits:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deleted group can not be restored!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deleted projects cannot be restored!"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17617,6 +17620,9 @@ msgstr ""
|
|||
msgid "Deleting the project will delete its repository and all related resources, including issues and merge requests."
|
||||
msgstr ""
|
||||
|
||||
msgid "Deleting this group also deletes all child projects, including archived projects, and their resources."
|
||||
msgstr ""
|
||||
|
||||
msgid "Deletion pending. This project will be deleted on %{date}. Repository and other project resources are read-only."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -19497,6 +19503,15 @@ msgstr ""
|
|||
msgid "DuoChat|What is a fork?"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoCodeReview|Hey :wave: I'm starting to review your merge request and I will let you know when I'm finished."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoCodeReview|I finished my review and found nothing to comment on. Nice work! :tada:"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoCodeReview|I have encountered some issues while I was reviewing. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoProDiscover|Ship software faster and more securely with AI integrated into your entire DevSecOps lifecycle."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -38313,6 +38328,9 @@ msgstr ""
|
|||
msgid "Permalink"
|
||||
msgstr ""
|
||||
|
||||
msgid "Permanently delete group"
|
||||
msgstr ""
|
||||
|
||||
msgid "Permissions"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -42632,10 +42650,10 @@ msgstr ""
|
|||
msgid "Promotions|Not now, thanks!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Push Rules"
|
||||
msgid "Promotions|Push rules"
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Push Rules are defined per project so you can have different rules applied to different projects depends on your needs."
|
||||
msgid "Promotions|Push rules are defined per project so you can have different rules applied to different projects depends on your needs."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Repository Mirroring"
|
||||
|
|
@ -43248,12 +43266,6 @@ msgstr ""
|
|||
msgid "Push Rule updated successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Push Rules"
|
||||
msgstr ""
|
||||
|
||||
msgid "Push Rules updated successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Push an existing Git repository"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -43278,6 +43290,12 @@ msgstr ""
|
|||
msgid "Push project from command line"
|
||||
msgstr ""
|
||||
|
||||
msgid "Push rules"
|
||||
msgstr ""
|
||||
|
||||
msgid "Push rules updated successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Push the source branch up to GitLab."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -54064,16 +54082,16 @@ msgstr ""
|
|||
msgid "This action cannot be undone, and will permanently delete the %{key} SSH key. All commits signed using this SSH key will be marked as unverified."
|
||||
msgstr ""
|
||||
|
||||
msgid "This action will permanently delete this group, including its subgroups and projects."
|
||||
msgid "This action deletes %{codeOpen}%{project_path_with_namespace}%{codeClose} and everything this project contains. %{strongOpen}There is no going back.%{strongClose}"
|
||||
msgstr ""
|
||||
|
||||
msgid "This action will permanently delete this project, including all its resources."
|
||||
msgid "This action deletes %{codeOpen}%{project_path_with_namespace}%{codeClose} on %{date} and everything this project contains."
|
||||
msgstr ""
|
||||
|
||||
msgid "This action will place this group, including its subgroups and projects, in a pending deletion state for %{deletion_delayed_period} days, and delete it permanently on %{date}."
|
||||
msgid "This action deletes %{codeOpen}%{project_path_with_namespace}%{codeClose} on %{date} and everything this project contains. %{strongOpen}There is no going back.%{strongClose}"
|
||||
msgstr ""
|
||||
|
||||
msgid "This action will place this project, including all its resources, in a pending deletion state for %{deletion_adjourned_period} days, and delete it permanently on %{strongOpen}%{date}%{strongClose}."
|
||||
msgid "This action will %{strongOpen}permanently remove%{strongClose} %{codeOpen}%{group}%{codeClose} %{strongOpen}immediately%{strongClose}."
|
||||
msgstr ""
|
||||
|
||||
msgid "This also resolves all related threads"
|
||||
|
|
@ -54268,6 +54286,9 @@ msgstr ""
|
|||
msgid "This group"
|
||||
msgstr ""
|
||||
|
||||
msgid "This group and its subgroups and projects will be placed in a 'pending deletion' state for %{deletion_delayed_period} days, then permanently deleted on %{date}. The group can be fully restored before that date."
|
||||
msgstr ""
|
||||
|
||||
msgid "This group can be restored until %{date}. %{linkStart}Learn more%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -54298,9 +54319,6 @@ msgstr ""
|
|||
msgid "This group is not permitted to create compliance violations"
|
||||
msgstr ""
|
||||
|
||||
msgid "This group is scheduled for deletion on %{date}. This action will permanently delete this group, including its subgroups and projects, %{strong_open}immediately%{strong_close}. This action cannot be undone."
|
||||
msgstr ""
|
||||
|
||||
msgid "This group is scheduled to be deleted on %{date}. You are about to delete this group, including its subgroups and projects, immediately. This action cannot be undone."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -54612,9 +54630,6 @@ msgstr ""
|
|||
msgid "This project is public. Non-members can guess the Service Desk email address, because it contains the group and project name. %{linkStart}How do I create a custom email address?%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "This project is scheduled for deletion on %{strongOpen}%{date}%{strongClose}. This action will permanently delete this project, including all its resources, %{strongOpen}immediately%{strongClose}. This action cannot be undone."
|
||||
msgstr ""
|
||||
|
||||
msgid "This project manages its dependencies using %{strong_start}%{manager_name}%{strong_end}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -60154,6 +60169,9 @@ msgstr ""
|
|||
msgid "Work in progress limit: %{wipLimit}"
|
||||
msgstr ""
|
||||
|
||||
msgid "WorkItems|An error occurred while fetching children"
|
||||
msgstr ""
|
||||
|
||||
msgid "WorkItem|%{count} more assignees"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ module QA
|
|||
resource.add_name_uuid = false
|
||||
resource.name = name
|
||||
resource.path_with_namespace = "#{user.username}/#{name}"
|
||||
resource.api_client = @api_client
|
||||
resource.api_client = api_client
|
||||
end
|
||||
end
|
||||
|
||||
attribute :upstream do
|
||||
Repository::ProjectPush.fabricate!.project
|
||||
Resource::Project.fabricate_via_api! do |resource|
|
||||
resource.initialize_with_readme = true
|
||||
end
|
||||
end
|
||||
|
||||
attribute :user do
|
||||
|
|
@ -41,8 +43,6 @@ module QA
|
|||
# Sign out as admin and sign is as the fork user
|
||||
Flow::Login.sign_in(as: user)
|
||||
|
||||
@api_client = Runtime::API::Client.new(:gitlab, is_new_session: false, user: user)
|
||||
|
||||
upstream.visit!
|
||||
|
||||
Page::Project::Show.perform(&:fork_project)
|
||||
|
|
@ -61,8 +61,6 @@ module QA
|
|||
def fabricate_via_api!
|
||||
populate(:upstream, :user)
|
||||
|
||||
@api_client = Runtime::API::Client.new(:gitlab, is_new_session: false, user: user)
|
||||
|
||||
Runtime::Logger.debug("Forking project #{upstream.name} to namespace #{user.username}...")
|
||||
super
|
||||
wait_until_forked
|
||||
|
|
@ -76,6 +74,15 @@ module QA
|
|||
user.remove_via_api! unless Specs::Helpers::ContextSelector.dot_com?
|
||||
end
|
||||
|
||||
# Public api client method
|
||||
# By default resources have api_client private. Fork requires operating with 2 users, so it needs to be public
|
||||
# to correctly fabricate mr from fork
|
||||
#
|
||||
# @return [Runtime::API::Client]
|
||||
def api_client
|
||||
@api_client ||= Runtime::API::Client.new(:gitlab, is_new_session: false, user: user)
|
||||
end
|
||||
|
||||
def api_get_path
|
||||
"/projects/#{CGI.escape(path_with_namespace)}"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,23 +3,26 @@
|
|||
module QA
|
||||
module Resource
|
||||
class MergeRequestFromFork < MergeRequest
|
||||
attr_accessor :fork_branch
|
||||
|
||||
attribute :fork do
|
||||
Fork.fabricate_via_browser_ui!
|
||||
Fork.fabricate_via_api!
|
||||
end
|
||||
|
||||
attribute :push do
|
||||
Repository::ProjectPush.fabricate! do |resource|
|
||||
resource.project = fork.project
|
||||
resource.branch_name = fork_branch
|
||||
resource.file_name = "file2-#{SecureRandom.hex(8)}.txt"
|
||||
resource.user = fork.user
|
||||
attribute :project do
|
||||
fork.project
|
||||
end
|
||||
|
||||
attribute :source do
|
||||
Repository::Commit.fabricate_via_api! do |resource|
|
||||
resource.project = project
|
||||
resource.api_client = api_client
|
||||
resource.commit_message = 'This is a test commit'
|
||||
resource.add_files([{ file_path: "file-#{SecureRandom.hex(8)}.txt", content: 'MR init' }])
|
||||
resource.branch = project.default_branch
|
||||
end
|
||||
end
|
||||
|
||||
def fabricate!
|
||||
populate(:push)
|
||||
populate(:source)
|
||||
|
||||
fork.project.visit!
|
||||
|
||||
|
|
@ -37,8 +40,42 @@ module QA
|
|||
visit(mr_url)
|
||||
end
|
||||
|
||||
def api_post_body
|
||||
super.merge({
|
||||
target_project_id: upstream.id,
|
||||
source_branch: project.default_branch,
|
||||
target_branch: upstream.default_branch
|
||||
})
|
||||
end
|
||||
|
||||
def fabricate_via_api!
|
||||
raise NotImplementedError
|
||||
populate(:source)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# Fabricated mr needs to be fetched from upstream project rather than source project
|
||||
#
|
||||
# @return [String]
|
||||
def api_get_path
|
||||
"/projects/#{upstream.id}/merge_requests/#{iid}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def api_client
|
||||
fork.api_client
|
||||
end
|
||||
|
||||
# Target is upstream, in fork workflow it must not be populated
|
||||
#
|
||||
# @return [Boolean]
|
||||
def create_target?
|
||||
false
|
||||
end
|
||||
|
||||
def upstream
|
||||
fork.upstream
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,21 +3,14 @@
|
|||
module QA
|
||||
RSpec.describe 'Create' do
|
||||
describe 'Merge request creation from fork', product_group: :code_review do
|
||||
let(:merge_request) do
|
||||
Resource::MergeRequestFromFork.fabricate_via_browser_ui! do |merge_request|
|
||||
merge_request.fork_branch = 'feature-branch'
|
||||
end
|
||||
end
|
||||
let(:merge_request) { Resource::MergeRequestFromFork.fabricate_via_api! }
|
||||
|
||||
before do
|
||||
Flow::Login.sign_in
|
||||
end
|
||||
|
||||
after do
|
||||
merge_request.fork.remove_via_api!
|
||||
end
|
||||
|
||||
it 'can merge source branch from fork into upstream repository', :blocking, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347818' do
|
||||
it 'can merge source branch from fork into upstream repository', :blocking,
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347818' do
|
||||
merge_request.visit!
|
||||
|
||||
Page::MergeRequest::Show.perform do |merge_request|
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
module RuboCop
|
||||
module Cop
|
||||
module BackgroundMigration
|
||||
# Checks for rescuing errors inside Batched Background Migration Job Classes.
|
||||
# Checks for rescuing errors inside batched background Migration Job Classes.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module RuboCop
|
|||
class BackgroundMigrations < RuboCop::Cop::Base
|
||||
include MigrationHelpers
|
||||
|
||||
MSG = 'Background migrations are deprecated. Please use a Batched Background Migration instead. '\
|
||||
MSG = 'Background migrations are deprecated. Please use a batched background migration instead. '\
|
||||
'More info: https://docs.gitlab.com/ee/development/database/batched_background_migrations.html'
|
||||
|
||||
def on_send(node)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ class SemgrepResultProcessor
|
|||
ALLOWED_API_URLS = %w[https://gitlab.com/api/v4].freeze
|
||||
|
||||
# Remove this when the feature is fully working
|
||||
MESSAGE_FOOTER = <<-FOOTER
|
||||
MESSAGE_FOOTER = <<~FOOTER
|
||||
|
||||
|
||||
<small>
|
||||
This AppSec automation is currently under testing.
|
||||
|
|
@ -19,6 +20,7 @@ class SemgrepResultProcessor
|
|||
For any detailed feedback, [add a comment here](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/issues/38).
|
||||
</small>
|
||||
|
||||
|
||||
/label ~"appsec-sast::commented"
|
||||
FOOTER
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ describe('Project remove modal', () => {
|
|||
|
||||
const defaultProps = {
|
||||
confirmPhrase: 'foo',
|
||||
buttonText: 'Delete project',
|
||||
formPath: 'some/path',
|
||||
isFork: false,
|
||||
issuesCount: 1,
|
||||
|
|
@ -46,7 +45,6 @@ describe('Project remove modal', () => {
|
|||
it('passes confirmPhrase and formPath props to the shared delete button', () => {
|
||||
expect(findSharedDeleteButton().props()).toEqual({
|
||||
confirmPhrase: defaultProps.confirmPhrase,
|
||||
buttonText: defaultProps.buttonText,
|
||||
forksCount: defaultProps.forksCount,
|
||||
formPath: defaultProps.formPath,
|
||||
isFork: defaultProps.isFork,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ describe('DeleteButton', () => {
|
|||
|
||||
const findForm = () => wrapper.findComponent(GlForm);
|
||||
const findModal = () => wrapper.findComponent(DeleteModal);
|
||||
const findDeleteButton = () => wrapper.findComponent(GlButton);
|
||||
|
||||
const defaultPropsData = {
|
||||
confirmPhrase: 'foo',
|
||||
|
|
@ -34,18 +33,6 @@ describe('DeleteButton', () => {
|
|||
});
|
||||
};
|
||||
|
||||
it('renders the correct default title', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findDeleteButton().text()).toBe('Delete project');
|
||||
});
|
||||
|
||||
it('renders a title passed via `buttonText` prop', () => {
|
||||
createComponent({ buttonText: 'Delete project immediately' });
|
||||
|
||||
expect(findDeleteButton().text()).toBe('Delete project immediately');
|
||||
});
|
||||
|
||||
it('renders modal and passes correct props', () => {
|
||||
createComponent();
|
||||
|
||||
|
|
|
|||
|
|
@ -545,6 +545,18 @@ describe('WorkItemDetail component', () => {
|
|||
expect(findHierarchyTree().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it.each([true, false])(
|
||||
'passes hasChildren %s to WorkItemActions when `WorkItemTree` emits `childrenLoaded` %s',
|
||||
async (hasChildren) => {
|
||||
createComponent({ handler: objectiveHandler });
|
||||
await waitForPromises();
|
||||
|
||||
await findHierarchyTree().vm.$emit('childrenLoaded', hasChildren);
|
||||
|
||||
expect(findWorkItemActions().props('hasChildren')).toBe(hasChildren);
|
||||
},
|
||||
);
|
||||
|
||||
it('renders a modal', async () => {
|
||||
createComponent({ handler: objectiveHandler });
|
||||
await waitForPromises();
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import projectWorkItemsQuery from '~/work_items/graphql/project_work_items.query
|
|||
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 createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_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 {
|
||||
|
|
@ -88,7 +88,7 @@ describe('WorkItemLinksForm', () => {
|
|||
[groupWorkItemTypesQuery, groupWorkItemTypesResolver],
|
||||
[groupProjectsForLinksWidgetQuery, groupProjectsFormLinksWidgetResolver],
|
||||
[relatedProjectsForLinksWidgetQuery, relatedProjectsForLinksWidgetResolver],
|
||||
[updateWorkItemMutation, updateMutation],
|
||||
[updateWorkItemHierarchyMutation, updateMutation],
|
||||
[createWorkItemMutation, createMutationResolver],
|
||||
]),
|
||||
propsData: {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlToggle } 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';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import { RENDER_ALL_SLOTS_TEMPLATE, stubComponent } from 'helpers/stub_component';
|
||||
import issueDetailsQuery from 'ee_else_ce/work_items/graphql/get_issue_details.query.graphql';
|
||||
|
||||
import { resolvers } from '~/graphql_shared/issuable_client';
|
||||
import WidgetWrapper from '~/work_items/components/widget_wrapper.vue';
|
||||
import WorkItemLinks from '~/work_items/components/work_item_links/work_item_links.vue';
|
||||
|
|
@ -14,13 +16,13 @@ import WorkItemChildrenWrapper from '~/work_items/components/work_item_links/wor
|
|||
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
|
||||
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
|
||||
import { FORM_TYPES } from '~/work_items/constants';
|
||||
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 getWorkItemTreeQuery from '~/work_items/graphql/work_item_tree.query.graphql';
|
||||
|
||||
import {
|
||||
getIssueDetailsResponse,
|
||||
groupWorkItemByIidResponseFactory,
|
||||
workItemHierarchyResponse,
|
||||
workItemHierarchyEmptyResponse,
|
||||
workItemHierarchyTreeResponse,
|
||||
workItemHierarchyTreeEmptyResponse,
|
||||
workItemHierarchyNoUpdatePermissionResponse,
|
||||
workItemByIidResponseFactory,
|
||||
mockWorkItemCommentNote,
|
||||
|
|
@ -34,7 +36,7 @@ describe('WorkItemLinks', () => {
|
|||
let wrapper;
|
||||
let mockApollo;
|
||||
|
||||
const responseWithAddChildPermission = jest.fn().mockResolvedValue(workItemHierarchyResponse);
|
||||
const responseWithAddChildPermission = jest.fn().mockResolvedValue(workItemHierarchyTreeResponse);
|
||||
const groupResponseWithAddChildPermission = jest
|
||||
.fn()
|
||||
.mockResolvedValue(groupWorkItemByIidResponseFactory());
|
||||
|
|
@ -50,8 +52,7 @@ describe('WorkItemLinks', () => {
|
|||
} = {}) => {
|
||||
mockApollo = createMockApollo(
|
||||
[
|
||||
[workItemByIidQuery, fetchHandler],
|
||||
[groupWorkItemByIidQuery, groupResponseWithAddChildPermission],
|
||||
[getWorkItemTreeQuery, fetchHandler],
|
||||
[issueDetailsQuery, issueDetailsQueryHandler],
|
||||
],
|
||||
resolvers,
|
||||
|
|
@ -149,7 +150,7 @@ describe('WorkItemLinks', () => {
|
|||
describe('when no child links', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
fetchHandler: jest.fn().mockResolvedValue(workItemHierarchyEmptyResponse),
|
||||
fetchHandler: jest.fn().mockResolvedValue(workItemHierarchyTreeEmptyResponse),
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -162,7 +163,7 @@ describe('WorkItemLinks', () => {
|
|||
await createComponent();
|
||||
|
||||
expect(findWorkItemLinkChildrenWrapper().exists()).toBe(true);
|
||||
expect(findWorkItemLinkChildrenWrapper().props().children).toHaveLength(4);
|
||||
expect(findWorkItemLinkChildrenWrapper().props().children).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('shows an alert when list loading fails', async () => {
|
||||
|
|
@ -178,7 +179,7 @@ describe('WorkItemLinks', () => {
|
|||
await createComponent();
|
||||
|
||||
expect(findChildrenCount().exists()).toBe(true);
|
||||
expect(findChildrenCount().text()).toContain('4');
|
||||
expect(findChildrenCount().text()).toContain('1');
|
||||
});
|
||||
|
||||
describe('when no permission to update', () => {
|
||||
|
|
@ -267,20 +268,6 @@ describe('WorkItemLinks', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when group context', () => {
|
||||
it('skips calling the project work item query', () => {
|
||||
createComponent({ isGroup: true });
|
||||
|
||||
expect(responseWithAddChildPermission).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the group work item query', () => {
|
||||
createComponent({ isGroup: true });
|
||||
|
||||
expect(groupResponseWithAddChildPermission).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it.each`
|
||||
toggleValue
|
||||
${true}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlToggle } from '@gitlab/ui';
|
||||
import { GlLoadingIcon, GlToggle } 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';
|
||||
import WidgetWrapper from '~/work_items/components/widget_wrapper.vue';
|
||||
import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue';
|
||||
import WorkItemChildrenWrapper from '~/work_items/components/work_item_links/work_item_children_wrapper.vue';
|
||||
import WorkItemLinksForm from '~/work_items/components/work_item_links/work_item_links_form.vue';
|
||||
import WorkItemActionsSplitButton from '~/work_items/components/work_item_links/work_item_actions_split_button.vue';
|
||||
import WorkItemTreeActions from '~/work_items/components/work_item_links/work_item_tree_actions.vue';
|
||||
import getWorkItemTreeQuery from '~/work_items/graphql/work_item_tree.query.graphql';
|
||||
import {
|
||||
FORM_TYPES,
|
||||
WORK_ITEM_TYPE_ENUM_OBJECTIVE,
|
||||
|
|
@ -17,14 +20,23 @@ import {
|
|||
WORK_ITEM_TYPE_VALUE_EPIC,
|
||||
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
|
||||
} from '~/work_items/constants';
|
||||
import { childrenWorkItems } from '../../mock_data';
|
||||
import {
|
||||
workItemHierarchyTreeResponse,
|
||||
workItemHierarchyTreeEmptyResponse,
|
||||
workItemHierarchyNoUpdatePermissionResponse,
|
||||
} from '../../mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('WorkItemTree', () => {
|
||||
let wrapper;
|
||||
|
||||
const workItemHierarchyTreeResponseHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(workItemHierarchyTreeResponse);
|
||||
|
||||
const findEmptyState = () => wrapper.findByTestId('tree-empty');
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findToggleFormSplitButton = () => wrapper.findComponent(WorkItemActionsSplitButton);
|
||||
const findForm = () => wrapper.findComponent(WorkItemLinksForm);
|
||||
const findWidgetWrapper = () => wrapper.findComponent(WidgetWrapper);
|
||||
|
|
@ -32,15 +44,15 @@ describe('WorkItemTree', () => {
|
|||
const findShowLabelsToggle = () => wrapper.findComponent(GlToggle);
|
||||
const findTreeActions = () => wrapper.findComponent(WorkItemTreeActions);
|
||||
|
||||
const createComponent = ({
|
||||
const createComponent = async ({
|
||||
workItemType = 'Objective',
|
||||
workItemIid = '2',
|
||||
parentWorkItemType = 'Objective',
|
||||
confidential = false,
|
||||
children = childrenWorkItems,
|
||||
canUpdate = true,
|
||||
canUpdateChildren = true,
|
||||
hasSubepicsFeature = true,
|
||||
workItemHierarchyTreeHandler = workItemHierarchyTreeResponseHandler,
|
||||
} = {}) => {
|
||||
wrapper = shallowMountExtended(WorkItemTree, {
|
||||
propsData: {
|
||||
|
|
@ -48,17 +60,18 @@ describe('WorkItemTree', () => {
|
|||
workItemType,
|
||||
workItemIid,
|
||||
parentWorkItemType,
|
||||
workItemId: 'gid://gitlab/WorkItem/515',
|
||||
workItemId: 'gid://gitlab/WorkItem/2',
|
||||
confidential,
|
||||
children,
|
||||
canUpdate,
|
||||
canUpdateChildren,
|
||||
},
|
||||
apolloProvider: createMockApollo([[getWorkItemTreeQuery, workItemHierarchyTreeHandler]]),
|
||||
provide: {
|
||||
hasSubepicsFeature,
|
||||
},
|
||||
stubs: { WidgetWrapper },
|
||||
});
|
||||
await waitForPromises();
|
||||
};
|
||||
|
||||
it('displays Add button', () => {
|
||||
|
|
@ -67,17 +80,25 @@ describe('WorkItemTree', () => {
|
|||
expect(findToggleFormSplitButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('displays empty state if there are no children', () => {
|
||||
createComponent({ children: [] });
|
||||
it('displays empty state if there are no children', async () => {
|
||||
await createComponent({
|
||||
workItemHierarchyTreeHandler: jest.fn().mockResolvedValue(workItemHierarchyTreeEmptyResponse),
|
||||
});
|
||||
|
||||
expect(findEmptyState().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders hierarchy widget children container', () => {
|
||||
it('displays loading-icon while children are being loaded', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders hierarchy widget children container', async () => {
|
||||
await createComponent();
|
||||
|
||||
expect(findWorkItemLinkChildrenWrapper().exists()).toBe(true);
|
||||
expect(findWorkItemLinkChildrenWrapper().props().children).toHaveLength(4);
|
||||
expect(findWorkItemLinkChildrenWrapper().props().children).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does not display form by default', () => {
|
||||
|
|
@ -88,7 +109,7 @@ describe('WorkItemTree', () => {
|
|||
|
||||
it('shows an error message on error', async () => {
|
||||
const errorMessage = 'Some error';
|
||||
createComponent();
|
||||
await createComponent();
|
||||
|
||||
findWorkItemLinkChildrenWrapper().vm.$emit('error', errorMessage);
|
||||
await nextTick();
|
||||
|
|
@ -167,10 +188,13 @@ describe('WorkItemTree', () => {
|
|||
});
|
||||
|
||||
describe('when no permission to update', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
canUpdate: false,
|
||||
canUpdateChildren: false,
|
||||
workItemHierarchyTreeHandler: jest
|
||||
.fn()
|
||||
.mockResolvedValue(workItemHierarchyNoUpdatePermissionResponse),
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -200,7 +224,7 @@ describe('WorkItemTree', () => {
|
|||
`(
|
||||
'passes showLabels as $toggleValue to child items when toggle is $toggleValue',
|
||||
async ({ toggleValue }) => {
|
||||
createComponent();
|
||||
await createComponent();
|
||||
|
||||
findShowLabelsToggle().vm.$emit('change', toggleValue);
|
||||
|
||||
|
|
@ -217,8 +241,8 @@ describe('WorkItemTree', () => {
|
|||
${false} | ${WORK_ITEM_TYPE_VALUE_OBJECTIVE}
|
||||
`(
|
||||
'When displaying a $workItemType, it is $visible that the action menu is rendered',
|
||||
({ workItemType, visible }) => {
|
||||
createComponent({ workItemType });
|
||||
async ({ workItemType, visible }) => {
|
||||
await createComponent({ workItemType });
|
||||
|
||||
expect(findTreeActions().exists()).toBe(visible);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { mountExtended } from 'helpers/vue_test_utils_helper';
|
|||
import { __ } from '~/locale';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
import WorkItemParent from '~/work_items/components/work_item_parent.vue';
|
||||
import { removeHierarchyChild } from '~/work_items/graphql/cache_utils';
|
||||
import { updateParent } from '~/work_items/graphql/cache_utils';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
|
||||
import groupWorkItemsQuery from '~/work_items/graphql/group_work_items.query.graphql';
|
||||
import projectWorkItemsQuery from '~/work_items/graphql/project_work_items.query.graphql';
|
||||
|
|
@ -23,7 +23,7 @@ import {
|
|||
|
||||
jest.mock('~/sentry/sentry_browser_wrapper');
|
||||
jest.mock('~/work_items/graphql/cache_utils', () => ({
|
||||
removeHierarchyChild: jest.fn(),
|
||||
updateParent: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('WorkItemParent component', () => {
|
||||
|
|
@ -337,7 +337,7 @@ describe('WorkItemParent component', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(removeHierarchyChild).toHaveBeenCalledWith({
|
||||
expect(updateParent).toHaveBeenCalledWith({
|
||||
cache: expect.anything(Object),
|
||||
fullPath: mockFullPath,
|
||||
iid: undefined,
|
||||
|
|
@ -373,7 +373,7 @@ describe('WorkItemParent component', () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
expect(removeHierarchyChild).toHaveBeenCalledWith({
|
||||
expect(updateParent).toHaveBeenCalledWith({
|
||||
cache: expect.anything(Object),
|
||||
fullPath: mockFullPath,
|
||||
iid: '1',
|
||||
|
|
|
|||
|
|
@ -1,29 +1,26 @@
|
|||
import { WIDGET_TYPE_HIERARCHY } from '~/work_items/constants';
|
||||
import { addHierarchyChild, removeHierarchyChild } from '~/work_items/graphql/cache_utils';
|
||||
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
|
||||
import getWorkItemTreeQuery from '~/work_items/graphql/work_item_tree.query.graphql';
|
||||
|
||||
describe('work items graphql cache utils', () => {
|
||||
const fullPath = 'full/path';
|
||||
const iid = '10';
|
||||
const id = 'gid://gitlab/WorkItem/10';
|
||||
const mockCacheData = {
|
||||
workspace: {
|
||||
workItem: {
|
||||
id: 'gid://gitlab/WorkItem/10',
|
||||
title: 'Work item',
|
||||
widgets: [
|
||||
{
|
||||
type: WIDGET_TYPE_HIERARCHY,
|
||||
children: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/WorkItem/20',
|
||||
title: 'Child',
|
||||
},
|
||||
],
|
||||
},
|
||||
workItem: {
|
||||
id: 'gid://gitlab/WorkItem/10',
|
||||
title: 'Work item',
|
||||
widgets: [
|
||||
{
|
||||
type: WIDGET_TYPE_HIERARCHY,
|
||||
children: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/WorkItem/20',
|
||||
title: 'Child',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -39,31 +36,29 @@ describe('work items graphql cache utils', () => {
|
|||
title: 'New child',
|
||||
};
|
||||
|
||||
addHierarchyChild({ cache: mockCache, fullPath, iid, workItem: child });
|
||||
addHierarchyChild({ cache: mockCache, id, workItem: child });
|
||||
|
||||
expect(mockCache.writeQuery).toHaveBeenCalledWith({
|
||||
query: workItemByIidQuery,
|
||||
variables: { fullPath, iid },
|
||||
query: getWorkItemTreeQuery,
|
||||
variables: { id },
|
||||
data: {
|
||||
workspace: {
|
||||
workItem: {
|
||||
id: 'gid://gitlab/WorkItem/10',
|
||||
title: 'Work item',
|
||||
widgets: [
|
||||
{
|
||||
type: WIDGET_TYPE_HIERARCHY,
|
||||
children: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/WorkItem/20',
|
||||
title: 'Child',
|
||||
},
|
||||
child,
|
||||
],
|
||||
},
|
||||
workItem: {
|
||||
id: 'gid://gitlab/WorkItem/10',
|
||||
title: 'Work item',
|
||||
widgets: [
|
||||
{
|
||||
type: WIDGET_TYPE_HIERARCHY,
|
||||
children: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/WorkItem/20',
|
||||
title: 'Child',
|
||||
},
|
||||
child,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -80,7 +75,7 @@ describe('work items graphql cache utils', () => {
|
|||
title: 'New child',
|
||||
};
|
||||
|
||||
addHierarchyChild({ cache: mockCache, fullPath, iid, workItem: child });
|
||||
addHierarchyChild({ cache: mockCache, id, workItem: child });
|
||||
|
||||
expect(mockCache.writeQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
@ -98,25 +93,23 @@ describe('work items graphql cache utils', () => {
|
|||
title: 'Child',
|
||||
};
|
||||
|
||||
removeHierarchyChild({ cache: mockCache, fullPath, iid, workItem: childToRemove });
|
||||
removeHierarchyChild({ cache: mockCache, id, workItem: childToRemove });
|
||||
|
||||
expect(mockCache.writeQuery).toHaveBeenCalledWith({
|
||||
query: workItemByIidQuery,
|
||||
variables: { fullPath, iid },
|
||||
query: getWorkItemTreeQuery,
|
||||
variables: { id },
|
||||
data: {
|
||||
workspace: {
|
||||
workItem: {
|
||||
id: 'gid://gitlab/WorkItem/10',
|
||||
title: 'Work item',
|
||||
widgets: [
|
||||
{
|
||||
type: WIDGET_TYPE_HIERARCHY,
|
||||
children: {
|
||||
nodes: [],
|
||||
},
|
||||
workItem: {
|
||||
id: 'gid://gitlab/WorkItem/10',
|
||||
title: 'Work item',
|
||||
widgets: [
|
||||
{
|
||||
type: WIDGET_TYPE_HIERARCHY,
|
||||
children: {
|
||||
nodes: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -133,7 +126,7 @@ describe('work items graphql cache utils', () => {
|
|||
title: 'Child',
|
||||
};
|
||||
|
||||
removeHierarchyChild({ cache: mockCache, fullPath, iid, workItem: childToRemove });
|
||||
removeHierarchyChild({ cache: mockCache, id, workItem: childToRemove });
|
||||
|
||||
expect(mockCache.writeQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1417,6 +1417,14 @@ export const workItemHierarchyEmptyResponse = {
|
|||
parent: null,
|
||||
hasChildren: false,
|
||||
children: {
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
__typename: 'PageInfo',
|
||||
},
|
||||
count: 1,
|
||||
nodes: [],
|
||||
__typename: 'WorkItemConnection',
|
||||
},
|
||||
|
|
@ -1471,6 +1479,14 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
|
|||
parent: null,
|
||||
hasChildren: true,
|
||||
children: {
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
__typename: 'PageInfo',
|
||||
},
|
||||
count: 1,
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/WorkItem/2',
|
||||
|
|
@ -1767,6 +1783,65 @@ export const workItemObjectiveNoMetadata = {
|
|||
],
|
||||
};
|
||||
|
||||
export const workItemHierarchyTreeEmptyResponse = {
|
||||
data: {
|
||||
workItem: {
|
||||
id: 'gid://gitlab/WorkItem/2',
|
||||
iid: '2',
|
||||
archived: false,
|
||||
workItemType: {
|
||||
id: 'gid://gitlab/WorkItems::Type/2411',
|
||||
name: 'Objective',
|
||||
iconName: 'issue-type-objective',
|
||||
__typename: 'WorkItemType',
|
||||
},
|
||||
title: 'New title',
|
||||
userPermissions: {
|
||||
deleteWorkItem: true,
|
||||
updateWorkItem: true,
|
||||
setWorkItemMetadata: true,
|
||||
adminParentLink: true,
|
||||
createNote: true,
|
||||
adminWorkItemLink: true,
|
||||
__typename: 'WorkItemPermissions',
|
||||
},
|
||||
confidential: false,
|
||||
reference: 'test-project-path#2',
|
||||
namespace: {
|
||||
__typename: 'Project',
|
||||
id: '1',
|
||||
fullPath: 'test-project-path',
|
||||
name: 'Project name',
|
||||
},
|
||||
widgets: [
|
||||
{
|
||||
type: 'DESCRIPTION',
|
||||
__typename: 'WorkItemWidgetDescription',
|
||||
},
|
||||
{
|
||||
type: 'HIERARCHY',
|
||||
parent: null,
|
||||
hasChildren: true,
|
||||
children: {
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
__typename: 'PageInfo',
|
||||
},
|
||||
count: 0,
|
||||
nodes: [],
|
||||
__typename: 'WorkItemConnection',
|
||||
},
|
||||
__typename: 'WorkItemWidgetHierarchy',
|
||||
},
|
||||
],
|
||||
__typename: 'WorkItem',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const workItemHierarchyTreeResponse = {
|
||||
data: {
|
||||
workItem: {
|
||||
|
|
@ -1807,10 +1882,18 @@ export const workItemHierarchyTreeResponse = {
|
|||
parent: null,
|
||||
hasChildren: true,
|
||||
children: {
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
__typename: 'PageInfo',
|
||||
},
|
||||
count: 1,
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/WorkItem/13',
|
||||
iid: '13',
|
||||
id: 'gid://gitlab/WorkItem/2',
|
||||
iid: '2',
|
||||
workItemType: {
|
||||
id: 'gid://gitlab/WorkItems::Type/2411',
|
||||
name: 'Objective',
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ RSpec.describe Gitlab::Database::Decomposition::Migrate, :delete, query_analyzer
|
|||
it 'raises error' do
|
||||
expect { process }.to raise_error(
|
||||
Gitlab::Database::Decomposition::MigrateError,
|
||||
"Found 1 unfinished Background Migration(s). Please wait until they are finished."
|
||||
"Found 1 unfinished background migration(s). Please wait until they are finished."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ RSpec.describe RuboCop::Cop::Migration::BackgroundMigrations do
|
|||
expect_offense(<<~RUBY)
|
||||
def up
|
||||
queue_background_migration_jobs_by_range_at_intervals('example', 'example', 1, batch_size: 1, track_jobs: true)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Background migrations are deprecated. Please use a Batched Background Migration instead[...]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Background migrations are deprecated. Please use a batched background migration instead[...]
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
|
@ -20,7 +20,7 @@ RSpec.describe RuboCop::Cop::Migration::BackgroundMigrations do
|
|||
expect_offense(<<~RUBY)
|
||||
def up
|
||||
requeue_background_migration_jobs_by_range_at_intervals('example', 1)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Background migrations are deprecated. Please use a Batched Background Migration instead[...]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Background migrations are deprecated. Please use a batched background migration instead[...]
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
|
@ -31,7 +31,7 @@ RSpec.describe RuboCop::Cop::Migration::BackgroundMigrations do
|
|||
expect_offense(<<~RUBY)
|
||||
def up
|
||||
migrate_in(1, 'example', 1, ['example'])
|
||||
^^^^^^^^^^ Background migrations are deprecated. Please use a Batched Background Migration instead[...]
|
||||
^^^^^^^^^^ Background migrations are deprecated. Please use a batched background migration instead[...]
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ RSpec.describe SemgrepResultProcessor, feature_category: :tooling do
|
|||
{
|
||||
"id" => 1933334610,
|
||||
"type" => "DiffNote",
|
||||
"body" => "Deserializing user-controlled objects can cause vulnerabilities. \n\n \u003csmall\u003e\n This AppSec automation is currently under testing.\n Use ~\"appsec-sast::helpful\" or ~\"appsec-sast::unhelpful\" for quick feedback.\n For any detailed feedback, [add a comment here](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/issues/38).\n \u003c/small\u003e\n\n /label ~\"appsec-sast::commented\"",
|
||||
"body" => "Deserializing user-controlled objects can cause vulnerabilities.\n\n\n\u003csmall\u003e\nThis AppSec automation is currently under testing.\nUse ~\"appsec-sast::helpful\" or ~\"appsec-sast::unhelpful\" for quick feedback.\nFor any detailed feedback, [add a comment here](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/issues/38).\n\u003c/small\u003e\n\n\n/label ~\"appsec-sast::commented\"",
|
||||
"author" => {
|
||||
"id" => 21564538
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue