Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b68afda329
commit
49d36ce6e3
|
|
@ -160,7 +160,6 @@ RSpec/ContextWording:
|
|||
- 'ee/spec/features/projects/settings/push_rules_settings_spec.rb'
|
||||
- 'ee/spec/features/promotion_spec.rb'
|
||||
- 'ee/spec/features/protected_branches_spec.rb'
|
||||
- 'ee/spec/features/signup_spec.rb'
|
||||
- 'ee/spec/features/users/login_spec.rb'
|
||||
- 'ee/spec/features/users/signup_spec.rb'
|
||||
- 'ee/spec/finders/approval_rules/group_finder_spec.rb'
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ RSpec/ExpectInHook:
|
|||
- 'ee/spec/features/projects/feature_flags/user_creates_feature_flag_spec.rb'
|
||||
- 'ee/spec/features/projects/feature_flags/user_deletes_feature_flag_spec.rb'
|
||||
- 'ee/spec/features/projects/settings/ee/service_desk_setting_spec.rb'
|
||||
- 'ee/spec/features/signup_spec.rb'
|
||||
- 'ee/spec/finders/license_template_finder_spec.rb'
|
||||
- 'ee/spec/finders/projects/integrations/jira/issues_finder_spec.rb'
|
||||
- 'ee/spec/finders/template_finder_spec.rb'
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export default {
|
|||
|
||||
if (window.location.hash) {
|
||||
const hash = getLocationHash();
|
||||
const lineToMatch = `L${line.lineNumber + 1}`;
|
||||
const lineToMatch = `L${line.lineNumber}`;
|
||||
|
||||
if (hash === lineToMatch) {
|
||||
applyHashHighlight = true;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
const hash = getLocationHash();
|
||||
const lineToMatch = `L${this.line.lineNumber + 1}`;
|
||||
const lineToMatch = `L${this.line.lineNumber}`;
|
||||
|
||||
if (hash === lineToMatch) {
|
||||
this.applyHashHighlight = true;
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ export default {
|
|||
render(h, { props }) {
|
||||
const { lineNumber, path } = props;
|
||||
|
||||
const parsedLineNumber = lineNumber + 1;
|
||||
const lineId = `L${parsedLineNumber}`;
|
||||
const lineId = `L${lineNumber}`;
|
||||
const lineHref = `${path}#${lineId}`;
|
||||
|
||||
return h(
|
||||
|
|
@ -27,7 +26,7 @@ export default {
|
|||
href: lineHref,
|
||||
},
|
||||
},
|
||||
parsedLineNumber,
|
||||
lineNumber,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,20 +19,17 @@ export const parseLine = (line = {}, lineNumber) => ({
|
|||
* @param Number lineNumber
|
||||
*/
|
||||
export const parseHeaderLine = (line = {}, lineNumber, hash) => {
|
||||
let isClosed = parseBoolean(line.section_options?.collapsed);
|
||||
|
||||
// if a hash is present in the URL then we ensure
|
||||
// all sections are visible so we can scroll to the hash
|
||||
// in the DOM
|
||||
if (hash) {
|
||||
return {
|
||||
isClosed: false,
|
||||
isHeader: true,
|
||||
line: parseLine(line, lineNumber),
|
||||
lines: [],
|
||||
};
|
||||
isClosed = false;
|
||||
}
|
||||
|
||||
return {
|
||||
isClosed: parseBoolean(line.section_options?.collapsed),
|
||||
isClosed,
|
||||
isHeader: true,
|
||||
line: parseLine(line, lineNumber),
|
||||
lines: [],
|
||||
|
|
@ -80,27 +77,28 @@ export const isCollapsibleSection = (acc = [], last = {}, section = {}) =>
|
|||
section.section === last.line.section;
|
||||
|
||||
/**
|
||||
* Returns the lineNumber of the last line in
|
||||
* a parsed log
|
||||
* Returns the next line number in the parsed log
|
||||
*
|
||||
* @param Array acc
|
||||
* @returns Number
|
||||
*/
|
||||
export const getIncrementalLineNumber = (acc) => {
|
||||
let lineNumberValue;
|
||||
const lastIndex = acc.length - 1;
|
||||
const lastElement = acc[lastIndex];
|
||||
export const getNextLineNumber = (acc) => {
|
||||
if (!acc?.length) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const lastElement = acc[acc.length - 1];
|
||||
const nestedLines = lastElement.lines;
|
||||
|
||||
if (lastElement.isHeader && !nestedLines.length && lastElement.line) {
|
||||
lineNumberValue = lastElement.line.lineNumber;
|
||||
} else if (lastElement.isHeader && nestedLines.length) {
|
||||
lineNumberValue = nestedLines[nestedLines.length - 1].lineNumber;
|
||||
} else {
|
||||
lineNumberValue = lastElement.lineNumber;
|
||||
return lastElement.line.lineNumber + 1;
|
||||
}
|
||||
|
||||
return lineNumberValue === 0 ? 1 : lineNumberValue + 1;
|
||||
if (lastElement.isHeader && nestedLines.length) {
|
||||
return nestedLines[nestedLines.length - 1].lineNumber + 1;
|
||||
}
|
||||
|
||||
return lastElement.lineNumber + 1;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -119,31 +117,28 @@ export const getIncrementalLineNumber = (acc) => {
|
|||
* @returns Array parsed log lines
|
||||
*/
|
||||
export const logLinesParser = (lines = [], prevLogLines = [], hash = '') =>
|
||||
lines.reduce(
|
||||
(acc, line, index) => {
|
||||
const lineNumber = acc.length > 0 ? getIncrementalLineNumber(acc) : index;
|
||||
lines.reduce((acc, line) => {
|
||||
const lineNumber = getNextLineNumber(acc);
|
||||
|
||||
const last = acc[acc.length - 1];
|
||||
const last = acc[acc.length - 1];
|
||||
|
||||
// If the object is an header, we parse it into another structure
|
||||
if (line.section_header) {
|
||||
acc.push(parseHeaderLine(line, lineNumber, hash));
|
||||
} else if (isCollapsibleSection(acc, last, line)) {
|
||||
// if the object belongs to a nested section, we append it to the new `lines` array of the
|
||||
// previously formatted header
|
||||
last.lines.push(parseLine(line, lineNumber));
|
||||
} else if (line.section_duration) {
|
||||
// if the line has section_duration, we look for the correct header to add it
|
||||
addDurationToHeader(acc, line);
|
||||
} else {
|
||||
// otherwise it's a regular line
|
||||
acc.push(parseLine(line, lineNumber));
|
||||
}
|
||||
// If the object is an header, we parse it into another structure
|
||||
if (line.section_header) {
|
||||
acc.push(parseHeaderLine(line, lineNumber, hash));
|
||||
} else if (isCollapsibleSection(acc, last, line)) {
|
||||
// if the object belongs to a nested section, we append it to the new `lines` array of the
|
||||
// previously formatted header
|
||||
last.lines.push(parseLine(line, lineNumber));
|
||||
} else if (line.section_duration) {
|
||||
// if the line has section_duration, we look for the correct header to add it
|
||||
addDurationToHeader(acc, line);
|
||||
} else {
|
||||
// otherwise it's a regular line
|
||||
acc.push(parseLine(line, lineNumber));
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
[...prevLogLines],
|
||||
);
|
||||
return acc;
|
||||
}, prevLogLines);
|
||||
|
||||
/**
|
||||
* Finds the repeated offset, removes the old one
|
||||
|
|
|
|||
|
|
@ -362,7 +362,12 @@ export default {
|
|||
},
|
||||
},
|
||||
update: (cache, { data: { workItemCreate } }) =>
|
||||
addHierarchyChild(cache, this.fullPath, String(this.issueIid), workItemCreate.workItem),
|
||||
addHierarchyChild({
|
||||
cache,
|
||||
fullPath: this.fullPath,
|
||||
iid: String(this.issueIid),
|
||||
workItem: workItemCreate.workItem,
|
||||
}),
|
||||
});
|
||||
|
||||
const { workItem, errors } = data.workItemCreate;
|
||||
|
|
@ -392,7 +397,12 @@ export default {
|
|||
mutation: deleteWorkItemMutation,
|
||||
variables: { input: { id } },
|
||||
update: (cache) =>
|
||||
removeHierarchyChild(cache, this.fullPath, String(this.issueIid), { id }),
|
||||
removeHierarchyChild({
|
||||
cache,
|
||||
fullPath: this.fullPath,
|
||||
iid: String(this.issueIid),
|
||||
workItem: { id },
|
||||
}),
|
||||
});
|
||||
|
||||
if (data.workItemDelete.errors?.length) {
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@ export const I18N_OAUTH_FAILED_MESSAGE = s__(
|
|||
export const INTEGRATIONS_DOC_LINK = helpPagePath('integration/jira/development_panel', {
|
||||
anchor: 'use-the-integration',
|
||||
});
|
||||
export const OAUTH_SELF_MANAGED_DOC_LINK = helpPagePath('integration/jira/connect-app', {
|
||||
anchor: 'connect-the-gitlab-for-jira-cloud-app-for-self-managed-instances',
|
||||
export const OAUTH_SELF_MANAGED_DOC_LINK = helpPagePath('administration/settings/jira_cloud_app', {
|
||||
anchor: 'set-up-oauth-authentication',
|
||||
});
|
||||
export const FAILED_TO_UPDATE_DOC_LINK = helpPagePath('integration/jira/connect-app', {
|
||||
anchor: 'failed-to-update-the-gitlab-instance-for-self-managed-instances',
|
||||
export const FAILED_TO_UPDATE_DOC_LINK = helpPagePath('administration/settings/jira_cloud_app', {
|
||||
anchor: 'failed-to-update-the-gitlab-instance',
|
||||
});
|
||||
|
||||
export const GITLAB_COM_BASE_PATH = 'https://gitlab.com';
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<gl-disclosure-dropdown-item
|
||||
data-qa-selector="delete_member_dropdown_item"
|
||||
data-testid="delete-member-dropdown-item"
|
||||
@action="showRemoveMemberModal(modalData)"
|
||||
>
|
||||
<template #list-item>
|
||||
|
|
|
|||
|
|
@ -109,7 +109,6 @@ export default {
|
|||
no-caret
|
||||
placement="right"
|
||||
data-testid="user-action-dropdown"
|
||||
data-qa-selector="user_action_dropdown"
|
||||
>
|
||||
<disable-two-factor-dropdown-item
|
||||
v-if="permissions.canDisableTwoFactor"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
import { WORKSPACE_GROUP } from '~/issues/constants';
|
||||
import { initWorkItemsRoot } from '~/work_items';
|
||||
|
||||
initWorkItemsRoot(WORKSPACE_GROUP);
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
import { initWorkItemsRoot } from '~/work_items/index';
|
||||
import { initWorkItemsRoot } from '~/work_items';
|
||||
|
||||
initWorkItemsRoot();
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ export default {
|
|||
class="table tree-table"
|
||||
:class="{ 'gl-table-layout-fixed': !showParentRow }"
|
||||
aria-live="polite"
|
||||
data-qa-selector="file_tree_table"
|
||||
data-testid="file-tree-table"
|
||||
>
|
||||
<table-header v-once />
|
||||
<tbody>
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ export default {
|
|||
'is-submodule': isSubmodule,
|
||||
}"
|
||||
class="tree-item-link str-truncated"
|
||||
data-qa-selector="file_name_link"
|
||||
data-testid="file-name-link"
|
||||
>
|
||||
<file-icon
|
||||
:file-name="fullPath"
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export default {
|
|||
item.extraAttrs = {
|
||||
...USER_MENU_TRACKING_DEFAULTS,
|
||||
'data-track-label': 'user_profile',
|
||||
'data-testid': 'user_profile_link',
|
||||
'data-testid': 'user-profile-link',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { ASC } from '~/notes/constants';
|
|||
import { __ } from '~/locale';
|
||||
import { clearDraft } from '~/lib/utils/autosave';
|
||||
import createNoteMutation from '../../graphql/notes/create_work_item_note.mutation.graphql';
|
||||
import groupWorkItemByIidQuery from '../../graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '../../graphql/work_item_by_iid.query.graphql';
|
||||
import { TRACKING_CATEGORY_SHOW, i18n } from '../../constants';
|
||||
import WorkItemNoteSignedOut from './work_item_note_signed_out.vue';
|
||||
|
|
@ -21,7 +22,7 @@ export default {
|
|||
WorkItemCommentForm,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
inject: ['fullPath'],
|
||||
inject: ['fullPath', 'isGroup'],
|
||||
props: {
|
||||
workItemId: {
|
||||
type: String,
|
||||
|
|
@ -90,7 +91,9 @@ export default {
|
|||
},
|
||||
apollo: {
|
||||
workItem: {
|
||||
query: workItemByIidQuery,
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { GlAvatarLink, GlAvatar } from '@gitlab/ui';
|
|||
import * as Sentry from '@sentry/browser';
|
||||
import toast from '~/vue_shared/plugins/global_toast';
|
||||
import { __ } from '~/locale';
|
||||
import { i18n, TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
|
||||
import Tracking from '~/tracking';
|
||||
import { updateDraft, clearDraft } from '~/lib/utils/autosave';
|
||||
import { renderMarkdown } from '~/notes/utils';
|
||||
|
|
@ -11,15 +10,17 @@ import { getLocationHash } from '~/lib/utils/url_utility';
|
|||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import EditedAt from '~/issues/show/components/edited.vue';
|
||||
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
|
||||
import NoteBody from '~/work_items/components/notes/work_item_note_body.vue';
|
||||
import NoteHeader from '~/notes/components/note_header.vue';
|
||||
import NoteActions from '~/work_items/components/notes/work_item_note_actions.vue';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
|
||||
import { i18n, TRACKING_CATEGORY_SHOW } from '../../constants';
|
||||
import groupWorkItemByIidQuery from '../../graphql/group_work_item_by_iid.query.graphql';
|
||||
import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
|
||||
import updateWorkItemNoteMutation from '../../graphql/notes/update_work_item_note.mutation.graphql';
|
||||
import workItemByIidQuery from '../../graphql/work_item_by_iid.query.graphql';
|
||||
import { isAssigneesWidget } from '../../utils';
|
||||
import WorkItemCommentForm from './work_item_comment_form.vue';
|
||||
import NoteActions from './work_item_note_actions.vue';
|
||||
import WorkItemNoteAwardsList from './work_item_note_awards_list.vue';
|
||||
import NoteBody from './work_item_note_body.vue';
|
||||
|
||||
export default {
|
||||
name: 'WorkItemNoteThread',
|
||||
|
|
@ -35,7 +36,7 @@ export default {
|
|||
EditedAt,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
inject: ['fullPath'],
|
||||
inject: ['fullPath', 'isGroup'],
|
||||
props: {
|
||||
workItemId: {
|
||||
type: String,
|
||||
|
|
@ -169,7 +170,9 @@ export default {
|
|||
},
|
||||
apollo: {
|
||||
workItem: {
|
||||
query: workItemByIidQuery,
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ import { __, s__ } from '~/locale';
|
|||
import Tracking from '~/tracking';
|
||||
import toast from '~/vue_shared/plugins/global_toast';
|
||||
import { isLoggedIn } from '~/lib/utils/common_utils';
|
||||
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
|
||||
import groupWorkItemByIidQuery from '../graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
|
||||
|
||||
import {
|
||||
sprintfWorkItem,
|
||||
|
|
@ -70,7 +71,7 @@ export default {
|
|||
copyCreateNoteEmailTestId: TEST_ID_COPY_CREATE_NOTE_EMAIL_ACTION,
|
||||
deleteActionTestId: TEST_ID_DELETE_ACTION,
|
||||
promoteActionTestId: TEST_ID_PROMOTE_ACTION,
|
||||
inject: ['fullPath'],
|
||||
inject: ['fullPath', 'isGroup'],
|
||||
props: {
|
||||
workItemId: {
|
||||
type: String,
|
||||
|
|
@ -256,7 +257,7 @@ export default {
|
|||
},
|
||||
updateWorkItemNotificationsWidgetCache({ cache, issue }) {
|
||||
const query = {
|
||||
query: workItemByIidQuery,
|
||||
query: this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery,
|
||||
variables: { fullPath: this.fullPath, iid: this.workItemIid },
|
||||
};
|
||||
// Read the work item object
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ import { GlAvatarLink, GlSprintf, GlLoadingIcon } from '@gitlab/ui';
|
|||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { WORKSPACE_PROJECT } from '~/issues/constants';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import WorkItemStateBadge from '~/work_items/components/work_item_state_badge.vue';
|
||||
import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
|
||||
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
|
||||
import groupWorkItemByIidQuery from '../graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
|
||||
import WorkItemStateBadge from './work_item_state_badge.vue';
|
||||
import WorkItemTypeIcon from './work_item_type_icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -18,7 +19,7 @@ export default {
|
|||
ConfidentialityBadge,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
inject: ['fullPath'],
|
||||
inject: ['fullPath', 'isGroup'],
|
||||
props: {
|
||||
workItemIid: {
|
||||
type: String,
|
||||
|
|
@ -59,7 +60,9 @@ export default {
|
|||
},
|
||||
apollo: {
|
||||
workItem: {
|
||||
query: workItemByIidQuery,
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import Tracking from '~/tracking';
|
|||
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
|
||||
import { autocompleteDataSources, markdownPreviewPath } from '../utils';
|
||||
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 { i18n, TRACKING_CATEGORY_SHOW, WIDGET_TYPE_DESCRIPTION } from '../constants';
|
||||
import WorkItemDescriptionRendered from './work_item_description_rendered.vue';
|
||||
|
|
@ -25,7 +26,7 @@ export default {
|
|||
WorkItemDescriptionRendered,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
inject: ['fullPath'],
|
||||
inject: ['fullPath', 'isGroup'],
|
||||
props: {
|
||||
workItemId: {
|
||||
type: String,
|
||||
|
|
@ -55,7 +56,9 @@ export default {
|
|||
},
|
||||
apollo: {
|
||||
workItem: {
|
||||
query: workItemByIidQuery,
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import { getParameterByName, updateHistory, setUrlParams } from '~/lib/utils/url
|
|||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { isLoggedIn } from '~/lib/utils/common_utils';
|
||||
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
|
||||
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
|
||||
import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
|
||||
import { WORKSPACE_PROJECT } from '~/issues/constants';
|
||||
|
|
@ -37,6 +36,7 @@ import {
|
|||
import workItemUpdatedSubscription from '../graphql/work_item_updated.subscription.graphql';
|
||||
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
|
||||
import updateWorkItemTaskMutation from '../graphql/update_work_item_task.mutation.graphql';
|
||||
import groupWorkItemByIidQuery from '../graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
|
||||
import { findHierarchyWidgetChildren } from '../utils';
|
||||
|
||||
|
|
@ -52,6 +52,7 @@ import WorkItemDetailModal from './work_item_detail_modal.vue';
|
|||
import WorkItemAwardEmoji from './work_item_award_emoji.vue';
|
||||
import WorkItemStateToggleButton from './work_item_state_toggle_button.vue';
|
||||
import WorkItemRelationships from './work_item_relationships/work_item_relationships.vue';
|
||||
import WorkItemTypeIcon from './work_item_type_icon.vue';
|
||||
|
||||
export default {
|
||||
i18n,
|
||||
|
|
@ -84,7 +85,7 @@ export default {
|
|||
WorkItemRelationships,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
inject: ['fullPath', 'reportAbusePath'],
|
||||
inject: ['fullPath', 'isGroup', 'reportAbusePath'],
|
||||
props: {
|
||||
isModal: {
|
||||
type: Boolean,
|
||||
|
|
@ -118,7 +119,9 @@ export default {
|
|||
},
|
||||
apollo: {
|
||||
workItem: {
|
||||
query: workItemByIidQuery,
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
|
|
@ -189,8 +192,8 @@ export default {
|
|||
canAssignUnassignUser() {
|
||||
return this.workItemAssignees && this.canSetWorkItemMetadata;
|
||||
},
|
||||
fullPath() {
|
||||
return this.workItem?.project.fullPath;
|
||||
projectFullPath() {
|
||||
return this.workItem?.project?.fullPath;
|
||||
},
|
||||
workItemsMvc2Enabled() {
|
||||
return this.glFeatures.workItemsMvc2;
|
||||
|
|
@ -460,7 +463,7 @@ export default {
|
|||
v-if="showWorkItemCurrentUserTodos"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-iid="workItemIid"
|
||||
:work-item-fullpath="workItem.project.fullPath"
|
||||
:work-item-fullpath="projectFullPath"
|
||||
:current-user-todos="currentUserTodos"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
|
|
@ -535,7 +538,7 @@ export default {
|
|||
v-if="showWorkItemCurrentUserTodos"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-iid="workItemIid"
|
||||
:work-item-fullpath="workItem.project.fullPath"
|
||||
:work-item-fullpath="projectFullPath"
|
||||
:current-user-todos="currentUserTodos"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
|
|
@ -585,7 +588,7 @@ export default {
|
|||
<work-item-award-emoji
|
||||
v-if="workItemAwardEmoji"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-fullpath="workItem.project.fullPath"
|
||||
:work-item-fullpath="projectFullPath"
|
||||
:award-emoji="workItemAwardEmoji.awardEmoji"
|
||||
:work-item-iid="workItemIid"
|
||||
@error="updateError = $event"
|
||||
|
|
@ -607,7 +610,7 @@ export default {
|
|||
v-if="showWorkItemLinkedItems"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-iid="workItemIid"
|
||||
:work-item-full-path="workItem.project.fullPath"
|
||||
:work-item-full-path="projectFullPath"
|
||||
:work-item-type="workItem.workItemType.name"
|
||||
@showModal="openInModal"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import LabelItem from '~/sidebar/components/labels/labels_select_widget/label_it
|
|||
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
||||
import { isScopedLabel } from '~/lib/utils/common_utils';
|
||||
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 { i18n, I18N_WORK_ITEM_ERROR_FETCHING_LABELS, TRACKING_CATEGORY_SHOW } from '../constants';
|
||||
import { isLabelsWidget } from '../utils';
|
||||
|
|
@ -37,7 +38,7 @@ export default {
|
|||
LabelItem,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
inject: ['fullPath'],
|
||||
inject: ['fullPath', 'isGroup'],
|
||||
props: {
|
||||
workItemId: {
|
||||
type: String,
|
||||
|
|
@ -65,7 +66,9 @@ export default {
|
|||
},
|
||||
apollo: {
|
||||
workItem: {
|
||||
query: workItemByIidQuery,
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ 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 WorkItemLinkChild from './work_item_link_child.vue';
|
||||
|
||||
|
|
@ -20,7 +21,7 @@ export default {
|
|||
components: {
|
||||
WorkItemLinkChild,
|
||||
},
|
||||
inject: ['fullPath'],
|
||||
inject: ['fullPath', 'isGroup'],
|
||||
props: {
|
||||
workItemType: {
|
||||
type: String,
|
||||
|
|
@ -83,7 +84,14 @@ export default {
|
|||
const { data } = await this.$apollo.mutate({
|
||||
mutation: updateWorkItemMutation,
|
||||
variables: { input: { id: child.id, hierarchyWidget: { parentId: null } } },
|
||||
update: (cache) => removeHierarchyChild(cache, this.fullPath, this.workItemIid, child),
|
||||
update: (cache) =>
|
||||
removeHierarchyChild({
|
||||
cache,
|
||||
fullPath: this.fullPath,
|
||||
iid: this.workItemIid,
|
||||
isGroup: this.isGroup,
|
||||
workItem: child,
|
||||
}),
|
||||
});
|
||||
|
||||
if (data.workItemUpdate.errors.length) {
|
||||
|
|
@ -109,7 +117,14 @@ export default {
|
|||
const { data } = await this.$apollo.mutate({
|
||||
mutation: updateWorkItemMutation,
|
||||
variables: { input: { id: child.id, hierarchyWidget: { parentId: this.workItemId } } },
|
||||
update: (cache) => addHierarchyChild(cache, this.fullPath, this.workItemIid, child),
|
||||
update: (cache) =>
|
||||
addHierarchyChild({
|
||||
cache,
|
||||
fullPath: this.fullPath,
|
||||
iid: this.workItemIid,
|
||||
isGroup: this.isGroup,
|
||||
workItem: child,
|
||||
}),
|
||||
});
|
||||
|
||||
if (data.workItemUpdate.errors.length) {
|
||||
|
|
@ -124,7 +139,7 @@ export default {
|
|||
},
|
||||
addWorkItemQuery({ iid }) {
|
||||
this.$apollo.addSmartQuery('prefetchedWorkItem', {
|
||||
query: workItemByIidQuery,
|
||||
query: this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery,
|
||||
variables: {
|
||||
fullPath: this.fullPath,
|
||||
iid,
|
||||
|
|
@ -206,7 +221,7 @@ export default {
|
|||
update: (store) => {
|
||||
store.updateQuery(
|
||||
{
|
||||
query: workItemByIidQuery,
|
||||
query: this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery,
|
||||
variables: { fullPath: this.fullPath, iid: this.workItemIid },
|
||||
},
|
||||
(sourceData) =>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_sel
|
|||
import { FORM_TYPES, WIDGET_ICONS, WORK_ITEM_STATUS_TEXT } 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 WidgetWrapper from '../widget_wrapper.vue';
|
||||
import WorkItemDetailModal from '../work_item_detail_modal.vue';
|
||||
|
|
@ -39,7 +40,7 @@ export default {
|
|||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
inject: ['fullPath', 'reportAbusePath'],
|
||||
inject: ['fullPath', 'isGroup', 'reportAbusePath'],
|
||||
props: {
|
||||
issuableId: {
|
||||
type: Number,
|
||||
|
|
@ -52,7 +53,9 @@ export default {
|
|||
},
|
||||
apollo: {
|
||||
workItem: {
|
||||
query: workItemByIidQuery,
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
|
|
@ -171,7 +174,13 @@ export default {
|
|||
},
|
||||
handleWorkItemDeleted(child) {
|
||||
const { defaultClient: cache } = this.$apollo.provider.clients;
|
||||
removeHierarchyChild(cache, this.fullPath, this.iid, child);
|
||||
removeHierarchyChild({
|
||||
cache,
|
||||
fullPath: this.fullPath,
|
||||
iid: this.iid,
|
||||
isGroup: this.isGroup,
|
||||
workItem: child,
|
||||
});
|
||||
this.$toast.show(s__('WorkItem|Task deleted'));
|
||||
},
|
||||
updateWorkItemIdUrlQuery({ iid } = {}) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default {
|
|||
GlTooltip,
|
||||
WorkItemTokenInput,
|
||||
},
|
||||
inject: ['fullPath', 'hasIterationsFeature'],
|
||||
inject: ['fullPath', 'hasIterationsFeature', 'isGroup'],
|
||||
props: {
|
||||
issuableGid: {
|
||||
type: String,
|
||||
|
|
@ -260,7 +260,13 @@ export default {
|
|||
input: this.workItemInput,
|
||||
},
|
||||
update: (cache, { data }) =>
|
||||
addHierarchyChild(cache, this.fullPath, this.workItemIid, data.workItemCreate.workItem),
|
||||
addHierarchyChild({
|
||||
cache,
|
||||
fullPath: this.fullPath,
|
||||
iid: this.workItemIid,
|
||||
isGroup: this.isGroup,
|
||||
workItem: data.workItemCreate.workItem,
|
||||
}),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.workItemCreate?.errors?.length) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { GlLoadingIcon, GlIcon, GlButton } from '@gitlab/ui';
|
|||
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
import groupWorkItemByIidQuery from '../../graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '../../graphql/work_item_by_iid.query.graphql';
|
||||
import { WIDGET_TYPE_LINKED_ITEMS, LINKED_CATEGORIES_MAP } from '../../constants';
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ export default {
|
|||
WorkItemRelationshipList,
|
||||
WorkItemAddRelationshipForm,
|
||||
},
|
||||
inject: ['isGroup'],
|
||||
props: {
|
||||
workItemId: {
|
||||
type: String,
|
||||
|
|
@ -41,7 +43,9 @@ export default {
|
|||
},
|
||||
apollo: {
|
||||
workItem: {
|
||||
query: workItemByIidQuery,
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.workItemFullPath,
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ import { produce } from 'immer';
|
|||
|
||||
import { s__ } from '~/locale';
|
||||
import { updateGlobalTodoCount } from '~/sidebar/utils';
|
||||
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
|
||||
import createWorkItemTodosMutation from '~/work_items/graphql/create_work_item_todos.mutation.graphql';
|
||||
import markDoneWorkItemTodosMutation from '~/work_items/graphql/mark_done_work_item_todos.mutation.graphql';
|
||||
import groupWorkItemByIidQuery from '../graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
|
||||
import createWorkItemTodosMutation from '../graphql/create_work_item_todos.mutation.graphql';
|
||||
import markDoneWorkItemTodosMutation from '../graphql/mark_done_work_item_todos.mutation.graphql';
|
||||
|
||||
import {
|
||||
TODO_ADD_ICON,
|
||||
|
|
@ -28,6 +29,7 @@ export default {
|
|||
GlIcon,
|
||||
GlButton,
|
||||
},
|
||||
inject: ['isGroup'],
|
||||
props: {
|
||||
workItemId: {
|
||||
type: String,
|
||||
|
|
@ -148,7 +150,7 @@ export default {
|
|||
},
|
||||
updateWorkItemCurrentTodosWidgetCache({ cache, todos }) {
|
||||
const query = {
|
||||
query: workItemByIidQuery,
|
||||
query: this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery,
|
||||
variables: { fullPath: this.workItemFullpath, iid: this.workItemIid },
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ export default {
|
|||
return this.workItemType.toUpperCase().split(' ').join('_');
|
||||
},
|
||||
iconName() {
|
||||
// TODO Delete this conditional once we have an `issue-type-epic` icon
|
||||
if (this.workItemIconName === 'issue-type-epic') {
|
||||
return 'epic';
|
||||
}
|
||||
|
||||
return (
|
||||
this.workItemIconName ||
|
||||
WORK_ITEMS_TYPE_MAP[this.workItemTypeUppercase]?.icon ||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { produce } from 'immer';
|
||||
import { WIDGET_TYPE_NOTES } 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 { findHierarchyWidgetChildren } from '~/work_items/utils';
|
||||
|
||||
|
|
@ -127,8 +128,11 @@ export const updateCacheAfterRemovingAwardEmojiFromNote = (currentNotes, note) =
|
|||
});
|
||||
};
|
||||
|
||||
export const addHierarchyChild = (cache, fullPath, iid, workItem) => {
|
||||
const queryArgs = { query: workItemByIidQuery, variables: { fullPath, iid } };
|
||||
export const addHierarchyChild = ({ cache, fullPath, iid, isGroup, workItem }) => {
|
||||
const queryArgs = {
|
||||
query: isGroup ? groupWorkItemByIidQuery : workItemByIidQuery,
|
||||
variables: { fullPath, iid },
|
||||
};
|
||||
const sourceData = cache.readQuery(queryArgs);
|
||||
|
||||
if (!sourceData) {
|
||||
|
|
@ -143,8 +147,11 @@ export const addHierarchyChild = (cache, fullPath, iid, workItem) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const removeHierarchyChild = (cache, fullPath, iid, workItem) => {
|
||||
const queryArgs = { query: workItemByIidQuery, variables: { fullPath, iid } };
|
||||
export const removeHierarchyChild = ({ cache, fullPath, iid, isGroup, workItem }) => {
|
||||
const queryArgs = {
|
||||
query: isGroup ? groupWorkItemByIidQuery : workItemByIidQuery,
|
||||
variables: { fullPath, iid },
|
||||
};
|
||||
const sourceData = cache.readQuery(queryArgs);
|
||||
|
||||
if (!sourceData) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
#import "./work_item.fragment.graphql"
|
||||
|
||||
query groupWorkItemByIid($fullPath: ID!, $iid: String) {
|
||||
workspace: group(fullPath: $fullPath) @persist {
|
||||
id
|
||||
workItems(iid: $iid) {
|
||||
nodes {
|
||||
...WorkItem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,25 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { WORKSPACE_GROUP } from '~/issues/constants';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { apolloProvider } from '~/graphql_shared/issuable_client';
|
||||
import App from './components/app.vue';
|
||||
import WorkItemRoot from './pages/work_item_root.vue';
|
||||
import { createRouter } from './router';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
export const initWorkItemsRoot = () => {
|
||||
export const initWorkItemsRoot = (workspace) => {
|
||||
const el = document.querySelector('#js-work-items');
|
||||
|
||||
if (!el) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const {
|
||||
fullPath,
|
||||
hasIssueWeightsFeature,
|
||||
iid,
|
||||
issuesListPath,
|
||||
registerPath,
|
||||
signInPath,
|
||||
|
|
@ -22,6 +30,8 @@ export const initWorkItemsRoot = () => {
|
|||
reportAbusePath,
|
||||
} = el.dataset;
|
||||
|
||||
const Component = workspace === WORKSPACE_GROUP ? WorkItemRoot : App;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'WorkItemsRoot',
|
||||
|
|
@ -29,6 +39,7 @@ export const initWorkItemsRoot = () => {
|
|||
apolloProvider,
|
||||
provide: {
|
||||
fullPath,
|
||||
isGroup: workspace === WORKSPACE_GROUP,
|
||||
hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature),
|
||||
hasOkrsFeature: parseBoolean(hasOkrsFeature),
|
||||
issuesListPath,
|
||||
|
|
@ -40,7 +51,11 @@ export const initWorkItemsRoot = () => {
|
|||
reportAbusePath,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(App);
|
||||
return createElement(Component, {
|
||||
props: {
|
||||
iid: workspace === WORKSPACE_GROUP ? iid : undefined,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from '../constants';
|
||||
import createWorkItemMutation from '../graphql/create_work_item.mutation.graphql';
|
||||
import projectWorkItemTypesQuery from '../graphql/project_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 ItemTitle from '../components/item_title.vue';
|
||||
|
|
@ -22,7 +23,7 @@ export default {
|
|||
ItemTitle,
|
||||
GlFormSelect,
|
||||
},
|
||||
inject: ['fullPath'],
|
||||
inject: ['fullPath', 'isGroup'],
|
||||
props: {
|
||||
initialTitle: {
|
||||
type: String,
|
||||
|
|
@ -94,7 +95,7 @@ export default {
|
|||
const { workItem } = workItemCreate;
|
||||
|
||||
store.writeQuery({
|
||||
query: workItemByIidQuery,
|
||||
query: this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery,
|
||||
variables: {
|
||||
fullPath: this.fullPath,
|
||||
iid: workItem.iid,
|
||||
|
|
|
|||
|
|
@ -26,11 +26,7 @@ module Repositories
|
|||
end
|
||||
|
||||
if download_request?
|
||||
if Feature.enabled?(:lfs_batch_direct_downloads, project)
|
||||
render json: { objects: download_objects! }, content_type: LfsRequest::CONTENT_TYPE
|
||||
else
|
||||
render json: { objects: legacy_download_objects! }, content_type: LfsRequest::CONTENT_TYPE
|
||||
end
|
||||
render json: { objects: download_objects! }, content_type: LfsRequest::CONTENT_TYPE
|
||||
elsif upload_request?
|
||||
render json: { objects: upload_objects! }, content_type: LfsRequest::CONTENT_TYPE
|
||||
else
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ module WikiHelper
|
|||
end
|
||||
|
||||
def wiki_sidebar_toggle_button
|
||||
content_tag :button, class: 'gl-button btn btn-default btn-icon sidebar-toggle js-sidebar-wiki-toggle', role: 'button', type: 'button' do
|
||||
sprite_icon('chevron-double-lg-left')
|
||||
end
|
||||
render Pajamas::ButtonComponent.new(icon: 'chevron-double-lg-left', button_options: { class: 'sidebar-toggle js-sidebar-wiki-toggle' })
|
||||
end
|
||||
|
||||
# Produces a pure text breadcrumb for a given page.
|
||||
|
|
@ -60,17 +58,14 @@ module WikiHelper
|
|||
end
|
||||
|
||||
def wiki_sort_controls(wiki, direction)
|
||||
link_class = 'gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn rspec-reverse-sort'
|
||||
link_class = 'has-tooltip reverse-sort-btn rspec-reverse-sort'
|
||||
reversed_direction = direction == 'desc' ? 'asc' : 'desc'
|
||||
icon_class = direction == 'desc' ? 'highest' : 'lowest'
|
||||
title = direction == 'desc' ? _('Sort direction: Descending') : _('Sort direction: Ascending')
|
||||
|
||||
link_options = { action: :pages, direction: reversed_direction }
|
||||
|
||||
link_to(wiki_path(wiki, **link_options),
|
||||
type: 'button', class: link_class, title: title) do
|
||||
sprite_icon("sort-#{icon_class}")
|
||||
end
|
||||
render Pajamas::ButtonComponent.new(href: wiki_path(wiki, **link_options), icon: "sort-#{icon_class}", button_options: { class: link_class, title: title })
|
||||
end
|
||||
|
||||
def wiki_sort_title(key)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItemsHelper
|
||||
def work_items_index_data(project)
|
||||
def work_items_index_data(resource_parent)
|
||||
{
|
||||
full_path: project.full_path,
|
||||
issues_list_path: project_issues_path(project),
|
||||
full_path: resource_parent.full_path,
|
||||
issues_list_path:
|
||||
resource_parent.is_a?(Group) ? issues_group_path(resource_parent) : project_issues_path(resource_parent),
|
||||
register_path: new_user_registration_path(redirect_to_referer: 'yes'),
|
||||
sign_in_path: new_session_path(:user, redirect_to_referer: 'yes'),
|
||||
new_comment_template_path: profile_comment_templates_path,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ module Integrations
|
|||
super - ['deployment']
|
||||
end
|
||||
|
||||
def avatar_url
|
||||
ActionController::Base.helpers.image_path('illustrations/third-party-logos/integrations-logos/telegram.svg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_webhook
|
||||
|
|
|
|||
|
|
@ -695,7 +695,6 @@ class ProjectPolicy < BasePolicy
|
|||
enable :read_merge_request
|
||||
enable :read_note
|
||||
enable :read_pipeline
|
||||
enable :read_pipeline_schedule
|
||||
enable :read_environment
|
||||
enable :read_deployment
|
||||
enable :read_commit_status
|
||||
|
|
@ -712,7 +711,10 @@ class ProjectPolicy < BasePolicy
|
|||
enable :read_issue
|
||||
end
|
||||
|
||||
rule { can?(:public_access) & public_builds }.enable :read_ci_cd_analytics
|
||||
rule { can?(:public_access) & public_builds }.policy do
|
||||
enable :read_ci_cd_analytics
|
||||
enable :read_pipeline_schedule
|
||||
end
|
||||
|
||||
rule { public_builds }.policy do
|
||||
enable :read_build
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ module Issues
|
|||
return error_invalid_params unless valid_params?
|
||||
|
||||
@existing_ids = issue.customer_relations_contact_ids
|
||||
determine_changes if params[:replace_ids].present?
|
||||
determine_changes if set_present?
|
||||
return error_too_many if too_many?
|
||||
|
||||
@added_count = 0
|
||||
|
|
@ -108,7 +108,7 @@ module Issues
|
|||
end
|
||||
|
||||
def set_present?
|
||||
params[:replace_ids].present?
|
||||
!params[:replace_ids].nil?
|
||||
end
|
||||
|
||||
def add_or_remove_present?
|
||||
|
|
|
|||
|
|
@ -1 +1,7 @@
|
|||
.h1 Work Item
|
||||
- page_title "##{request.params['iid']}"
|
||||
- add_to_breadcrumbs _("Issues"), issues_group_path(@group)
|
||||
- add_page_specific_style 'page_bundles/work_items'
|
||||
- @gfm_form = true
|
||||
- @noteable_type = 'WorkItem'
|
||||
|
||||
#js-work-items{ data: work_items_index_data(@group).merge(iid: request.params['iid']) }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.gl-display-flex.gl-mt-7
|
||||
- submit_button_options = { type: :submit, variant: :confirm, button_options: { id: 'commit-changes', class: 'js-commit-button', data: { qa_selector: 'commit_button' } } }
|
||||
- submit_button_options = { type: :submit, variant: :confirm, button_options: { id: 'commit-changes', class: 'js-commit-button', data: { testid: 'commit-button' } } }
|
||||
= render Pajamas::ButtonComponent.new(**submit_button_options) do
|
||||
= _('Commit changes')
|
||||
= render Pajamas::ButtonComponent.new(loading: true, disabled: true, **submit_button_options.merge({ button_options: { class: 'js-commit-button-loading gl-display-none' } })) do
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@
|
|||
= _('Unfollow')
|
||||
- else
|
||||
= form_tag user_follow_path(@user, :json), class: link_classes do
|
||||
= render Pajamas::ButtonComponent.new(variant: :confirm, type: :submit, button_options: { class: 'gl-w-full', data: { qa_selector: 'follow_user_link', track_action: 'click_button', track_label: 'follow_from_profile' } }) do
|
||||
= render Pajamas::ButtonComponent.new(variant: :confirm, type: :submit, button_options: { class: 'gl-w-full', data: { testid: 'follow-user-link', track_action: 'click_button', track_label: 'follow_from_profile' } }) do
|
||||
= _('Follow')
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
%h4.gl-flex-grow-1
|
||||
= Feature.enabled?(:security_auto_fix) && @user.bot? ? s_('UserProfile|Bot activity') : s_('UserProfile|Activity')
|
||||
= link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all"
|
||||
.overview-content-list{ data: { href: user_activity_path, qa_selector: 'user_activity_content' } }
|
||||
.overview-content-list{ data: { href: user_activity_path, testid: 'user-activity-content' } }
|
||||
= gl_loading_icon(size: 'md', css_class: 'loading')
|
||||
|
||||
- unless Feature.enabled?(:security_auto_fix) && @user.bot?
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ class GitlabShellWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
raise(ArgumentError, "#{action} not allowed for #{self.class.name}")
|
||||
end
|
||||
|
||||
Gitlab::GitalyClient::NamespaceService.allow do
|
||||
gitlab_shell.public_send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
gitlab_shell.public_send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: lfs_batch_direct_downloads
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122221
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/421692
|
||||
milestone: '16.1'
|
||||
name: user_pat_rest_api
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131923
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/425967
|
||||
milestone: '16.5'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: true
|
||||
group: group::authentication and authorization
|
||||
default_enabled: false
|
||||
|
|
@ -845,6 +845,8 @@ these are separate buckets. Use of bucket prefixes
|
|||
Helm-based installs require separate buckets to
|
||||
[handle backup restorations](https://docs.gitlab.com/charts/advanced/external-object-storage/#lfs-artifacts-uploads-packages-external-diffs-terraform-state-dependency-proxy).
|
||||
|
||||
If the same bucket is used for multiple object types, only the first object type in the configuration uses the bucket. Other object types revert to their default local storage (for the default storage locations, see the [table in the linked section)[https://docs.gitlab.com/omnibus/settings/configuration.html#disable-the-varoptgitlab-directory-management)).
|
||||
|
||||
### S3 API compatibility issues
|
||||
|
||||
Not all S3 providers [are fully compatible](../administration/backup_restore/backup_gitlab.md#other-s3-providers)
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ With this method:
|
|||
- The instance must be publicly available.
|
||||
- The instance must be on GitLab version 15.7 or later.
|
||||
- You must set up [OAuth authentication](#set-up-oauth-authentication).
|
||||
- If your instance is using HTTPS, your GitLab certificate must be publicly trusted or contain the full chained certificate.
|
||||
- Your network must allow inbound and outbound connections between GitLab and Jira. For self-managed instances that are behind a
|
||||
firewall and cannot be directly accessed from the internet:
|
||||
- Open your firewall and only allow inbound traffic from [Atlassian IP addresses](https://support.atlassian.com/organization-administration/docs/ip-addresses-and-domains-for-atlassian-cloud-products/#Outgoing-Connections).
|
||||
|
|
|
|||
|
|
@ -328,3 +328,8 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
|
|||
## Create a personal access token (administrator only)
|
||||
|
||||
See the [Users API documentation](users.md#create-a-personal-access-token) for information on creating a personal access token.
|
||||
|
||||
## Create a personal access token with limited scopes for the currently authenticated user **(FREE SELF)**
|
||||
|
||||
See the [Users API documentation](users.md#create-a-personal-access-token-with-limited-scopes-for-the-currently-authenticated-user)
|
||||
for information on creating a personal access token for the currently authenticated user.
|
||||
|
|
|
|||
|
|
@ -2126,6 +2126,47 @@ Example response:
|
|||
}
|
||||
```
|
||||
|
||||
## Create a personal access token with limited scopes for the currently authenticated user **(FREE SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131923) in GitLab 16.5 with a flag named `user_pat_rest_api`.
|
||||
|
||||
Use this API to create a new personal access token for the currently authenticated user.
|
||||
For security purposes, the scopes are limited to only `k8s_proxy` and by default the token will expire by
|
||||
the end of the day it was created at.
|
||||
Token values are returned once so, make sure you save it as you can't access it again.
|
||||
|
||||
```plaintext
|
||||
POST /user/personal_access_tokens
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|--------------|--------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `name` | string | yes | Name of the personal access token |
|
||||
| `scopes` | array | yes | Array of scopes of the personal access token. Possible values are `k8s_proxy` |
|
||||
| `expires_at` | array | no | Expiration date of the access token in ISO format (`YYYY-MM-DD`). If no date is set, the expiration is at the end of the current day. The expiration is subject to the [maximum allowable lifetime of an access token](../user/profile/personal_access_tokens.md#when-personal-access-tokens-expire). |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "name=mytoken" --data "scopes[]=k8s_proxy" "https://gitlab.example.com/api/v4/user/personal_access_tokens"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 3,
|
||||
"name": "mytoken",
|
||||
"revoked": false,
|
||||
"created_at": "2020-10-14T11:58:53.526Z",
|
||||
"scopes": [
|
||||
"k8s_proxy"
|
||||
],
|
||||
"user_id": 42,
|
||||
"active": true,
|
||||
"expires_at": "2020-10-15",
|
||||
"token": "ggbfKkC4n-Lujy8jwCR2"
|
||||
}
|
||||
```
|
||||
|
||||
## Get user activities **(FREE SELF)**
|
||||
|
||||
Pre-requisite:
|
||||
|
|
|
|||
|
|
@ -96,13 +96,21 @@ of each ref do not expire and are not deleted.
|
|||
|
||||
## Error message `This job could not start because it could not retrieve the needed artifacts.`
|
||||
|
||||
A job configured with the [`needs:artifacts`](../yaml/index.md#needsartifacts) keyword
|
||||
fails to start and returns this error message if:
|
||||
A job fails to start and returns this error message if it can't fetch the artifacts
|
||||
it expects. This error is returned when:
|
||||
|
||||
- The job's dependencies cannot be found.
|
||||
- The job's dependencies are not found. By default, jobs in later stages fetch artifacts
|
||||
from jobs in all earlier stages, so the earlier jobs are all considered dependent.
|
||||
If the job uses the [`dependencies`](../yaml/index.md#dependencies) keyword, only
|
||||
the listed jobs are dependent.
|
||||
- The artifacts are already expired. You can set a longer expiry with [`artifacts:expire_in`](../yaml/index.md#artifactsexpire_in).
|
||||
- The job cannot access the relevant resources due to insufficient permissions.
|
||||
|
||||
The troubleshooting steps to follow differ based on the syntax the job uses:
|
||||
See these additional troubleshooting steps if the job uses the [`needs:artifacts`](../yaml/index.md#needsartifacts):
|
||||
keyword with:
|
||||
|
||||
- [`needs:project`](#for-a-job-configured-with-needsproject)
|
||||
- [`needs:pipeline:job`](#for-a-job-configured-with-needspipelinejob)
|
||||
|
||||
- [`needs:project`](#for-a-job-configured-with-needsproject)
|
||||
- [`needs:pipeline:job`](#for-a-job-configured-with-needspipelinejob)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@ On self-managed GitLab instances:
|
|||
- Administrators can [assign more compute minutes](#set-the-compute-quota-for-a-specific-namespace)
|
||||
if a namespace uses all its monthly quota.
|
||||
|
||||
[Trigger jobs](../../ci/yaml/index.md#trigger) do not execute on runners, so they do not
|
||||
consume compute minutes, even when using [`strategy:depend`](../yaml/index.md#triggerstrategy)
|
||||
to wait for the [downstream pipeline](../pipelines/downstream_pipelines.md) status.
|
||||
The triggered downstream pipeline consumes compute minutes the same as other pipelines.
|
||||
|
||||
[Project runners](../runners/runners_scope.md#project-runners) are not subject to a compute quota.
|
||||
|
||||
## Set the compute quota for all namespaces
|
||||
|
|
|
|||
|
|
@ -898,6 +898,11 @@ job:
|
|||
- Select **Keep** on the job page.
|
||||
- [In GitLab 13.3 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/22761), set the value of
|
||||
`expire_in` to `never`.
|
||||
- If the expiry time is too short, jobs in later stages of a long pipeline might try to fetch
|
||||
expired artifacts from earlier jobs. If the artifacts are expired, jobs that try to fetch
|
||||
them fail with a [`could not retrieve the needed artifacts` error](../jobs/job_artifacts_troubleshooting.md#error-message-this-job-could-not-start-because-it-could-not-retrieve-the-needed-artifacts).
|
||||
Set the expiry time to be longer, or use [`dependencies`](#dependencies) in later jobs
|
||||
to ensure they don't try to fetch expired artifacts.
|
||||
|
||||
#### `artifacts:expose_as`
|
||||
|
||||
|
|
@ -1619,10 +1624,11 @@ to select a specific site profile and scanner profile.
|
|||
|
||||
### `dependencies`
|
||||
|
||||
Use the `dependencies` keyword to define a list of jobs to fetch [artifacts](#artifacts) from.
|
||||
You can also set a job to download no artifacts at all.
|
||||
Use the `dependencies` keyword to define a list of specific jobs to fetch [artifacts](#artifacts)
|
||||
from. When `dependencies` is not defined in a job, all jobs in earlier stages are considered dependent
|
||||
and the job fetches all artifacts from those jobs.
|
||||
|
||||
If you do not use `dependencies`, all artifacts from previous stages are passed to each job.
|
||||
You can also set a job to download no artifacts at all.
|
||||
|
||||
**Keyword type**: Job keyword. You can use it only as part of a job.
|
||||
|
||||
|
|
|
|||
|
|
@ -869,7 +869,7 @@ Make sure to prepare for this task by having a
|
|||
bundle exec rake gitlab:elastic:index_users RAILS_ENV=production
|
||||
```
|
||||
|
||||
1. Enable replication and refreshing again after indexing (only if you previously disabled it):
|
||||
1. Enable replication and refreshing again after indexing (only if you previously increased the `refresh_interval`):
|
||||
|
||||
```shell
|
||||
curl --request PUT localhost:9200/gitlab-production/_settings --header 'Content-Type: application/json' \
|
||||
|
|
|
|||
|
|
@ -338,6 +338,7 @@ Below is a list of Mattermost version changes for GitLab 14.0 and later:
|
|||
|
||||
| GitLab version | Mattermost version | Notes |
|
||||
| :------------- | :----------------- | ---------------------------------------------------------------------------------------- |
|
||||
| 16.5 | 9.0 | |
|
||||
| 16.4 | 8.1 | |
|
||||
| 16.3 | 8.0 | |
|
||||
| 16.0 | 7.10 | |
|
||||
|
|
|
|||
|
|
@ -193,7 +193,11 @@ When upgrading:
|
|||
GitLab instances with multiple web nodes) > [`15.4.6`](versions/gitlab_15_changes.md#1540) >
|
||||
[`15.11.13`](versions/gitlab_15_changes.md#15110).
|
||||
- GitLab 16: [`16.0.x`](versions/gitlab_16_changes.md#1600) (only
|
||||
[instances with lots of users](versions/gitlab_16_changes.md#long-running-user-type-data-change)) > [`16.1`](versions/gitlab_16_changes.md#1610)(instances with NPM packages in their Package Registry) > [`16.3`](versions/gitlab_16_changes.md#1630) > [latest `16.Y.Z`](https://gitlab.com/gitlab-org/gitlab/-/releases).
|
||||
instances with [lots of users](versions/gitlab_16_changes.md#long-running-user-type-data-change) or
|
||||
[large pipeline variables history](versions/gitlab_16_changes.md#1610)) >
|
||||
[`16.1`](versions/gitlab_16_changes.md#1610)(instances with NPM packages in their Package Registry) >
|
||||
[`16.2.x`](versions/gitlab_16_changes.md#1620) (only instances with [large pipeline variables history](versions/gitlab_16_changes.md#1630)) >
|
||||
[`16.3`](versions/gitlab_16_changes.md#1630) > [latest `16.Y.Z`](https://gitlab.com/gitlab-org/gitlab/-/releases).
|
||||
|
||||
1. Check for [required upgrade stops](#required-upgrade-stops).
|
||||
1. Consult the [version-specific upgrade instructions](#version-specific-upgrading-instructions).
|
||||
|
|
|
|||
|
|
@ -100,6 +100,19 @@ For more information about upgrading GitLab Helm Chart, see [the release notes f
|
|||
for any of the applications above before
|
||||
upgrading.
|
||||
|
||||
- A `BackfillCiPipelineVariablesForPipelineIdBigintConversion` background migration is finalized with
|
||||
the `EnsureAgainBackfillForCiPipelineVariablesPipelineIdIsFinished` post-deploy migration.
|
||||
GitLab 16.2.0 introduced a [batched background migration](../background_migrations.md#batched-background-migrations) to
|
||||
[backfill bigint `pipeline_id` values on the `ci_pipeline_variables` table](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123132). This
|
||||
migration may take a long time to complete on larger GitLab instances (4 hours to process 50 million rows reported in one case).
|
||||
To avoid a prolonged upgrade downtime, make sure the migration has completed successfully before upgrading to 16.3.
|
||||
|
||||
You can check the size of the `ci_pipeline_variables` table in the [database console](../../administration/troubleshooting/postgresql.md#start-a-database-console):
|
||||
|
||||
```sql
|
||||
select count(*) from ci_pipeline_variables;
|
||||
```
|
||||
|
||||
### Linux package installations
|
||||
|
||||
Specific information applies to Linux package installations:
|
||||
|
|
@ -204,6 +217,18 @@ Specific information applies to installations using Geo:
|
|||
migration may take multiple days to complete on larger GitLab instances. Make sure the migration
|
||||
has completed successfully before upgrading to 16.1.0.
|
||||
- GitLab 16.1.0 includes a [batched background migration](../background_migrations.md#batched-background-migrations) `MarkDuplicateNpmPackagesForDestruction` to mark duplicate NPM packages for destruction. Make sure the migration has completed successfully before upgrading to 16.3.0 or later.
|
||||
- A `BackfillCiPipelineVariablesForBigintConversion` background migration is finalized with
|
||||
the `EnsureBackfillBigintIdIsCompleted` post-deploy migration.
|
||||
GitLab 16.0.0 introduced a [batched background migration](../background_migrations.md#batched-background-migrations) to
|
||||
[backfill bigint `id` values on the `ci_pipeline_variables` table](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118878). This
|
||||
migration may take a long time to complete on larger GitLab instances (4 hours to process 50 million rows reported in one case).
|
||||
To avoid a prolonged upgrade downtime, make sure the migration has completed successfully before upgrading to 16.1.
|
||||
|
||||
You can check the size of the `ci_pipeline_variables` table in the [database console](../../administration/troubleshooting/postgresql.md#start-a-database-console):
|
||||
|
||||
```sql
|
||||
select count(*) from ci_pipeline_variables;
|
||||
```
|
||||
|
||||
### Self-compiled installations
|
||||
|
||||
|
|
|
|||
|
|
@ -1373,6 +1373,35 @@ module API
|
|||
get 'status', feature_category: :user_profile do
|
||||
present current_user.status || {}, with: Entities::UserStatus
|
||||
end
|
||||
|
||||
resource :personal_access_tokens do
|
||||
desc 'Create a personal access token with limited scopes for the currently authenticated user' do
|
||||
detail 'This feature was introduced in GitLab 16.5'
|
||||
success Entities::PersonalAccessTokenWithToken
|
||||
end
|
||||
params do
|
||||
requires :name, type: String, desc: 'The name of the personal access token'
|
||||
# NOTE: for security reasons only the k8s_proxy scope is allowed at the moment.
|
||||
# See details in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131923#note_1571272897
|
||||
# and in https://gitlab.com/gitlab-org/gitlab/-/issues/425171
|
||||
requires :scopes, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, values: [::Gitlab::Auth::K8S_PROXY_SCOPE].map(&:to_s),
|
||||
desc: 'The array of scopes of the personal access token'
|
||||
optional :expires_at, type: Date, default: -> { 1.day.from_now.to_date }, desc: 'The expiration date in the format YEAR-MONTH-DAY of the personal access token'
|
||||
end
|
||||
post feature_category: :system_access do
|
||||
bad_request!('Endpoint is disabled via user_pat_rest_api feature flag. Please contact your administrator to enable it.') unless Feature.enabled?(:user_pat_rest_api)
|
||||
|
||||
response = ::PersonalAccessTokens::CreateService.new(
|
||||
current_user: current_user, target_user: current_user, params: declared_params(include_missing: false)
|
||||
).execute
|
||||
|
||||
if response.success?
|
||||
present response.payload[:personal_access_token], with: Entities::PersonalAccessTokenWithToken
|
||||
else
|
||||
render_api_error!(response.message, response.http_status || :unprocessable_entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# UserReferenceTransformer replaces specified user
|
||||
# reference key with a user id being either:
|
||||
# - A user id found by `public_email` in the group
|
||||
# - Current user id
|
||||
# under a new key `"#{@reference}_id"`.
|
||||
module BulkImports
|
||||
module Common
|
||||
module Transformers
|
||||
class UserReferenceTransformer
|
||||
DEFAULT_REFERENCE = 'user'
|
||||
|
||||
def initialize(options = {})
|
||||
@reference = options[:reference].to_s.presence || DEFAULT_REFERENCE
|
||||
@suffixed_reference = "#{@reference}_id"
|
||||
end
|
||||
|
||||
def transform(context, data)
|
||||
return unless data
|
||||
|
||||
user = find_user(context, data&.dig(@reference, 'public_email')) || context.current_user
|
||||
|
||||
data
|
||||
.except(@reference)
|
||||
.merge(@suffixed_reference => user.id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_user(context, email)
|
||||
return if email.blank?
|
||||
|
||||
context.group.users.find_by_any_email(email, confirmed: true) # rubocop: disable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module GitalyClient
|
||||
class NamespaceService
|
||||
extend Gitlab::TemporarilyAllow
|
||||
|
||||
NamespaceServiceAccessError = Class.new(StandardError)
|
||||
ALLOW_KEY = :allow_namespace
|
||||
|
||||
def self.allow
|
||||
temporarily_allow(ALLOW_KEY) { yield }
|
||||
end
|
||||
|
||||
def self.denied?
|
||||
!temporarily_allowed?(ALLOW_KEY)
|
||||
end
|
||||
|
||||
def initialize(storage)
|
||||
raise NamespaceServiceAccessError if self.class.denied?
|
||||
|
||||
@storage = storage
|
||||
end
|
||||
|
||||
def add(name)
|
||||
request = Gitaly::AddNamespaceRequest.new(storage_name: @storage, name: name)
|
||||
|
||||
gitaly_client_call(:add_namespace, request, timeout: GitalyClient.fast_timeout)
|
||||
end
|
||||
|
||||
def remove(name)
|
||||
request = Gitaly::RemoveNamespaceRequest.new(storage_name: @storage, name: name)
|
||||
|
||||
gitaly_client_call(:remove_namespace, request, timeout: GitalyClient.long_timeout)
|
||||
end
|
||||
|
||||
def rename(from, to)
|
||||
request = Gitaly::RenameNamespaceRequest.new(storage_name: @storage, from: from, to: to)
|
||||
|
||||
gitaly_client_call(:rename_namespace, request, timeout: GitalyClient.fast_timeout)
|
||||
end
|
||||
|
||||
def exists?(name)
|
||||
request = Gitaly::NamespaceExistsRequest.new(storage_name: @storage, name: name)
|
||||
|
||||
response = gitaly_client_call(:namespace_exists, request, timeout: GitalyClient.fast_timeout)
|
||||
response.exists
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gitaly_client_call(type, request, timeout: nil)
|
||||
GitalyClient.call(@storage, :namespace_service, type, request, timeout: timeout)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Prometheus
|
||||
ParsingError = Class.new(StandardError)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Prometheus
|
||||
module Queries
|
||||
class BaseQuery
|
||||
attr_accessor :client
|
||||
|
||||
delegate :query_range, :query, :label_values, :series, to: :client, prefix: true
|
||||
|
||||
def raw_memory_usage_query(environment_slug)
|
||||
%{avg(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / 2^20}
|
||||
end
|
||||
|
||||
def raw_cpu_usage_query(environment_slug)
|
||||
%{avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) * 100}
|
||||
end
|
||||
|
||||
def initialize(client)
|
||||
@client = client
|
||||
end
|
||||
|
||||
def query(*args)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def self.transform_reactive_result(result)
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Prometheus
|
||||
module Queries
|
||||
module QueryAdditionalMetrics
|
||||
def query_metrics(project, environment, query_context)
|
||||
matched_metrics(project).map(&query_group(query_context))
|
||||
.select(&method(:group_with_any_metrics))
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def query_group(query_context)
|
||||
query_processor = method(:process_query).curry[query_context]
|
||||
|
||||
lambda do |group|
|
||||
metrics = group.metrics.map do |metric|
|
||||
metric_hsh = {
|
||||
title: metric.title,
|
||||
weight: metric.weight,
|
||||
y_label: metric.y_label,
|
||||
queries: metric.queries.map(&query_processor).select(&method(:query_with_result))
|
||||
}
|
||||
|
||||
metric_hsh[:id] = metric.id if metric.id
|
||||
|
||||
metric_hsh
|
||||
end
|
||||
|
||||
{
|
||||
group: group.name,
|
||||
priority: group.priority,
|
||||
metrics: metrics.select(&method(:metric_with_any_queries))
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def metric_with_any_queries(metric)
|
||||
metric[:queries]&.count&.> 0
|
||||
end
|
||||
|
||||
def group_with_any_metrics(group)
|
||||
group[:metrics]&.count&.> 0
|
||||
end
|
||||
|
||||
def query_with_result(query)
|
||||
query[:result]&.any? do |item|
|
||||
item&.[](:values)&.any? || item&.[](:value)&.any?
|
||||
end
|
||||
end
|
||||
|
||||
def process_query(context, query)
|
||||
query = query.dup
|
||||
result =
|
||||
if query.key?(:query_range)
|
||||
query[:query_range] %= context
|
||||
client_query_range(query[:query_range], start_time: context[:timeframe_start], end_time: context[:timeframe_end])
|
||||
else
|
||||
query[:query] %= context
|
||||
client_query(query[:query], time: context[:timeframe_end])
|
||||
end
|
||||
|
||||
query[:result] = result&.map(&:deep_symbolize_keys)
|
||||
query
|
||||
end
|
||||
|
||||
def available_metrics
|
||||
@available_metrics ||= client_label_values || []
|
||||
end
|
||||
|
||||
def matched_metrics(project)
|
||||
result = Gitlab::Prometheus::MetricGroup.for_project(project).map do |group|
|
||||
group.metrics.select! do |metric|
|
||||
metric.required_metrics.all?(&available_metrics.method(:include?))
|
||||
end
|
||||
group
|
||||
end
|
||||
|
||||
result.select { |group| group.metrics.any? }
|
||||
end
|
||||
|
||||
def common_query_context(environment, timeframe_start:, timeframe_end:)
|
||||
base_query_context(timeframe_start, timeframe_end)
|
||||
.merge(QueryVariables.call(environment))
|
||||
end
|
||||
|
||||
def base_query_context(timeframe_start, timeframe_end)
|
||||
{
|
||||
timeframe_start: timeframe_start,
|
||||
timeframe_end: timeframe_end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::Prometheus::Queries::QueryAdditionalMetrics.prepend_mod_with('Gitlab::Prometheus::Queries::QueryAdditionalMetrics')
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Prometheus
|
||||
module QueryVariables
|
||||
# start_time and end_time should be Time objects.
|
||||
def self.call(environment, start_time: nil, end_time: nil)
|
||||
{
|
||||
__range: range(start_time, end_time),
|
||||
ci_environment_slug: environment.slug,
|
||||
kube_namespace: environment.deployment_namespace || '',
|
||||
environment_filter: %(container_name!="POD",environment="#{environment.slug}"),
|
||||
ci_project_name: environment.project.name,
|
||||
ci_project_namespace: environment.project.namespace.name,
|
||||
ci_project_path: environment.project.full_path,
|
||||
ci_environment_name: environment.name
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.range(start_time, end_time)
|
||||
if start_time && end_time
|
||||
range_seconds = (end_time - start_time).to_i
|
||||
"#{range_seconds}s"
|
||||
end
|
||||
end
|
||||
private_class_method :range
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -15,8 +15,7 @@ module Gitlab
|
|||
Error = Class.new(StandardError)
|
||||
|
||||
PERMITTED_ACTIONS = %w[
|
||||
mv_repository remove_repository add_namespace mv_namespace
|
||||
repository_exists?
|
||||
mv_repository remove_repository repository_exists?
|
||||
].freeze
|
||||
|
||||
class << self
|
||||
|
|
@ -127,41 +126,6 @@ module Gitlab
|
|||
false
|
||||
end
|
||||
|
||||
# Add empty directory for storing repositories
|
||||
#
|
||||
# @example Add new namespace directory
|
||||
# add_namespace("default", "gitlab")
|
||||
#
|
||||
# @param [String] storage project's storage path
|
||||
# @param [String] name namespace name
|
||||
#
|
||||
# @deprecated
|
||||
def add_namespace(storage, name)
|
||||
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||
Gitlab::GitalyClient::NamespaceService.new(storage).add(name)
|
||||
end
|
||||
rescue GRPC::InvalidArgument => e
|
||||
raise ArgumentError, e.message
|
||||
end
|
||||
|
||||
# Move namespace directory inside repositories storage
|
||||
#
|
||||
# @example Move/rename a namespace directory
|
||||
# mv_namespace("/path/to/storage", "gitlab", "gitlabhq")
|
||||
#
|
||||
# @param [String] storage project's storage path
|
||||
# @param [String] old_name current namespace name
|
||||
# @param [String] new_name new namespace name
|
||||
#
|
||||
# @deprecated
|
||||
def mv_namespace(storage, old_name, new_name)
|
||||
Gitlab::GitalyClient::NamespaceService.new(storage).rename(old_name, new_name)
|
||||
rescue GRPC::InvalidArgument => e
|
||||
Gitlab::ErrorTracking.track_exception(e, old_name: old_name, new_name: new_name, storage: storage)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Check if repository exists on disk
|
||||
#
|
||||
# @example Check if repository exists
|
||||
|
|
|
|||
|
|
@ -22882,7 +22882,7 @@ msgstr ""
|
|||
msgid "GroupSelect|Select a group"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings| %{link_start}What are Experiment features?%{link_end}"
|
||||
msgid "GroupSettings| %{link_start}What do Experiment and Beta mean?%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|After the instance reaches the user cap, any user who is added or requests access must be approved by an administrator. Leave empty for an unlimited user cap. If you change the user cap to unlimited, you must re-enable %{project_sharing_docs_link_start}project sharing%{link_end} and %{group_sharing_docs_link_start}group sharing%{link_end}."
|
||||
|
|
@ -22960,7 +22960,7 @@ msgstr ""
|
|||
msgid "GroupSettings|Enabling these features is your acceptance of the %{link_start}GitLab Testing Agreement%{link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|Experiment features"
|
||||
msgid "GroupSettings|Experiment and Beta features"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|Export group"
|
||||
|
|
@ -23041,7 +23041,7 @@ msgstr ""
|
|||
msgid "GroupSettings|There was a problem updating the pipeline settings: %{error_messages}."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|These features can cause performance and stability issues and may change over time."
|
||||
msgid "GroupSettings|These features are being developed and might be unstable."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
|
||||
|
|
@ -23056,7 +23056,7 @@ msgstr ""
|
|||
msgid "GroupSettings|Transfer group"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|Use Experiment features"
|
||||
msgid "GroupSettings|Use Experiment and Beta features"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|Users can create %{link_start_project}project access tokens%{link_end} and %{link_start_group}group access tokens%{link_end} in this group"
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ module QA
|
|||
end
|
||||
|
||||
base.view 'app/assets/javascripts/members/components/action_dropdowns/user_action_dropdown.vue' do
|
||||
element :user_action_dropdown
|
||||
element 'user-action-dropdown'
|
||||
end
|
||||
|
||||
base.view 'app/assets/javascripts/members/components/action_dropdowns/remove_member_dropdown_item.vue' do
|
||||
element :delete_member_dropdown_item
|
||||
element 'delete-member-dropdown-item'
|
||||
end
|
||||
|
||||
base.view 'app/assets/javascripts/members/components/action_buttons/approve_access_request_button.vue' do
|
||||
|
|
@ -60,8 +60,8 @@ module QA
|
|||
|
||||
def remove_member(username)
|
||||
within_element(:member_row, text: username) do
|
||||
click_element :user_action_dropdown
|
||||
click_element :delete_member_dropdown_item
|
||||
click_element 'user-action-dropdown'
|
||||
click_element 'delete-member-dropdown-item'
|
||||
end
|
||||
|
||||
confirm_remove_member
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ module QA
|
|||
super
|
||||
|
||||
base.view 'app/views/projects/_commit_button.html.haml' do
|
||||
element :commit_button
|
||||
element 'commit-button'
|
||||
end
|
||||
end
|
||||
|
||||
def commit_changes
|
||||
click_element(:commit_button)
|
||||
click_element('commit-button')
|
||||
|
||||
wait_until(reload: false, max_duration: 60) do
|
||||
finished_loading?
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ module QA
|
|||
end
|
||||
|
||||
view 'app/assets/javascripts/super_sidebar/components/user_name_group.vue' do
|
||||
element :user_profile_link
|
||||
element 'user-profile-link'
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/super_sidebar/components/user_bar.vue' do
|
||||
|
|
@ -115,10 +115,10 @@ module QA
|
|||
return false unless has_personal_area?
|
||||
|
||||
within_user_menu do
|
||||
has_element?(:user_profile_link, text: /#{user.username}/)
|
||||
has_element?('user-profile-link', text: /#{user.username}/)
|
||||
end
|
||||
# we need to close user menu because plain user link check will leave it open
|
||||
click_element :user_avatar_content if has_element?(:user_profile_link, wait: 0)
|
||||
click_element :user_avatar_content if has_element?('user-profile-link', wait: 0)
|
||||
end
|
||||
|
||||
def not_signed_in?
|
||||
|
|
@ -159,7 +159,7 @@ module QA
|
|||
|
||||
def click_user_profile_link
|
||||
within_user_menu do
|
||||
click_element(:user_profile_link)
|
||||
click_element('user-profile-link')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -189,7 +189,7 @@ module QA
|
|||
|
||||
def within_user_menu(&block)
|
||||
within_element(:navbar) do
|
||||
click_element :user_avatar_content unless has_element?(:user_profile_link, wait: 1)
|
||||
click_element :user_avatar_content unless has_element?('user-profile-link', wait: 1)
|
||||
|
||||
within_element('user-dropdown', &block)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ module QA
|
|||
end
|
||||
|
||||
view 'app/assets/javascripts/repository/components/table/row.vue' do
|
||||
element :file_name_link
|
||||
element 'file-name-link'
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/repository/components/table/index.vue' do
|
||||
element :file_tree_table
|
||||
element 'file-tree-table'
|
||||
end
|
||||
|
||||
view 'app/views/layouts/header/_new_dropdown.html.haml' do
|
||||
|
|
@ -102,15 +102,15 @@ module QA
|
|||
end
|
||||
|
||||
def click_file(filename)
|
||||
within_element(:file_tree_table) do
|
||||
click_element(:file_name_link, text: filename)
|
||||
within_element('file-tree-table') do
|
||||
click_element('file-name-link', text: filename)
|
||||
end
|
||||
end
|
||||
|
||||
def click_commit(commit_msg)
|
||||
wait_for_requests
|
||||
|
||||
within_element(:file_tree_table) do
|
||||
within_element('file-tree-table') do
|
||||
click_on commit_msg
|
||||
end
|
||||
end
|
||||
|
|
@ -120,16 +120,16 @@ module QA
|
|||
end
|
||||
|
||||
def has_file?(name)
|
||||
return false unless has_element?(:file_tree_table)
|
||||
return false unless has_element?('file-tree-table')
|
||||
|
||||
within_element(:file_tree_table) do
|
||||
has_element?(:file_name_link, text: name)
|
||||
within_element('file-tree-table') do
|
||||
has_element?('file-name-link', text: name)
|
||||
end
|
||||
end
|
||||
|
||||
def has_no_file?(name)
|
||||
within_element(:file_tree_table) do
|
||||
has_no_element?(:file_name_link, text: name)
|
||||
within_element('file-tree-table') do
|
||||
has_no_element?('file-name-link', text: name)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module QA
|
|||
module User
|
||||
class Show < Page::Base
|
||||
view 'app/views/users/_follow_user.html.haml' do
|
||||
element :follow_user_link
|
||||
element 'follow-user-link'
|
||||
end
|
||||
|
||||
view 'app/views/shared/users/_user.html.haml' do
|
||||
|
|
@ -13,11 +13,11 @@ module QA
|
|||
end
|
||||
|
||||
view 'app/views/users/_overview.html.haml' do
|
||||
element :user_activity_content
|
||||
element 'user-activity-content'
|
||||
end
|
||||
|
||||
def click_follow_user_link
|
||||
click_element(:follow_user_link)
|
||||
click_element('follow-user-link')
|
||||
end
|
||||
|
||||
def click_following_tab
|
||||
|
|
@ -29,7 +29,7 @@ module QA
|
|||
end
|
||||
|
||||
def has_activity?(activity)
|
||||
within_element(:user_activity_content) do
|
||||
within_element('user-activity-content') do
|
||||
has_text?(activity)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ module QA
|
|||
Page::File::Show.perform(&:click_edit)
|
||||
|
||||
Page::File::Form.perform do |file_form|
|
||||
expect(file_form).to have_element(:commit_button)
|
||||
expect(file_form).to have_element('commit-button')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,17 +15,14 @@ module RuboCop
|
|||
--format RuboCop::Formatter::GracefulFormatter
|
||||
]
|
||||
|
||||
available_cops = RuboCop::Cop::Registry.global.to_h
|
||||
|
||||
cop_names, paths = args.partition { available_cops.key?(_1) }
|
||||
# Convert from Rake::TaskArguments into an Array to make `any?` work as expected.
|
||||
cop_names = args.to_a
|
||||
|
||||
if cop_names.any?
|
||||
list = cop_names.sort.join(',')
|
||||
options.concat ['--only', list]
|
||||
end
|
||||
|
||||
options.concat(paths)
|
||||
|
||||
puts <<~MSG
|
||||
Running RuboCop in graceful mode:
|
||||
rubocop #{options.join(' ')}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ RSpec.describe 'Pipeline Schedules', :js, feature_category: :continuous_integrat
|
|||
wait_for_requests
|
||||
end
|
||||
|
||||
describe 'The view' do
|
||||
describe 'the view' do
|
||||
it 'displays the required information description' do
|
||||
page.within('[data-testid="pipeline-schedule-table-row"]') do
|
||||
expect(page).to have_content('pipeline schedule')
|
||||
|
|
@ -280,36 +280,47 @@ RSpec.describe 'Pipeline Schedules', :js, feature_category: :continuous_integrat
|
|||
end
|
||||
end
|
||||
|
||||
context 'logged in as non-member' do
|
||||
before do
|
||||
gitlab_sign_in(user)
|
||||
end
|
||||
|
||||
shared_examples 'when not logged in' do
|
||||
describe 'GET /projects/pipeline_schedules' do
|
||||
before do
|
||||
visit_pipelines_schedules
|
||||
end
|
||||
|
||||
describe 'The view' do
|
||||
describe 'the view' do
|
||||
it 'does not show create schedule button' do
|
||||
visit_pipelines_schedules
|
||||
|
||||
expect(page).not_to have_link('New schedule')
|
||||
end
|
||||
|
||||
context 'when project is public' do
|
||||
let_it_be(:project) { create(:project, :repository, :public, public_builds: true) }
|
||||
|
||||
it 'shows Pipelines Schedules page' do
|
||||
visit_pipelines_schedules
|
||||
|
||||
expect(page).to have_link('New schedule')
|
||||
end
|
||||
|
||||
context 'when public pipelines are disabled' do
|
||||
before do
|
||||
project.update!(public_builds: false)
|
||||
visit_pipelines_schedules
|
||||
end
|
||||
|
||||
it 'shows Not Found page' do
|
||||
expect(page).to have_content('Page Not Found')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'not logged in' do
|
||||
describe 'GET /projects/pipeline_schedules' do
|
||||
before do
|
||||
visit_pipelines_schedules
|
||||
end
|
||||
it_behaves_like 'when not logged in'
|
||||
|
||||
describe 'The view' do
|
||||
it 'does not show create schedule button' do
|
||||
expect(page).not_to have_link('New schedule')
|
||||
end
|
||||
end
|
||||
context 'logged in as non-member' do
|
||||
before do
|
||||
gitlab_sign_in(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'when not logged in'
|
||||
end
|
||||
|
||||
def visit_new_pipeline_schedule
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ describe('Job Log Collapsible Section', () => {
|
|||
});
|
||||
|
||||
it('renders clickable header line', () => {
|
||||
expect(findLogLineHeader().text()).toBe('2 foo');
|
||||
expect(findLogLineHeader().text()).toBe('1 foo');
|
||||
expect(findLogLineHeader().attributes('role')).toBe('button');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ describe('Job Log Header Line', () => {
|
|||
style: 'term-fg-l-green',
|
||||
},
|
||||
],
|
||||
lineNumber: 76,
|
||||
lineNumber: 77,
|
||||
},
|
||||
isClosed: true,
|
||||
path: '/jashkenas/underscore/-/jobs/335',
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ describe('Job Log Line Number', () => {
|
|||
let wrapper;
|
||||
|
||||
const data = {
|
||||
lineNumber: 0,
|
||||
lineNumber: 1,
|
||||
path: '/jashkenas/underscore/-/jobs/335',
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ describe('Job Log Line', () => {
|
|||
offset: 24526,
|
||||
content: [{ text: 'job log content' }],
|
||||
section: 'custom-section',
|
||||
lineNumber: 76,
|
||||
lineNumber: 77,
|
||||
},
|
||||
path: '/root/ci-project/-/jobs/6353',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ describe('Job Log', () => {
|
|||
],
|
||||
section: 'prepare-executor',
|
||||
section_header: true,
|
||||
lineNumber: 2,
|
||||
lineNumber: 3,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ export const collapsibleSectionClosed = {
|
|||
offset: 80,
|
||||
content: [{ text: 'this is a collapsible nested section' }],
|
||||
section: 'prepare-script',
|
||||
lineNumber: 3,
|
||||
lineNumber: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -193,7 +193,7 @@ export const collapsibleSectionOpened = {
|
|||
offset: 80,
|
||||
content: [{ text: 'this is a collapsible nested section' }],
|
||||
section: 'prepare-script',
|
||||
lineNumber: 3,
|
||||
lineNumber: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ describe('Jobs Store Mutations', () => {
|
|||
{
|
||||
offset: 1,
|
||||
content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }],
|
||||
lineNumber: 0,
|
||||
lineNumber: 1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
@ -127,7 +127,7 @@ describe('Jobs Store Mutations', () => {
|
|||
{
|
||||
offset: 0,
|
||||
content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }],
|
||||
lineNumber: 0,
|
||||
lineNumber: 1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
addDurationToHeader,
|
||||
isCollapsibleSection,
|
||||
findOffsetAndRemove,
|
||||
getIncrementalLineNumber,
|
||||
getNextLineNumber,
|
||||
} from '~/ci/job_details/store/utils';
|
||||
import {
|
||||
mockJobLog,
|
||||
|
|
@ -192,14 +192,14 @@ describe('Jobs Store Utils', () => {
|
|||
|
||||
describe('regular line', () => {
|
||||
it('adds a lineNumber property with correct index', () => {
|
||||
expect(result[0].lineNumber).toEqual(0);
|
||||
expect(result[1].lineNumber).toEqual(1);
|
||||
expect(result[2].line.lineNumber).toEqual(2);
|
||||
expect(result[2].lines[0].lineNumber).toEqual(3);
|
||||
expect(result[2].lines[1].lineNumber).toEqual(4);
|
||||
expect(result[3].line.lineNumber).toEqual(5);
|
||||
expect(result[3].lines[0].lineNumber).toEqual(6);
|
||||
expect(result[3].lines[1].lineNumber).toEqual(7);
|
||||
expect(result[0].lineNumber).toEqual(1);
|
||||
expect(result[1].lineNumber).toEqual(2);
|
||||
expect(result[2].line.lineNumber).toEqual(3);
|
||||
expect(result[2].lines[0].lineNumber).toEqual(4);
|
||||
expect(result[2].lines[1].lineNumber).toEqual(5);
|
||||
expect(result[3].line.lineNumber).toEqual(6);
|
||||
expect(result[3].lines[0].lineNumber).toEqual(7);
|
||||
expect(result[3].lines[1].lineNumber).toEqual(8);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -326,17 +326,24 @@ describe('Jobs Store Utils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getIncrementalLineNumber', () => {
|
||||
describe('when last line is 0', () => {
|
||||
describe('getNextLineNumber', () => {
|
||||
describe('when there is no previous log', () => {
|
||||
it('returns 1', () => {
|
||||
expect(getNextLineNumber([])).toEqual(1);
|
||||
expect(getNextLineNumber(undefined)).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when last line is 1', () => {
|
||||
it('returns 1', () => {
|
||||
const log = [
|
||||
{
|
||||
content: [],
|
||||
lineNumber: 0,
|
||||
lineNumber: 1,
|
||||
},
|
||||
];
|
||||
|
||||
expect(getIncrementalLineNumber(log)).toEqual(1);
|
||||
expect(getNextLineNumber(log)).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -353,7 +360,7 @@ describe('Jobs Store Utils', () => {
|
|||
},
|
||||
];
|
||||
|
||||
expect(getIncrementalLineNumber(log)).toEqual(102);
|
||||
expect(getNextLineNumber(log)).toEqual(102);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -374,7 +381,7 @@ describe('Jobs Store Utils', () => {
|
|||
},
|
||||
];
|
||||
|
||||
expect(getIncrementalLineNumber(log)).toEqual(102);
|
||||
expect(getNextLineNumber(log)).toEqual(102);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -401,7 +408,7 @@ describe('Jobs Store Utils', () => {
|
|||
},
|
||||
];
|
||||
|
||||
expect(getIncrementalLineNumber(log)).toEqual(104);
|
||||
expect(getNextLineNumber(log)).toEqual(104);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -420,7 +427,7 @@ describe('Jobs Store Utils', () => {
|
|||
text: 'Downloading',
|
||||
},
|
||||
],
|
||||
lineNumber: 0,
|
||||
lineNumber: 1,
|
||||
},
|
||||
{
|
||||
offset: 2,
|
||||
|
|
@ -429,7 +436,7 @@ describe('Jobs Store Utils', () => {
|
|||
text: 'log line',
|
||||
},
|
||||
],
|
||||
lineNumber: 1,
|
||||
lineNumber: 2,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
@ -448,7 +455,7 @@ describe('Jobs Store Utils', () => {
|
|||
text: 'log line',
|
||||
},
|
||||
],
|
||||
lineNumber: 0,
|
||||
lineNumber: 1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
@ -472,7 +479,7 @@ describe('Jobs Store Utils', () => {
|
|||
},
|
||||
],
|
||||
section: 'section',
|
||||
lineNumber: 0,
|
||||
lineNumber: 1,
|
||||
},
|
||||
lines: [],
|
||||
},
|
||||
|
|
@ -498,7 +505,7 @@ describe('Jobs Store Utils', () => {
|
|||
},
|
||||
],
|
||||
section: 'section',
|
||||
lineNumber: 0,
|
||||
lineNumber: 1,
|
||||
},
|
||||
lines: [
|
||||
{
|
||||
|
|
@ -509,7 +516,7 @@ describe('Jobs Store Utils', () => {
|
|||
},
|
||||
],
|
||||
section: 'section',
|
||||
lineNumber: 1,
|
||||
lineNumber: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ exports[`Repository table row component renders a symlink table row 1`] = `
|
|||
>
|
||||
<a
|
||||
class="str-truncated tree-item-link"
|
||||
data-qa-selector="file_name_link"
|
||||
data-testid="file-name-link"
|
||||
href="https://test.com"
|
||||
title="test"
|
||||
>
|
||||
|
|
@ -65,7 +65,7 @@ exports[`Repository table row component renders table row 1`] = `
|
|||
>
|
||||
<a
|
||||
class="str-truncated tree-item-link"
|
||||
data-qa-selector="file_name_link"
|
||||
data-testid="file-name-link"
|
||||
href="https://test.com"
|
||||
title="test"
|
||||
>
|
||||
|
|
@ -121,7 +121,7 @@ exports[`Repository table row component renders table row for path with special
|
|||
>
|
||||
<a
|
||||
class="str-truncated tree-item-link"
|
||||
data-qa-selector="file_name_link"
|
||||
data-testid="file-name-link"
|
||||
href="https://test.com"
|
||||
title="test"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@ import WorkItemCommentLocked from '~/work_items/components/notes/work_item_comme
|
|||
import WorkItemCommentForm from '~/work_items/components/notes/work_item_comment_form.vue';
|
||||
import createNoteMutation from '~/work_items/graphql/notes/create_work_item_note.mutation.graphql';
|
||||
import { TRACKING_CATEGORY_SHOW } 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 {
|
||||
createWorkItemNoteResponse,
|
||||
groupWorkItemByIidResponseFactory,
|
||||
workItemByIidResponseFactory,
|
||||
workItemQueryResponse,
|
||||
} from '../../mock_data';
|
||||
|
|
@ -29,6 +31,7 @@ describe('Work item add note', () => {
|
|||
|
||||
const mutationSuccessHandler = jest.fn().mockResolvedValue(createWorkItemNoteResponse);
|
||||
let workItemResponseHandler;
|
||||
let groupWorkItemResponseHandler;
|
||||
|
||||
const findCommentForm = () => wrapper.findComponent(WorkItemCommentForm);
|
||||
const findTextarea = () => wrapper.findByTestId('note-reply-textarea');
|
||||
|
|
@ -40,27 +43,30 @@ describe('Work item add note', () => {
|
|||
canCreateNote = true,
|
||||
workItemIid = '1',
|
||||
workItemResponse = workItemByIidResponseFactory({ canUpdate, canCreateNote }),
|
||||
groupWorkItemResponse = groupWorkItemByIidResponseFactory({ canUpdate, canCreateNote }),
|
||||
signedIn = true,
|
||||
isEditing = true,
|
||||
isGroup = false,
|
||||
workItemType = 'Task',
|
||||
isInternalThread = false,
|
||||
} = {}) => {
|
||||
workItemResponseHandler = jest.fn().mockResolvedValue(workItemResponse);
|
||||
groupWorkItemResponseHandler = jest.fn().mockResolvedValue(groupWorkItemResponse);
|
||||
if (signedIn) {
|
||||
window.gon.current_user_id = '1';
|
||||
window.gon.current_user_avatar_url = 'avatar.png';
|
||||
}
|
||||
|
||||
const apolloProvider = createMockApollo([
|
||||
[workItemByIidQuery, workItemResponseHandler],
|
||||
[createNoteMutation, mutationHandler],
|
||||
]);
|
||||
|
||||
const { id } = workItemQueryResponse.data.workItem;
|
||||
wrapper = shallowMountExtended(WorkItemAddNote, {
|
||||
apolloProvider,
|
||||
apolloProvider: createMockApollo([
|
||||
[workItemByIidQuery, workItemResponseHandler],
|
||||
[groupWorkItemByIidQuery, groupWorkItemResponseHandler],
|
||||
[createNoteMutation, mutationHandler],
|
||||
]),
|
||||
provide: {
|
||||
fullPath: 'test-project-path',
|
||||
isGroup,
|
||||
},
|
||||
propsData: {
|
||||
workItemId: id,
|
||||
|
|
@ -272,16 +278,44 @@ describe('Work item add note', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('calls the work item query', async () => {
|
||||
await createComponent();
|
||||
describe('when project context', () => {
|
||||
it('calls the project work item query', async () => {
|
||||
await createComponent();
|
||||
|
||||
expect(workItemResponseHandler).toHaveBeenCalled();
|
||||
expect(workItemResponseHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the group work item query', async () => {
|
||||
await createComponent();
|
||||
|
||||
expect(groupWorkItemResponseHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the project work item query when missing workItemIid', async () => {
|
||||
await createComponent({ workItemIid: '', isEditing: false });
|
||||
|
||||
expect(workItemResponseHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('skips calling the work item query when missing workItemIid', async () => {
|
||||
await createComponent({ workItemIid: '', isEditing: false });
|
||||
describe('when group context', () => {
|
||||
it('skips calling the project work item query', async () => {
|
||||
await createComponent({ isGroup: true });
|
||||
|
||||
expect(workItemResponseHandler).not.toHaveBeenCalled();
|
||||
expect(workItemResponseHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the group work item query', async () => {
|
||||
await createComponent({ isGroup: true });
|
||||
|
||||
expect(groupWorkItemResponseHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the group work item query when missing workItemIid', async () => {
|
||||
await createComponent({ isGroup: true, workItemIid: '', isEditing: false });
|
||||
|
||||
expect(groupWorkItemResponseHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('wrapper adds `internal-note` class when internal thread', async () => {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ import NoteActions from '~/work_items/components/notes/work_item_note_actions.vu
|
|||
import WorkItemCommentForm from '~/work_items/components/notes/work_item_comment_form.vue';
|
||||
import updateWorkItemNoteMutation from '~/work_items/graphql/notes/update_work_item_note.mutation.graphql';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_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 {
|
||||
groupWorkItemByIidResponseFactory,
|
||||
mockAssignees,
|
||||
mockWorkItemCommentNote,
|
||||
updateWorkItemMutationResponse,
|
||||
|
|
@ -68,6 +70,9 @@ describe('Work Item Note', () => {
|
|||
});
|
||||
|
||||
const workItemResponseHandler = jest.fn().mockResolvedValue(workItemByIidResponseFactory());
|
||||
const groupWorkItemResponseHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(groupWorkItemByIidResponseFactory());
|
||||
const workItemByAuthoredByDifferentUser = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockWorkItemByDifferentUser);
|
||||
|
|
@ -90,6 +95,7 @@ describe('Work Item Note', () => {
|
|||
const createComponent = ({
|
||||
note = mockWorkItemCommentNote,
|
||||
isFirstNote = false,
|
||||
isGroup = false,
|
||||
updateNoteMutationHandler = successHandler,
|
||||
workItemId = mockWorkItemId,
|
||||
updateWorkItemMutationHandler = updateWorkItemMutationSuccessHandler,
|
||||
|
|
@ -99,6 +105,7 @@ describe('Work Item Note', () => {
|
|||
wrapper = shallowMount(WorkItemNote, {
|
||||
provide: {
|
||||
fullPath: 'test-project-path',
|
||||
isGroup,
|
||||
},
|
||||
propsData: {
|
||||
workItemId,
|
||||
|
|
@ -112,6 +119,7 @@ describe('Work Item Note', () => {
|
|||
},
|
||||
apolloProvider: mockApollo([
|
||||
[workItemByIidQuery, workItemByIidResponseHandler],
|
||||
[groupWorkItemByIidQuery, groupWorkItemResponseHandler],
|
||||
[updateWorkItemNoteMutation, updateNoteMutationHandler],
|
||||
[updateWorkItemMutation, updateWorkItemMutationHandler],
|
||||
]),
|
||||
|
|
@ -442,4 +450,32 @@ describe('Work Item Note', () => {
|
|||
expect(findAwardsList().props('workItemIid')).toBe('1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when project context', () => {
|
||||
it('calls the project work item query', () => {
|
||||
createComponent();
|
||||
|
||||
expect(workItemResponseHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the group work item query', () => {
|
||||
createComponent();
|
||||
|
||||
expect(groupWorkItemResponseHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when group context', () => {
|
||||
it('skips calling the project work item query', () => {
|
||||
createComponent({ isGroup: true });
|
||||
|
||||
expect(workItemResponseHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the group work item query', () => {
|
||||
createComponent({ isGroup: true });
|
||||
|
||||
expect(groupWorkItemResponseHandler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ describe('WorkItemActions component', () => {
|
|||
},
|
||||
provide: {
|
||||
fullPath: mockFullPath,
|
||||
isGroup: false,
|
||||
glFeatures: { workItemsMvc2: true },
|
||||
},
|
||||
mocks: {
|
||||
|
|
|
|||
|
|
@ -7,12 +7,18 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
import WorkItemCreatedUpdated from '~/work_items/components/work_item_created_updated.vue';
|
||||
import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
|
||||
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
|
||||
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 { workItemByIidResponseFactory, mockAssignees } from '../mock_data';
|
||||
import {
|
||||
groupWorkItemByIidResponseFactory,
|
||||
mockAssignees,
|
||||
workItemByIidResponseFactory,
|
||||
} from '../mock_data';
|
||||
|
||||
describe('WorkItemCreatedUpdated component', () => {
|
||||
let wrapper;
|
||||
let successHandler;
|
||||
let groupSuccessHandler;
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
|
|
@ -30,19 +36,26 @@ describe('WorkItemCreatedUpdated component', () => {
|
|||
updatedAt,
|
||||
confidential = false,
|
||||
updateInProgress = false,
|
||||
isGroup = false,
|
||||
} = {}) => {
|
||||
const workItemQueryResponse = workItemByIidResponseFactory({
|
||||
const workItemQueryResponse = workItemByIidResponseFactory({ author, updatedAt, confidential });
|
||||
const groupWorkItemQueryResponse = groupWorkItemByIidResponseFactory({
|
||||
author,
|
||||
updatedAt,
|
||||
confidential,
|
||||
});
|
||||
|
||||
successHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
|
||||
groupSuccessHandler = jest.fn().mockResolvedValue(groupWorkItemQueryResponse);
|
||||
|
||||
wrapper = shallowMount(WorkItemCreatedUpdated, {
|
||||
apolloProvider: createMockApollo([[workItemByIidQuery, successHandler]]),
|
||||
apolloProvider: createMockApollo([
|
||||
[workItemByIidQuery, successHandler],
|
||||
[groupWorkItemByIidQuery, groupSuccessHandler],
|
||||
]),
|
||||
provide: {
|
||||
fullPath: '/some/project',
|
||||
isGroup,
|
||||
},
|
||||
propsData: { workItemIid, updateInProgress },
|
||||
stubs: {
|
||||
|
|
@ -54,10 +67,44 @@ describe('WorkItemCreatedUpdated component', () => {
|
|||
await waitForPromises();
|
||||
};
|
||||
|
||||
it('skips the work item query when workItemIid is not defined', async () => {
|
||||
await createComponent({ workItemIid: null });
|
||||
describe('when project context', () => {
|
||||
it('calls the project work item query', async () => {
|
||||
await createComponent();
|
||||
|
||||
expect(successHandler).not.toHaveBeenCalled();
|
||||
expect(successHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the group work item query', async () => {
|
||||
await createComponent();
|
||||
|
||||
expect(groupSuccessHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the project work item query when workItemIid is not defined', async () => {
|
||||
await createComponent({ workItemIid: null });
|
||||
|
||||
expect(successHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when group context', () => {
|
||||
it('skips calling the project work item query', async () => {
|
||||
await createComponent({ isGroup: true });
|
||||
|
||||
expect(successHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the group work item query', async () => {
|
||||
await createComponent({ isGroup: true });
|
||||
|
||||
expect(groupSuccessHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the group work item query when workItemIid is not defined', async () => {
|
||||
await createComponent({ isGroup: true, workItemIid: null });
|
||||
|
||||
expect(groupSuccessHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows work item type metadata with type and icon', async () => {
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ import WorkItemDescription from '~/work_items/components/work_item_description.v
|
|||
import WorkItemDescriptionRendered from '~/work_items/components/work_item_description_rendered.vue';
|
||||
import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_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 { autocompleteDataSources, markdownPreviewPath } from '~/work_items/utils';
|
||||
import {
|
||||
groupWorkItemByIidResponseFactory,
|
||||
updateWorkItemMutationResponse,
|
||||
workItemByIidResponseFactory,
|
||||
workItemQueryResponse,
|
||||
|
|
@ -33,6 +35,7 @@ describe('WorkItemDescription', () => {
|
|||
|
||||
const mutationSuccessHandler = jest.fn().mockResolvedValue(updateWorkItemMutationResponse);
|
||||
let workItemResponseHandler;
|
||||
let groupWorkItemResponseHandler;
|
||||
|
||||
const findForm = () => wrapper.findComponent(GlForm);
|
||||
const findMarkdownEditor = () => wrapper.findComponent(MarkdownEditor);
|
||||
|
|
@ -51,14 +54,19 @@ describe('WorkItemDescription', () => {
|
|||
canUpdate = true,
|
||||
workItemResponse = workItemByIidResponseFactory({ canUpdate }),
|
||||
isEditing = false,
|
||||
isGroup = false,
|
||||
workItemIid = '1',
|
||||
} = {}) => {
|
||||
workItemResponseHandler = jest.fn().mockResolvedValue(workItemResponse);
|
||||
groupWorkItemResponseHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(groupWorkItemByIidResponseFactory({ canUpdate }));
|
||||
|
||||
const { id } = workItemQueryResponse.data.workItem;
|
||||
wrapper = shallowMount(WorkItemDescription, {
|
||||
apolloProvider: createMockApollo([
|
||||
[workItemByIidQuery, workItemResponseHandler],
|
||||
[groupWorkItemByIidQuery, groupWorkItemResponseHandler],
|
||||
[updateWorkItemMutation, mutationHandler],
|
||||
]),
|
||||
propsData: {
|
||||
|
|
@ -67,6 +75,7 @@ describe('WorkItemDescription', () => {
|
|||
},
|
||||
provide: {
|
||||
fullPath: 'test-project-path',
|
||||
isGroup,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -247,9 +256,31 @@ describe('WorkItemDescription', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('calls the work item query', async () => {
|
||||
await createComponent();
|
||||
describe('when project context', () => {
|
||||
it('calls the project work item query', () => {
|
||||
createComponent();
|
||||
|
||||
expect(workItemResponseHandler).toHaveBeenCalled();
|
||||
expect(workItemResponseHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the group work item query', () => {
|
||||
createComponent();
|
||||
|
||||
expect(groupWorkItemResponseHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when group context', () => {
|
||||
it('skips calling the project work item query', () => {
|
||||
createComponent({ isGroup: true });
|
||||
|
||||
expect(workItemResponseHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the group work item query', () => {
|
||||
createComponent({ isGroup: true });
|
||||
|
||||
expect(groupWorkItemResponseHandler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,12 +28,14 @@ import WorkItemStateToggleButton from '~/work_items/components/work_item_state_t
|
|||
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
|
||||
import WorkItemTodos from '~/work_items/components/work_item_todos.vue';
|
||||
import { i18n } 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 updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
|
||||
import updateWorkItemTaskMutation from '~/work_items/graphql/update_work_item_task.mutation.graphql';
|
||||
import workItemUpdatedSubscription from '~/work_items/graphql/work_item_updated.subscription.graphql';
|
||||
|
||||
import {
|
||||
groupWorkItemByIidResponseFactory,
|
||||
mockParent,
|
||||
workItemByIidResponseFactory,
|
||||
objectiveType,
|
||||
|
|
@ -49,6 +51,10 @@ describe('WorkItemDetail component', () => {
|
|||
Vue.use(VueApollo);
|
||||
|
||||
const workItemQueryResponse = workItemByIidResponseFactory({ canUpdate: true, canDelete: true });
|
||||
const groupWorkItemQueryResponse = groupWorkItemByIidResponseFactory({
|
||||
canUpdate: true,
|
||||
canDelete: true,
|
||||
});
|
||||
const workItemQueryResponseWithCannotUpdate = workItemByIidResponseFactory({
|
||||
canUpdate: false,
|
||||
canDelete: false,
|
||||
|
|
@ -59,6 +65,7 @@ describe('WorkItemDetail component', () => {
|
|||
canDelete: true,
|
||||
});
|
||||
const successHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
|
||||
const groupSuccessHandler = jest.fn().mockResolvedValue(groupWorkItemQueryResponse);
|
||||
const showModalHandler = jest.fn();
|
||||
const { id } = workItemQueryResponse.data.workspace.workItems.nodes[0];
|
||||
const workItemUpdatedSubscriptionHandler = jest
|
||||
|
|
@ -92,6 +99,7 @@ describe('WorkItemDetail component', () => {
|
|||
const findWorkItemTypeIcon = () => wrapper.findComponent(WorkItemTypeIcon);
|
||||
|
||||
const createComponent = ({
|
||||
isGroup = false,
|
||||
isModal = false,
|
||||
updateInProgress = false,
|
||||
workItemIid = '1',
|
||||
|
|
@ -101,14 +109,13 @@ describe('WorkItemDetail component', () => {
|
|||
workItemsMvc2Enabled = false,
|
||||
linkedWorkItemsEnabled = false,
|
||||
} = {}) => {
|
||||
const handlers = [
|
||||
[workItemByIidQuery, handler],
|
||||
[workItemUpdatedSubscription, workItemUpdatedSubscriptionHandler],
|
||||
confidentialityMock,
|
||||
];
|
||||
|
||||
wrapper = shallowMountExtended(WorkItemDetail, {
|
||||
apolloProvider: createMockApollo(handlers),
|
||||
apolloProvider: createMockApollo([
|
||||
[workItemByIidQuery, handler],
|
||||
[groupWorkItemByIidQuery, groupSuccessHandler],
|
||||
[workItemUpdatedSubscription, workItemUpdatedSubscriptionHandler],
|
||||
confidentialityMock,
|
||||
]),
|
||||
isLoggedIn: isLoggedIn(),
|
||||
propsData: {
|
||||
isModal,
|
||||
|
|
@ -131,6 +138,7 @@ describe('WorkItemDetail component', () => {
|
|||
hasIssuableHealthStatusFeature: true,
|
||||
projectNamespace: 'namespace',
|
||||
fullPath: 'group/project',
|
||||
isGroup,
|
||||
reportAbusePath: '/report/abuse/path',
|
||||
},
|
||||
stubs: {
|
||||
|
|
@ -484,25 +492,64 @@ describe('WorkItemDetail component', () => {
|
|||
expect(findAlert().text()).toBe(updateError);
|
||||
});
|
||||
|
||||
it('calls the work item query', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
describe('when project context', () => {
|
||||
it('calls the project work item query', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
||||
expect(successHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
|
||||
expect(successHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
|
||||
});
|
||||
|
||||
it('skips calling the group work item query', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
||||
expect(groupSuccessHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the project work item query when there is no workItemIid', async () => {
|
||||
createComponent({ workItemIid: null });
|
||||
await waitForPromises();
|
||||
|
||||
expect(successHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the project work item query when isModal=true', async () => {
|
||||
createComponent({ isModal: true });
|
||||
await waitForPromises();
|
||||
|
||||
expect(successHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
|
||||
});
|
||||
});
|
||||
|
||||
it('skips the work item query when there is no workItemIid', async () => {
|
||||
createComponent({ workItemIid: null });
|
||||
await waitForPromises();
|
||||
describe('when group context', () => {
|
||||
it('skips calling the project work item query', async () => {
|
||||
createComponent({ isGroup: true });
|
||||
await waitForPromises();
|
||||
|
||||
expect(successHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(successHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the work item query when isModal=true', async () => {
|
||||
createComponent({ isModal: true });
|
||||
await waitForPromises();
|
||||
it('calls the group work item query', async () => {
|
||||
createComponent({ isGroup: true });
|
||||
await waitForPromises();
|
||||
|
||||
expect(successHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
|
||||
expect(groupSuccessHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
|
||||
});
|
||||
|
||||
it('skips calling the group work item query when there is no workItemIid', async () => {
|
||||
createComponent({ isGroup: true, workItemIid: null });
|
||||
await waitForPromises();
|
||||
|
||||
expect(groupSuccessHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the group work item query when isModal=true', async () => {
|
||||
createComponent({ isGroup: true, isModal: true });
|
||||
await waitForPromises();
|
||||
|
||||
expect(groupSuccessHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('hierarchy widget', () => {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import { mountExtended } from 'helpers/vue_test_utils_helper';
|
|||
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
||||
import labelSearchQuery from '~/sidebar/components/labels/labels_select_widget/graphql/project_labels.query.graphql';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_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 WorkItemLabels from '~/work_items/components/work_item_labels.vue';
|
||||
import { i18n, I18N_WORK_ITEM_ERROR_FETCHING_LABELS } from '~/work_items/constants';
|
||||
import {
|
||||
groupWorkItemByIidResponseFactory,
|
||||
projectLabelsResponse,
|
||||
mockLabels,
|
||||
workItemByIidResponseFactory,
|
||||
|
|
@ -32,6 +34,9 @@ describe('WorkItemLabels component', () => {
|
|||
const workItemQuerySuccess = jest
|
||||
.fn()
|
||||
.mockResolvedValue(workItemByIidResponseFactory({ labels: null }));
|
||||
const groupWorkItemQuerySuccess = jest
|
||||
.fn()
|
||||
.mockResolvedValue(groupWorkItemByIidResponseFactory({ labels: null }));
|
||||
const successSearchQueryHandler = jest.fn().mockResolvedValue(projectLabelsResponse);
|
||||
const successUpdateWorkItemMutationHandler = jest
|
||||
.fn()
|
||||
|
|
@ -40,6 +45,7 @@ describe('WorkItemLabels component', () => {
|
|||
|
||||
const createComponent = ({
|
||||
canUpdate = true,
|
||||
isGroup = false,
|
||||
workItemQueryHandler = workItemQuerySuccess,
|
||||
searchQueryHandler = successSearchQueryHandler,
|
||||
updateWorkItemMutationHandler = successUpdateWorkItemMutationHandler,
|
||||
|
|
@ -48,11 +54,13 @@ describe('WorkItemLabels component', () => {
|
|||
wrapper = mountExtended(WorkItemLabels, {
|
||||
apolloProvider: createMockApollo([
|
||||
[workItemByIidQuery, workItemQueryHandler],
|
||||
[groupWorkItemByIidQuery, groupWorkItemQuerySuccess],
|
||||
[labelSearchQuery, searchQueryHandler],
|
||||
[updateWorkItemMutation, updateWorkItemMutationHandler],
|
||||
]),
|
||||
provide: {
|
||||
fullPath: 'test-project-path',
|
||||
isGroup,
|
||||
},
|
||||
propsData: {
|
||||
workItemId,
|
||||
|
|
@ -244,17 +252,49 @@ describe('WorkItemLabels component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('calls the work item query', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
describe('when project context', () => {
|
||||
it('calls the project work item query', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
||||
expect(workItemQuerySuccess).toHaveBeenCalled();
|
||||
expect(workItemQuerySuccess).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the group work item query', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
||||
expect(groupWorkItemQuerySuccess).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the project work item query when missing workItemIid', async () => {
|
||||
createComponent({ workItemIid: '' });
|
||||
await waitForPromises();
|
||||
|
||||
expect(workItemQuerySuccess).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('skips calling the work item query when missing workItemIid', async () => {
|
||||
createComponent({ workItemIid: '' });
|
||||
await waitForPromises();
|
||||
describe('when group context', () => {
|
||||
it('skips calling the project work item query', async () => {
|
||||
createComponent({ isGroup: true });
|
||||
await waitForPromises();
|
||||
|
||||
expect(workItemQuerySuccess).not.toHaveBeenCalled();
|
||||
expect(workItemQuerySuccess).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the group work item query', async () => {
|
||||
createComponent({ isGroup: true });
|
||||
await waitForPromises();
|
||||
|
||||
expect(groupWorkItemQuerySuccess).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the group work item query when missing workItemIid', async () => {
|
||||
createComponent({ isGroup: true, workItemIid: '' });
|
||||
await waitForPromises();
|
||||
|
||||
expect(groupWorkItemQuerySuccess).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ describe('WorkItemChildrenWrapper', () => {
|
|||
apolloProvider: mockApollo,
|
||||
provide: {
|
||||
fullPath: 'test/project',
|
||||
isGroup: false,
|
||||
},
|
||||
propsData: {
|
||||
workItemType,
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ describe('WorkItemLinksForm', () => {
|
|||
provide: {
|
||||
fullPath: 'project/path',
|
||||
hasIterationsFeature,
|
||||
isGroup: false,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ 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 {
|
||||
getIssueDetailsResponse,
|
||||
groupWorkItemByIidResponseFactory,
|
||||
workItemHierarchyResponse,
|
||||
workItemHierarchyEmptyResponse,
|
||||
workItemHierarchyNoUpdatePermissionResponse,
|
||||
|
|
@ -32,6 +34,9 @@ describe('WorkItemLinks', () => {
|
|||
let mockApollo;
|
||||
|
||||
const responseWithAddChildPermission = jest.fn().mockResolvedValue(workItemHierarchyResponse);
|
||||
const groupResponseWithAddChildPermission = jest
|
||||
.fn()
|
||||
.mockResolvedValue(groupWorkItemByIidResponseFactory());
|
||||
const responseWithoutAddChildPermission = jest
|
||||
.fn()
|
||||
.mockResolvedValue(workItemByIidResponseFactory({ adminParentLink: false }));
|
||||
|
|
@ -40,10 +45,12 @@ describe('WorkItemLinks', () => {
|
|||
fetchHandler = responseWithAddChildPermission,
|
||||
issueDetailsQueryHandler = jest.fn().mockResolvedValue(getIssueDetailsResponse()),
|
||||
hasIterationsFeature = false,
|
||||
isGroup = false,
|
||||
} = {}) => {
|
||||
mockApollo = createMockApollo(
|
||||
[
|
||||
[workItemByIidQuery, fetchHandler],
|
||||
[groupWorkItemByIidQuery, groupResponseWithAddChildPermission],
|
||||
[issueDetailsQuery, issueDetailsQueryHandler],
|
||||
],
|
||||
resolvers,
|
||||
|
|
@ -54,6 +61,7 @@ describe('WorkItemLinks', () => {
|
|||
provide: {
|
||||
fullPath: 'project/path',
|
||||
hasIterationsFeature,
|
||||
isGroup,
|
||||
reportAbusePath: '/report/abuse/path',
|
||||
},
|
||||
propsData: {
|
||||
|
|
@ -243,4 +251,32 @@ describe('WorkItemLinks', () => {
|
|||
expect(findAbuseCategorySelector().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when project context', () => {
|
||||
it('calls the project work item query', () => {
|
||||
createComponent();
|
||||
|
||||
expect(responseWithAddChildPermission).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the group work item query', () => {
|
||||
createComponent();
|
||||
|
||||
expect(groupResponseWithAddChildPermission).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@ import WidgetWrapper from '~/work_items/components/widget_wrapper.vue';
|
|||
import WorkItemRelationships from '~/work_items/components/work_item_relationships/work_item_relationships.vue';
|
||||
import WorkItemRelationshipList from '~/work_items/components/work_item_relationships/work_item_relationship_list.vue';
|
||||
import WorkItemAddRelationshipForm from '~/work_items/components/work_item_relationships/work_item_add_relationship_form.vue';
|
||||
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 {
|
||||
groupWorkItemByIidResponseFactory,
|
||||
workItemByIidResponseFactory,
|
||||
mockLinkedItems,
|
||||
mockBlockingLinkedItem,
|
||||
|
|
@ -25,21 +27,29 @@ describe('WorkItemRelationships', () => {
|
|||
const emptyLinkedWorkItemsQueryHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(workItemByIidResponseFactory());
|
||||
const groupWorkItemsQueryHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(groupWorkItemByIidResponseFactory());
|
||||
|
||||
const createComponent = async ({
|
||||
workItemQueryHandler = emptyLinkedWorkItemsQueryHandler,
|
||||
workItemType = 'Task',
|
||||
isGroup = false,
|
||||
} = {}) => {
|
||||
const mockApollo = createMockApollo([[workItemByIidQuery, workItemQueryHandler]]);
|
||||
|
||||
wrapper = shallowMountExtended(WorkItemRelationships, {
|
||||
apolloProvider: mockApollo,
|
||||
apolloProvider: createMockApollo([
|
||||
[workItemByIidQuery, workItemQueryHandler],
|
||||
[groupWorkItemByIidQuery, groupWorkItemsQueryHandler],
|
||||
]),
|
||||
propsData: {
|
||||
workItemId: 'gid://gitlab/WorkItem/1',
|
||||
workItemIid: '1',
|
||||
workItemFullPath: 'test-project-path',
|
||||
workItemType,
|
||||
},
|
||||
provide: {
|
||||
isGroup,
|
||||
},
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
|
@ -120,4 +130,32 @@ describe('WorkItemRelationships', () => {
|
|||
await findWorkItemRelationshipForm().vm.$emit('cancel');
|
||||
expect(findWorkItemRelationshipForm().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('when project context', () => {
|
||||
it('calls the project work item query', () => {
|
||||
createComponent();
|
||||
|
||||
expect(emptyLinkedWorkItemsQueryHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips calling the group work item query', () => {
|
||||
createComponent();
|
||||
|
||||
expect(groupWorkItemsQueryHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when group context', () => {
|
||||
it('skips calling the project work item query', () => {
|
||||
createComponent({ isGroup: true });
|
||||
|
||||
expect(emptyLinkedWorkItemsQueryHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the group work item query', () => {
|
||||
createComponent({ isGroup: true });
|
||||
|
||||
expect(groupWorkItemsQueryHandler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ describe('WorkItemTodo component', () => {
|
|||
workItemFullpath: mockWorkItemFullpath,
|
||||
currentUserTodos,
|
||||
},
|
||||
provide: {
|
||||
isGroup: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ describe('work items graphql cache utils', () => {
|
|||
title: 'New child',
|
||||
};
|
||||
|
||||
addHierarchyChild(mockCache, fullPath, iid, child);
|
||||
addHierarchyChild({ cache: mockCache, fullPath, iid, workItem: child });
|
||||
|
||||
expect(mockCache.writeQuery).toHaveBeenCalledWith({
|
||||
query: workItemByIidQuery,
|
||||
|
|
@ -88,7 +88,7 @@ describe('work items graphql cache utils', () => {
|
|||
title: 'New child',
|
||||
};
|
||||
|
||||
addHierarchyChild(mockCache, fullPath, iid, child);
|
||||
addHierarchyChild({ cache: mockCache, fullPath, iid, workItem: child });
|
||||
|
||||
expect(mockCache.writeQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
@ -106,7 +106,7 @@ describe('work items graphql cache utils', () => {
|
|||
title: 'Child',
|
||||
};
|
||||
|
||||
removeHierarchyChild(mockCache, fullPath, iid, childToRemove);
|
||||
removeHierarchyChild({ cache: mockCache, fullPath, iid, workItem: childToRemove });
|
||||
|
||||
expect(mockCache.writeQuery).toHaveBeenCalledWith({
|
||||
query: workItemByIidQuery,
|
||||
|
|
@ -145,7 +145,7 @@ describe('work items graphql cache utils', () => {
|
|||
title: 'Child',
|
||||
};
|
||||
|
||||
removeHierarchyChild(mockCache, fullPath, iid, childToRemove);
|
||||
removeHierarchyChild({ cache: mockCache, fullPath, iid, workItem: childToRemove });
|
||||
|
||||
expect(mockCache.writeQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -833,6 +833,21 @@ export const workItemByIidResponseFactory = (options) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const groupWorkItemByIidResponseFactory = (options) => {
|
||||
const response = workItemResponseFactory(options);
|
||||
return {
|
||||
data: {
|
||||
workspace: {
|
||||
__typename: 'Group',
|
||||
id: 'gid://gitlab/Group/1',
|
||||
workItems: {
|
||||
nodes: [response.data.workItem],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const updateWorkItemMutationResponseFactory = (options) => {
|
||||
const response = workItemResponseFactory(options);
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ describe('Create work item component', () => {
|
|||
},
|
||||
provide: {
|
||||
fullPath: 'full-path',
|
||||
isGroup: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ describe('Work items router', () => {
|
|||
router,
|
||||
provide: {
|
||||
fullPath: 'full-path',
|
||||
isGroup: false,
|
||||
issuesListPath: 'full-path/-/issues',
|
||||
hasIssueWeightsFeature: false,
|
||||
hasIterationsFeature: false,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WikiHelper do
|
||||
RSpec.describe WikiHelper, feature_category: :wiki do
|
||||
describe '#wiki_page_title' do
|
||||
let_it_be(:page) { create(:wiki_page) }
|
||||
|
||||
|
|
@ -75,38 +75,42 @@ RSpec.describe WikiHelper do
|
|||
|
||||
describe '#wiki_sort_controls' do
|
||||
let(:wiki) { create(:project_wiki) }
|
||||
let(:wiki_link) { helper.wiki_sort_controls(wiki, direction) }
|
||||
let(:classes) { "gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn rspec-reverse-sort" }
|
||||
|
||||
def expected_link(direction, icon_class)
|
||||
before do
|
||||
allow(Pajamas::ButtonComponent).to receive(:new).and_call_original
|
||||
end
|
||||
|
||||
def expected_link_args(direction, icon_class)
|
||||
path = "/#{wiki.project.full_path}/-/wikis/pages?direction=#{direction}"
|
||||
title = direction == 'desc' ? _('Sort direction: Ascending') : _('Sort direction: Descending')
|
||||
helper.link_to(path, type: 'button', class: classes, title: title) do
|
||||
helper.sprite_icon("sort-#{icon_class}")
|
||||
{
|
||||
href: path,
|
||||
icon: "sort-#{icon_class}",
|
||||
button_options: hash_including(title: title)
|
||||
}
|
||||
end
|
||||
|
||||
context 'when initially rendering' do
|
||||
it 'uses default values' do
|
||||
helper.wiki_sort_controls(wiki, nil)
|
||||
|
||||
expect(Pajamas::ButtonComponent).to have_received(:new).with(expected_link_args('desc', 'lowest'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'initial call' do
|
||||
let(:direction) { nil }
|
||||
|
||||
it 'renders with default values' do
|
||||
expect(wiki_link).to eq(expected_link('desc', 'lowest'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'sort by asc order' do
|
||||
let(:direction) { 'asc' }
|
||||
|
||||
context 'when the current sort order is ascending' do
|
||||
it 'renders a link with opposite direction' do
|
||||
expect(wiki_link).to eq(expected_link('desc', 'lowest'))
|
||||
helper.wiki_sort_controls(wiki, 'asc')
|
||||
|
||||
expect(Pajamas::ButtonComponent).to have_received(:new).with(expected_link_args('desc', 'lowest'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'sort by desc order' do
|
||||
let(:direction) { 'desc' }
|
||||
|
||||
context 'when the current sort order is descending' do
|
||||
it 'renders a link with opposite direction' do
|
||||
expect(wiki_link).to eq(expected_link('asc', 'highest'))
|
||||
helper.wiki_sort_controls(wiki, 'desc')
|
||||
|
||||
expect(Pajamas::ButtonComponent).to have_received(:new).with(expected_link_args('asc', 'highest'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Common::Transformers::UserReferenceTransformer do
|
||||
describe '#transform' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:bulk_import) { create(:bulk_import) }
|
||||
let_it_be(:entity) { create(:bulk_import_entity, bulk_import: bulk_import, group: group) }
|
||||
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
|
||||
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
|
||||
|
||||
let(:hash) do
|
||||
{
|
||||
'user' => {
|
||||
'public_email' => email
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
group.add_developer(user)
|
||||
end
|
||||
|
||||
shared_examples 'sets user_id and removes user key' do
|
||||
it 'sets found user_id and removes user key' do
|
||||
transformed_hash = subject.transform(context, hash)
|
||||
|
||||
expect(transformed_hash['user']).to be_nil
|
||||
expect(transformed_hash['user_id']).to eq(user.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can be found by email' do
|
||||
let(:email) { user.email }
|
||||
|
||||
include_examples 'sets user_id and removes user key'
|
||||
end
|
||||
|
||||
context 'when user cannot be found by email' do
|
||||
let(:user) { bulk_import.user }
|
||||
let(:email) { nil }
|
||||
|
||||
include_examples 'sets user_id and removes user key'
|
||||
end
|
||||
|
||||
context 'when there is no data to transform' do
|
||||
it 'returns' do
|
||||
expect(subject.transform(nil, nil)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when custom reference is provided' do
|
||||
shared_examples 'updates provided reference' do |reference|
|
||||
let(:hash) do
|
||||
{
|
||||
'author' => {
|
||||
'public_email' => user.email
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates provided reference' do
|
||||
transformer = described_class.new(reference: reference)
|
||||
result = transformer.transform(context, hash)
|
||||
|
||||
expect(result['author']).to be_nil
|
||||
expect(result['author_id']).to eq(user.id)
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'updates provided reference', 'author'
|
||||
include_examples 'updates provided reference', :author
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Prometheus::QueryVariables do
|
||||
describe '.call' do
|
||||
let_it_be_with_refind(:environment) { create(:environment) }
|
||||
|
||||
let(:project) { environment.project }
|
||||
let(:slug) { environment.slug }
|
||||
let(:params) { {} }
|
||||
|
||||
subject { described_class.call(environment, **params) }
|
||||
|
||||
it { is_expected.to include(ci_environment_slug: slug) }
|
||||
it { is_expected.to include(ci_project_name: project.name) }
|
||||
it { is_expected.to include(ci_project_namespace: project.namespace.name) }
|
||||
it { is_expected.to include(ci_project_path: project.full_path) }
|
||||
it { is_expected.to include(ci_environment_name: environment.name) }
|
||||
|
||||
it do
|
||||
is_expected.to include(environment_filter:
|
||||
%[container_name!="POD",environment="#{slug}"])
|
||||
end
|
||||
|
||||
context 'without deployment platform' do
|
||||
it { is_expected.to include(kube_namespace: '') }
|
||||
end
|
||||
|
||||
context 'with deployment platform' do
|
||||
context 'with project cluster' do
|
||||
let(:kube_namespace) { environment.deployment_namespace }
|
||||
|
||||
before do
|
||||
create(:cluster, :project, :provided_by_user, projects: [project])
|
||||
end
|
||||
|
||||
it { is_expected.to include(kube_namespace: kube_namespace) }
|
||||
end
|
||||
|
||||
context 'with group cluster' do
|
||||
let(:cluster) { create(:cluster, :group, :provided_by_user, groups: [group]) }
|
||||
let(:group) { create(:group) }
|
||||
let(:project2) { create(:project) }
|
||||
let(:kube_namespace) { k8s_ns.namespace }
|
||||
|
||||
let!(:k8s_ns) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project, environment: environment) }
|
||||
let!(:k8s_ns2) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project2, environment: environment) }
|
||||
|
||||
before do
|
||||
group.projects << project
|
||||
group.projects << project2
|
||||
end
|
||||
|
||||
it { is_expected.to include(kube_namespace: kube_namespace) }
|
||||
end
|
||||
end
|
||||
|
||||
context '__range' do
|
||||
context 'when start_time and end_time are present' do
|
||||
let(:params) do
|
||||
{
|
||||
start_time: Time.rfc3339('2020-05-29T07:23:05.008Z'),
|
||||
end_time: Time.rfc3339('2020-05-29T15:23:05.008Z')
|
||||
}
|
||||
end
|
||||
|
||||
it { is_expected.to include(__range: "#{8.hours.to_i}s") }
|
||||
end
|
||||
|
||||
context 'when start_time and end_time are not present' do
|
||||
it { is_expected.to include(__range: nil) }
|
||||
end
|
||||
|
||||
context 'when end_time is not present' do
|
||||
let(:params) do
|
||||
{
|
||||
start_time: Time.rfc3339('2020-05-29T07:23:05.008Z')
|
||||
}
|
||||
end
|
||||
|
||||
it { is_expected.to include(__range: nil) }
|
||||
end
|
||||
|
||||
context 'when start_time is not present' do
|
||||
let(:params) do
|
||||
{
|
||||
end_time: Time.rfc3339('2020-05-29T07:23:05.008Z')
|
||||
}
|
||||
end
|
||||
|
||||
it { is_expected.to include(__range: nil) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue