Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-06-04 15:23:49 +00:00
parent 15dbf53ff6
commit 2b0f233d35
360 changed files with 2113 additions and 1324 deletions

View File

@ -1013,6 +1013,11 @@ QA/SelectorUsage:
Exclude:
- 'spec/rubocop/**/*_spec.rb'
QA/FeatureFlags:
Enabled: true
Include:
- 'qa/qa/specs/features/**/*.rb'
Performance/ActiveRecordSubtransactions:
Exclude:
- 'spec/**/*.rb'

View File

@ -154,19 +154,6 @@ Layout/ArgumentAlignment:
- 'ee/app/services/external_status_checks/create_service.rb'
- 'ee/app/services/external_status_checks/destroy_service.rb'
- 'ee/app/services/external_status_checks/update_service.rb'
- 'lib/api/admin/plan_limits.rb'
- 'lib/api/alert_management_alerts.rb'
- 'lib/api/api.rb'
- 'lib/api/applications.rb'
- 'lib/api/branches.rb'
- 'lib/api/bulk_imports.rb'
- 'lib/api/ci/job_artifacts.rb'
- 'lib/api/ci/jobs.rb'
- 'lib/api/ci/pipeline_schedules.rb'
- 'lib/api/ci/pipelines.rb'
- 'lib/api/ci/resource_groups.rb'
- 'lib/api/ci/runner.rb'
- 'lib/api/ci/runners.rb'
- 'lib/api/entities/npm_package.rb'
- 'lib/api/entities/nuget/dependency_group.rb'
- 'lib/api/entities/nuget/package_metadata.rb'

View File

@ -1 +1 @@
55197564f4f57d8049d351f5c4614bc9449c2a72
fb36b2bea3817c3fd4f58804699e6e6830a0e578

View File

@ -129,7 +129,7 @@ export default {
label-position="left"
aria-describedby="board-labels-toggle-text"
data-testid="show-labels-toggle"
class="gl-flex-direction-row gl-justify-between gl-w-full"
class="board-dropdown-toggle gl-flex-direction-row gl-justify-between gl-w-full"
/>
</template>
</gl-disclosure-dropdown-item>

View File

@ -7,8 +7,20 @@ import isShowingLabelsQuery from '~/graphql_shared/client/is_showing_labels.quer
import getIssueStateQuery from '~/issues/show/queries/get_issue_state.query.graphql';
import createDefaultClient from '~/lib/graphql';
import typeDefs from '~/work_items/graphql/typedefs.graphql';
import { WIDGET_TYPE_NOTES, WIDGET_TYPE_AWARD_EMOJI } from '~/work_items/constants';
import { findWidget } from '~/issues/list/utils';
import { newWorkItemFullPath } from '~/work_items/utils';
import {
WIDGET_TYPE_NOTES,
WIDGET_TYPE_AWARD_EMOJI,
NEW_WORK_ITEM_IID,
WIDGET_TYPE_HEALTH_STATUS,
WIDGET_TYPE_ASSIGNEES,
WIDGET_TYPE_COLOR,
WIDGET_TYPE_DESCRIPTION,
} from '~/work_items/constants';
import activeBoardItemQuery from 'ee_else_ce/boards/graphql/client/active_board_item.query.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';
export const config = {
typeDefs,
@ -272,6 +284,85 @@ export const resolvers = {
});
return isShowingLabels;
},
updateNewWorkItem(_, { input }, { cache }) {
const {
healthStatus,
isGroup,
fullPath,
assignees,
color,
title,
description,
confidential,
} = input;
const query = isGroup ? groupWorkItemByIidQuery : workItemByIidQuery;
const variables = {
fullPath: newWorkItemFullPath(fullPath),
iid: NEW_WORK_ITEM_IID,
};
cache.updateQuery({ query, variables }, (sourceData) =>
produce(sourceData, (draftData) => {
if (healthStatus) {
const healthStatusWidget = findWidget(
WIDGET_TYPE_HEALTH_STATUS,
draftData?.workspace?.workItem,
);
healthStatusWidget.healthStatus = healthStatus;
const healthStatusWidgetIndex = draftData.workspace.workItem.widgets.findIndex(
(widget) => widget.type === WIDGET_TYPE_HEALTH_STATUS,
);
draftData.workspace.workItem.widgets[healthStatusWidgetIndex] = healthStatusWidget;
}
if (assignees) {
const assigneesWidget = findWidget(
WIDGET_TYPE_ASSIGNEES,
draftData?.workspace?.workItem,
);
assigneesWidget.assignees.nodes = assignees;
const assigneesWidgetIndex = draftData.workspace.workItem.widgets.findIndex(
(widget) => widget.type === WIDGET_TYPE_ASSIGNEES,
);
draftData.workspace.workItem.widgets[assigneesWidgetIndex] = assigneesWidget;
}
if (color) {
const colorWidget = findWidget(WIDGET_TYPE_COLOR, draftData?.workspace?.workItem);
colorWidget.color = color;
const colorWidgetIndex = draftData.workspace.workItem.widgets.findIndex(
(widget) => widget.type === WIDGET_TYPE_COLOR,
);
draftData.workspace.workItem.widgets[colorWidgetIndex] = colorWidget;
}
if (title) {
draftData.workspace.workItem.title = title;
}
if (description) {
const descriptionWidget = findWidget(
WIDGET_TYPE_DESCRIPTION,
draftData?.workspace?.workItem,
);
descriptionWidget.description = description;
const descriptionWidgetIndex = draftData.workspace.workItem.widgets.findIndex(
(widget) => widget.type === WIDGET_TYPE_DESCRIPTION,
);
draftData.workspace.workItem.widgets[descriptionWidgetIndex] = descriptionWidget;
}
if (confidential !== undefined) {
draftData.workspace.workItem.confidential = confidential;
}
}),
);
},
},
};

View File

@ -130,6 +130,7 @@ const CrmOrganizationToken = () =>
const DateToken = () => import('~/vue_shared/components/filtered_search_bar/tokens/date_token.vue');
export default {
name: 'IssuesListAppCE',
i18n,
issuableListTabs,
ISSUES_VIEW_TYPE_KEY,

View File

@ -2,12 +2,24 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import VueRouter from 'vue-router';
import IssuesListApp from 'ee_else_ce/issues/list/components/issues_list_app.vue';
import { resolvers, config } from '~/graphql_shared/issuable_client';
import createDefaultClient, { createApolloClientWithCaching } from '~/lib/graphql';
import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsWorkItems from '~/behaviors/shortcuts/shortcuts_work_items';
import { parseBoolean } from '~/lib/utils/common_utils';
import JiraIssuesImportStatusApp from './components/jira_issues_import_status_app.vue';
import { gqlClient } from './graphql';
let issuesClient;
export async function issuesListClient() {
if (issuesClient) return issuesClient;
issuesClient = gon.features?.frontendCaching
? await createApolloClientWithCaching(resolvers, { localCacheKey: 'issues_list', ...config })
: createDefaultClient(resolvers, config);
return issuesClient;
}
export async function mountJiraIssuesListApp() {
const el = document.querySelector('.js-jira-issues-import-status-root');
@ -109,7 +121,7 @@ export async function mountIssuesListApp() {
el,
name: 'IssuesListRoot',
apolloProvider: new VueApollo({
defaultClient: await gqlClient(),
defaultClient: await issuesListClient(),
}),
router: new VueRouter({
base: window.location.pathname,

View File

@ -423,7 +423,7 @@ export const convertToSearchQuery = (filterTokens) =>
.map((token) => token.value.data)
.join(' ') || undefined;
function findWidget(type, workItem) {
export function findWidget(type, workItem) {
return workItem?.widgets?.find((widget) => widget.type === type);
}

View File

@ -54,7 +54,7 @@ export default {
<template #list-item="{ item }">
<div
class="gl-line-clamp-2"
:class="{ 'gl-font-weight-bold': item.memberRoleId }"
:class="{ 'gl-font-bold': item.memberRoleId }"
data-testid="role-name"
>
{{ item.text }}

View File

@ -49,6 +49,7 @@ export const GLOBAL_COMMANDS_GROUP_TITLE = s__('CommandPalette|Global Commands')
export const USERS_GROUP_TITLE = s__('GlobalSearch|Users');
export const PAGES_GROUP_TITLE = s__('CommandPalette|Pages');
export const PROJECTS_GROUP_TITLE = s__('GlobalSearch|Projects');
export const GROUPS_GROUP_TITLE = s__('GlobalSearch|Groups');
export const ISSUES_GROUP_TITLE = s__('GlobalSearch|Issues');
export const PATH_GROUP_TITLE = s__('CommandPalette|Project files');

View File

@ -19,6 +19,7 @@ import {
PROJECTS_GROUP_TITLE,
ISSUES_GROUP_TITLE,
PAGES_GROUP_TITLE,
GROUPS_GROUP_TITLE,
} from '../command_palette/constants';
import SearchResultHoverLayover from './global_search_hover_overlay.vue';
import GlobalSearchNoResults from './global_search_no_results.vue';
@ -38,6 +39,7 @@ export default {
PROJECTS_GROUP_TITLE,
ISSUES_GROUP_TITLE,
PAGES_GROUP_TITLE,
GROUPS_GROUP_TITLE,
},
components: {
GlAvatar,
@ -103,13 +105,18 @@ export default {
},
trackingTypes({ category }) {
switch (category) {
case this.$options.i18n.GROUPS_GROUP_TITLE: {
this.trackEvent('click_group_result_in_command_palette');
break;
}
case this.$options.i18n.PROJECTS_GROUP_TITLE: {
this.trackEvent('click_project_result_in_command_palette');
break;
}
default: {
this.trackEvent('click_user_result_in_command_palette');
/* empty */
}
}
},

View File

@ -26,7 +26,7 @@ export default {
editStatus: s__('SetStatusModal|Edit status'),
editProfile: s__('CurrentUser|Edit profile'),
preferences: s__('CurrentUser|Preferences'),
buyPipelineMinutes: s__('CurrentUser|Buy Pipeline minutes'),
buyPipelineMinutes: s__('CurrentUser|Buy compute minutes'),
oneOfGroupsRunningOutOfPipelineMinutes: s__('CurrentUser|One of your groups is running out'),
gitlabNext: s__('CurrentUser|Switch to GitLab Next'),
startTrial: s__('CurrentUser|Start an Ultimate trial'),

View File

@ -2,7 +2,7 @@ import Vue from 'vue';
import { GlBreadcrumb, GlToast } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import createDefaultClient from '~/lib/graphql';
import { apolloProvider } from '~/graphql_shared/issuable_client';
import { staticBreadcrumbs } from '~/lib/utils/breadcrumbs';
import { JS_TOGGLE_EXPAND_CLASS, CONTEXT_NAMESPACE_GROUPS } from './constants';
import createStore from './components/global_search/store';
@ -16,10 +16,6 @@ import SuperSidebarToggle from './components/super_sidebar_toggle.vue';
Vue.use(GlToast);
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
const getTrialStatusWidgetData = (sidebarData) => {
if (sidebarData.trial_status_widget_data_attrs && sidebarData.trial_status_popover_data_attrs) {
const {
@ -135,6 +131,7 @@ export const initSuperSidebar = () => {
projectsPath,
groupsPath,
fullPath: sidebarData.work_items?.full_path,
hasIssuableHealthStatusFeature: sidebarData.work_items?.has_issuable_health_status_feature,
isGroup,
},
store: createStore({

View File

@ -7,24 +7,39 @@ import {
GlFormGroup,
GlFormSelect,
} from '@gitlab/ui';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { TYPENAME_PROJECT } from '~/graphql_shared/constants';
import { getPreferredLocales, s__ } from '~/locale';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { fetchPolicies } from '~/lib/graphql';
import { setNewWorkItemCache } from '~/work_items/graphql/cache_utils';
import { findWidget } from '~/issues/list/utils';
import { newWorkItemFullPath } from '~/work_items/utils';
import {
I18N_WORK_ITEM_CREATE_BUTTON_LABEL,
I18N_WORK_ITEM_ERROR_CREATING,
I18N_WORK_ITEM_ERROR_FETCHING_TYPES,
sprintfWorkItem,
i18n,
WIDGET_TYPE_ASSIGNEES,
WIDGET_TYPE_COLOR,
NEW_WORK_ITEM_IID,
WIDGET_TYPE_HEALTH_STATUS,
WIDGET_TYPE_PARTICIPANTS,
WIDGET_TYPE_DESCRIPTION,
NEW_WORK_ITEM_GID,
} from '../constants';
import createWorkItemMutation from '../graphql/create_work_item.mutation.graphql';
import groupWorkItemTypesQuery from '../graphql/group_work_item_types.query.graphql';
import projectWorkItemTypesQuery from '../graphql/project_work_item_types.query.graphql';
import groupWorkItemByIidQuery from '../graphql/group_work_item_by_iid.query.graphql';
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
import updateNewWorkItemMutation from '../graphql/update_new_work_item.mutation.graphql';
import WorkItemTitle from './work_item_title.vue';
import WorkItemAttributesWrapper from './work_item_attributes_wrapper.vue';
import WorkItemDescription from './work_item_description.vue';
import WorkItemAssignees from './work_item_assignees.vue';
import WorkItemLoading from './work_item_loading.vue';
export default {
components: {
@ -34,17 +49,16 @@ export default {
GlFormGroup,
GlFormCheckbox,
GlFormSelect,
WorkItemAttributesWrapper,
WorkItemDescription,
WorkItemTitle,
WorkItemAssignees,
WorkItemLoading,
WorkItemHealthStatus: () =>
import('ee_component/work_items/components/work_item_health_status.vue'),
WorkItemColor: () => import('ee_component/work_items/components/work_item_color.vue'),
},
inject: ['fullPath', 'isGroup'],
props: {
initialTitle: {
type: String,
required: false,
default: '',
},
workItemTypeName: {
type: String,
required: false,
@ -58,10 +72,6 @@ export default {
},
data() {
return {
draft: {
title: this.initialTitle,
description: '',
},
isTitleValid: true,
isConfidential: false,
error: null,
@ -69,13 +79,37 @@ export default {
selectedWorkItemTypeId: null,
loading: false,
showWorkItemTypeSelect: false,
newWorkItemPath: newWorkItemFullPath(this.fullPath),
};
},
apollo: {
workItem: {
query() {
return this.isGroup ? groupWorkItemByIidQuery : workItemByIidQuery;
},
variables() {
return {
fullPath: this.newWorkItemPath,
iid: NEW_WORK_ITEM_IID,
};
},
skip() {
return !this.fullPath;
},
update(data) {
return data?.workspace?.workItem ?? {};
},
error() {
this.error = i18n.fetchError;
},
},
workItemTypes: {
query() {
return this.isGroup ? groupWorkItemTypesQuery : projectWorkItemTypesQuery;
},
fetchPolicy() {
return this.workItemTypeName ? fetchPolicies.CACHE_ONLY : fetchPolicies.CACHE_FIRST;
},
variables() {
return {
fullPath: this.fullPath,
@ -86,7 +120,10 @@ export default {
return data.workspace?.workItemTypes?.nodes;
},
result() {
if (this.workItemTypes.length === 1) {
if (!this.workItemTypes?.length) {
return;
}
if (this.workItemTypes?.length === 1) {
this.selectedWorkItemTypeId = this.workItemTypes[0].id;
} else {
this.showWorkItemTypeSelect = true;
@ -98,17 +135,31 @@ export default {
},
},
computed: {
isLoading() {
return this.$apollo.queries.workItemTypes.loading || this.$apollo.queries.workItem.loading;
},
hasWidgets() {
return this.draft.widgets?.length > 0;
return this.workItem?.widgets?.length > 0;
},
workItemAssignees() {
return findWidget(WIDGET_TYPE_ASSIGNEES, this.workItem);
},
workItemHealthStatus() {
return findWidget(WIDGET_TYPE_HEALTH_STATUS, this.workItem);
},
workItemColor() {
return findWidget(WIDGET_TYPE_COLOR, this.workItem);
},
workItemTypesForSelect() {
return this.workItemTypes.map((node) => ({
value: node.id,
text: capitalizeFirstCharacter(node.name.toLocaleLowerCase(getPreferredLocales()[0])),
}));
return this.workItemTypes
? this.workItemTypes.map((node) => ({
value: node.id,
text: capitalizeFirstCharacter(node.name.toLocaleLowerCase(getPreferredLocales()[0])),
}))
: [];
},
selectedWorkItemType() {
return this.workItemTypes.find((item) => item.id === this.selectedWorkItemTypeId);
return this.workItemTypes?.find((item) => item.id === this.selectedWorkItemTypeId);
},
formOptions() {
return [{ value: null, text: s__('WorkItem|Select type') }, ...this.workItemTypesForSelect];
@ -129,24 +180,84 @@ export default {
this.selectedWorkItemType?.name,
);
},
titleText() {
return sprintfWorkItem(s__('WorkItem|New %{workItemType}'), this.selectedWorkItemType?.name);
},
canUpdate() {
return this.workItem?.userPermissions?.updateWorkItem;
},
workItemType() {
return this.workItem?.workItemType?.name;
},
workItemParticipantNodes() {
return this.workItemParticipants?.participants?.nodes ?? [];
},
workItemParticipants() {
return findWidget(WIDGET_TYPE_PARTICIPANTS, this.workItem);
},
workItemAssigneeIds() {
const assigneesWidget = findWidget(WIDGET_TYPE_ASSIGNEES, this.workItem);
return assigneesWidget?.assignees?.nodes?.map((assignee) => assignee.id) || [];
},
workItemColorValue() {
const colorWidget = findWidget(WIDGET_TYPE_COLOR, this.workItem);
return colorWidget?.color || '';
},
workItemHealthStatusValue() {
const healthStatusWidget = findWidget(WIDGET_TYPE_HEALTH_STATUS, this.workItem);
return healthStatusWidget?.healthStatus || null;
},
workItemAuthor() {
return this.workItem?.author;
},
workItemTitle() {
return this.workItem?.title || '';
},
workItemDescription() {
const descriptionWidget = findWidget(WIDGET_TYPE_DESCRIPTION, this.workItem);
return descriptionWidget?.description;
},
},
methods: {
validate() {
this.isTitleValid = Boolean(this.draft.title.trim());
isWidgetSupported(widgetType) {
const widgetDefinitions =
this.selectedWorkItemType?.widgetDefinitions?.flatMap((i) => i.type) || [];
return widgetDefinitions.indexOf(widgetType) !== -1;
},
updateDraftData(type, value) {
this.draft = {
...this.draft,
[type]: value,
};
this.$emit(`updateDraft`, { type, value });
validate(newValue) {
const title = newValue || this.workItemTitle;
this.isTitleValid = Boolean(title.trim());
},
updateCache() {
if (!this.selectedWorkItemTypeId) {
return;
}
setNewWorkItemCache(
this.isGroup,
this.fullPath,
this.selectedWorkItemType?.widgetDefinitions,
this.workItemType,
);
},
async updateDraftData(type, value) {
if (type === 'title') {
this.validate();
this.validate(value);
}
try {
this.$apollo.mutate({
mutation: updateNewWorkItemMutation,
variables: {
input: {
isGroup: this.isGroup,
fullPath: this.fullPath,
[type]: value,
},
},
});
} catch {
this.error = this.createErrorText;
Sentry.captureException(this.error);
}
},
async createWorkItem() {
@ -158,18 +269,42 @@ export default {
this.loading = true;
const workItemCreateInput = {
title: this.workItemTitle,
workItemTypeId: this.selectedWorkItemTypeId,
namespacePath: this.fullPath,
confidential: this.workItem.confidential,
descriptionWidget: {
description: this.workItemDescription || '',
},
};
// TODO , we can move this to util, currently objectives with other widgets not being supported is causing issues
if (this.isWidgetSupported(WIDGET_TYPE_COLOR)) {
workItemCreateInput.colorWidget = {
color: this.workItemColorValue,
};
}
if (this.isWidgetSupported(WIDGET_TYPE_ASSIGNEES)) {
workItemCreateInput.assigneesWidget = {
assigneeIds: this.workItemAssigneeIds,
};
}
if (this.isWidgetSupported(WIDGET_TYPE_HEALTH_STATUS)) {
workItemCreateInput.healthStatusWidget = {
healthStatus: this.workItemHealthStatusValue,
};
}
try {
const response = await this.$apollo.mutate({
mutation: createWorkItemMutation,
variables: {
input: {
title: this.draft.title,
workItemTypeId: this.selectedWorkItemTypeId,
namespacePath: this.fullPath,
confidential: this.draft.confidential,
descriptionWidget: {
description: this.draft.description,
},
...workItemCreateInput,
},
},
update: (store, { data: { workItemCreate } }) => {
@ -205,91 +340,127 @@ export default {
this.$emit('cancel');
},
},
NEW_WORK_ITEM_IID,
NEW_WORK_ITEM_GID,
};
</script>
<template>
<form @submit.prevent="createWorkItem">
<gl-alert v-if="error" variant="danger" @dismiss="error = null">{{ error }}</gl-alert>
<h1 v-if="!hideFormTitle" class="page-title gl-text-xl gl-pb-5">{{ titleText }}</h1>
<div class="gl-mb-5">
<gl-loading-icon
v-if="$apollo.queries.workItemTypes.loading"
size="lg"
data-testid="loading-types"
/>
<gl-form-group
v-else-if="showWorkItemTypeSelect"
:label="__('Type')"
label-for="work-item-type"
>
<gl-form-select
id="work-item-type"
v-model="selectedWorkItemTypeId"
:options="formOptions"
class="gl-max-w-26"
<work-item-loading v-if="isLoading" />
<template v-else>
<gl-alert v-if="error" variant="danger" @dismiss="error = null">{{ error }}</gl-alert>
<h1 v-if="!hideFormTitle" class="page-title gl-text-xl gl-pb-5">{{ titleText }}</h1>
<div class="gl-mb-5">
<gl-loading-icon
v-if="$apollo.queries.workItemTypes.loading"
size="lg"
data-testid="loading-types"
/>
</gl-form-group>
</div>
<work-item-title
ref="title"
data-testid="title-input"
is-editing
:is-valid="isTitleValid"
:title="draft.title"
@updateDraft="updateDraftData('title', $event)"
@updateWorkItem="createWorkItem"
/>
<div data-testid="work-item-overview" class="work-item-overview">
<section>
<work-item-description
edit-mode
disable-inline-editing
:autofocus="false"
:full-path="fullPath"
:show-buttons-below-field="false"
@error="updateError = $event"
@updateDraft="updateDraftData('description', $event)"
/>
<gl-form-group :label="__('Confidentiality')" label-for="work-item-confidential">
<gl-form-checkbox
id="work-item-confidential"
v-model="isConfidential"
data-testid="confidential-checkbox"
@change="updateDraftData('confidential', $event)"
>
{{ makeConfidentialText }}
</gl-form-checkbox>
</gl-form-group>
</section>
<aside
v-if="hasWidgets"
data-testid="work-item-overview-right-sidebar"
class="work-item-overview-right-sidebar gl-block"
:class="{ 'is-modal': true }"
>
<work-item-attributes-wrapper
is-create-view
:full-path="fullPath"
:work-item="draft"
@error="updateError = $event"
@updateWorkItem="updateDraftData"
@updateWorkItemAttribute="updateDraftData"
/>
</aside>
<div class="gl-py-3 gl-flex gl-gap-3 gl-col-start-1">
<gl-button
variant="confirm"
:loading="loading"
data-testid="create-button"
@click="createWorkItem"
<gl-form-group
v-else-if="showWorkItemTypeSelect"
:label="__('Type')"
label-for="work-item-type"
>
{{ createWorkItemText }}
</gl-button>
<gl-button type="button" data-testid="cancel-button" @click="handleCancelClick">
{{ __('Cancel') }}
</gl-button>
<gl-form-select
id="work-item-type"
v-model="selectedWorkItemTypeId"
:options="formOptions"
class="gl-max-w-26"
@change="updateCache"
/>
</gl-form-group>
</div>
</div>
<work-item-title
ref="title"
data-testid="title-input"
is-editing
:is-valid="isTitleValid"
:title="workItemTitle"
@updateDraft="updateDraftData('title', $event)"
@updateWorkItem="createWorkItem"
/>
<div data-testid="work-item-overview" class="work-item-overview">
<section>
<work-item-description
edit-mode
:autofocus="false"
:full-path="fullPath"
create-flow
:show-buttons-below-field="false"
:work-item-id="$options.NEW_WORK_ITEM_GID"
:work-item-iid="$options.NEW_WORK_ITEM_IID"
@error="updateError = $event"
@updateDraft="updateDraftData('description', $event)"
/>
<gl-form-group :label="__('Confidentiality')" label-for="work-item-confidential">
<gl-form-checkbox
id="work-item-confidential"
v-model="isConfidential"
data-testid="confidential-checkbox"
@change="updateDraftData('confidential', $event)"
>
{{ makeConfidentialText }}
</gl-form-checkbox>
</gl-form-group>
</section>
<aside
v-if="hasWidgets"
data-testid="work-item-overview-right-sidebar"
class="work-item-overview-right-sidebar"
:class="{ 'is-modal': true }"
>
<template v-if="workItemAssignees">
<work-item-assignees
class="gl-mb-5 js-assignee"
:can-update="canUpdate"
:full-path="fullPath"
:work-item-id="workItem.id"
:assignees="workItemAssignees.assignees.nodes"
:participants="workItemParticipantNodes"
:work-item-author="workItemAuthor"
:allows-multiple-assignees="workItemAssignees.allowsMultipleAssignees"
:work-item-type="workItemType"
:can-invite-members="workItemAssignees.canInviteMembers"
@error="$emit('error', $event)"
/>
</template>
<template v-if="workItemHealthStatus">
<work-item-health-status
class="gl-mb-5"
:health-status="workItemHealthStatus.healthStatus"
:can-update="canUpdate"
:work-item-id="workItem.id"
:work-item-iid="workItem.iid"
:work-item-type="workItemType"
:full-path="fullPath"
@error="$emit('error', $event)"
/>
</template>
<template v-if="workItemColor">
<work-item-color
class="gl-mb-5"
:work-item="workItem"
:full-path="fullPath"
:can-update="canUpdate"
@error="$emit('error', $event)"
/>
</template>
</aside>
<div class="gl-py-3 gl-flex gl-gap-3 gl-col-start-1">
<gl-button
variant="confirm"
:loading="loading"
data-testid="create-button"
@click="createWorkItem"
>
{{ createWorkItemText }}
</gl-button>
<gl-button type="button" data-testid="cancel-button" @click="handleCancelClick">
{{ __('Cancel') }}
</gl-button>
</div>
</div>
</template>
</form>
</template>

View File

@ -2,11 +2,15 @@
import { GlButton, GlModal, GlDisclosureDropdownItem } from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { setNewWorkItemCache } from '~/work_items/graphql/cache_utils';
import {
I18N_NEW_WORK_ITEM_BUTTON_LABEL,
I18N_WORK_ITEM_CREATED,
sprintfWorkItem,
I18N_WORK_ITEM_ERROR_FETCHING_TYPES,
} from '../constants';
import projectWorkItemTypesQuery from '../graphql/project_work_item_types.query.graphql';
import groupWorkItemTypesQuery from '../graphql/group_work_item_types.query.graphql';
import CreateWorkItem from './create_work_item.vue';
export default {
@ -16,6 +20,7 @@ export default {
GlModal,
GlDisclosureDropdownItem,
},
inject: ['fullPath', 'isGroup'],
props: {
workItemTypeName: {
type: String,
@ -31,8 +36,39 @@ export default {
data() {
return {
visible: false,
workItemTypes: [],
};
},
apollo: {
workItemTypes: {
query() {
return this.isGroup ? groupWorkItemTypesQuery : projectWorkItemTypesQuery;
},
variables() {
return {
fullPath: this.fullPath,
name: this.workItemTypeName,
};
},
update(data) {
return data.workspace?.workItemTypes?.nodes ?? [];
},
async result() {
if (!this.workItemTypes || this.workItemTypes.length === 0) {
return;
}
await setNewWorkItemCache(
this.isGroup,
this.fullPath,
this.workItemTypes[0]?.widgetDefinitions,
this.workItemTypeName,
);
},
error() {
this.error = I18N_WORK_ITEM_ERROR_FETCHING_TYPES;
},
},
},
computed: {
newWorkItemText() {
return sprintfWorkItem(I18N_NEW_WORK_ITEM_BUTTON_LABEL, this.workItemTypeName);
@ -50,6 +86,14 @@ export default {
methods: {
hideModal() {
this.visible = false;
if (this.workItemTypes && this.workItemTypes[0]) {
setNewWorkItemCache(
this.isGroup,
this.fullPath,
this.workItemTypes[0]?.widgetDefinitions,
this.workItemTypeName,
);
}
},
showModal() {
this.visible = true;
@ -79,8 +123,8 @@ export default {
variant="confirm"
data-testid="new-epic-button"
@click="showModal"
>{{ newWorkItemText }}</gl-button
>
>{{ newWorkItemText }}
</gl-button>
<gl-modal
modal-id="create-work-item-modal"
:visible="visible"

View File

@ -11,7 +11,8 @@ import WorkItemSidebarDropdownWidget from '~/work_items/components/shared/work_i
import { s__, sprintf, __ } from '~/locale';
import Tracking from '~/tracking';
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
import { i18n, TRACKING_CATEGORY_SHOW } from '../constants';
import updateNewWorkItemMutation from '../graphql/update_new_work_item.mutation.graphql';
import { i18n, TRACKING_CATEGORY_SHOW, NEW_WORK_ITEM_GID } from '../constants';
export default {
components: {
@ -235,6 +236,22 @@ export default {
this.updateInProgress = true;
const { localAssigneeIds } = this;
if (this.workItemId === NEW_WORK_ITEM_GID) {
this.$apollo.mutate({
mutation: updateNewWorkItemMutation,
variables: {
input: {
isGroup: this.isGroup,
fullPath: this.fullPath,
assignees: this.localAssignees,
},
},
});
this.updateInProgress = false;
return;
}
try {
const {
data: {

View File

@ -212,6 +212,7 @@ export default {
:due-date="workItemDueDate.dueDate"
:start-date="workItemDueDate.startDate"
:work-item-type="workItemType"
:full-path="fullPath"
:work-item="workItem"
@error="$emit('error', $event)"
/>
@ -234,6 +235,7 @@ export default {
:work-item-id="workItem.id"
:work-item-iid="workItem.iid"
:work-item-type="workItemType"
:full-path="fullPath"
@error="$emit('error', $event)"
/>
</template>
@ -241,6 +243,7 @@ export default {
<work-item-color
class="gl-mb-5"
:work-item="workItem"
:full-path="fullPath"
:can-update="canUpdate"
@error="$emit('error', $event)"
/>

View File

@ -7,7 +7,11 @@ import { __, s__ } from '~/locale';
import EditedAt from '~/issues/show/components/edited.vue';
import Tracking from '~/tracking';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import { autocompleteDataSources, markdownPreviewPath } from '../utils';
import {
newWorkItemFullPath,
autocompleteDataSources,
markdownPreviewPath,
} from '~/work_items/utils';
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';
@ -61,6 +65,11 @@ export default {
required: false,
default: true,
},
createFlow: {
type: Boolean,
required: false,
default: false,
},
},
markdownDocsPath: helpPagePath('user/markdown'),
data() {
@ -90,12 +99,12 @@ export default {
},
variables() {
return {
fullPath: this.fullPath,
fullPath: this.createFlow ? newWorkItemFullPath(this.fullPath) : this.fullPath,
iid: this.workItemIid,
};
},
update(data) {
return data.workspace.workItem || {};
return data?.workspace?.workItem || {};
},
result() {
if (this.isEditing) {

View File

@ -18,6 +18,7 @@ import {
} from '../constants';
export default {
name: 'WorkItemParent',
inputId: 'work-item-parent-listbox-value',
noWorkItemId: 'no-work-item-id',
i18n: {

View File

@ -354,3 +354,7 @@ export const MAX_FREQUENT_PROJECTS = 3;
export const CREATE_NEW_WORK_ITEM_MODAL = 'create_new_work_item_modal';
export const WORK_ITEM_REFERENCE_CHAR = '#';
export const NEW_WORK_ITEM_IID = 'new-work-item-iid';
export const NEW_WORK_ITEM_GID = 'gid://gitlab/WorkItem/new';

View File

@ -1,5 +1,29 @@
import { produce } from 'immer';
import { findHierarchyWidgetChildren, isNotesWidget } from '../utils';
import VueApollo from 'vue-apollo';
import { apolloProvider } from '~/graphql_shared/issuable_client';
import { issuesListClient } from '~/issues/list';
import { TYPENAME_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { getBaseURL } from '~/lib/utils/url_utility';
import { findHierarchyWidgetChildren, isNotesWidget, newWorkItemFullPath } from '../utils';
import {
WIDGET_TYPE_ASSIGNEES,
WIDGET_TYPE_COLOR,
WIDGET_TYPE_HIERARCHY,
WIDGET_TYPE_PARTICIPANTS,
WIDGET_TYPE_PROGRESS,
WIDGET_TYPE_ROLLEDUP_DATES,
WIDGET_TYPE_START_AND_DUE_DATE,
WIDGET_TYPE_TIME_TRACKING,
WIDGET_TYPE_LABELS,
WIDGET_TYPE_WEIGHT,
WIDGET_TYPE_MILESTONE,
WIDGET_TYPE_ITERATION,
WIDGET_TYPE_HEALTH_STATUS,
WIDGET_TYPE_DESCRIPTION,
NEW_WORK_ITEM_IID,
NEW_WORK_ITEM_GID,
} from '../constants';
import groupWorkItemByIidQuery from './group_work_item_by_iid.query.graphql';
import workItemByIidQuery from './work_item_by_iid.query.graphql';
@ -169,3 +193,261 @@ export const removeHierarchyChild = ({ cache, fullPath, iid, isGroup, workItem }
}),
});
};
export const setNewWorkItemCache = async (isGroup, fullPath, widgetDefinitions, workItemType) => {
const workItemAttributesWrapperOrder = [
WIDGET_TYPE_ASSIGNEES,
WIDGET_TYPE_LABELS,
WIDGET_TYPE_WEIGHT,
WIDGET_TYPE_ROLLEDUP_DATES,
WIDGET_TYPE_MILESTONE,
WIDGET_TYPE_ITERATION,
WIDGET_TYPE_START_AND_DUE_DATE,
WIDGET_TYPE_PROGRESS,
WIDGET_TYPE_HEALTH_STATUS,
WIDGET_TYPE_COLOR,
WIDGET_TYPE_HIERARCHY,
WIDGET_TYPE_TIME_TRACKING,
WIDGET_TYPE_PARTICIPANTS,
];
if (!widgetDefinitions) {
return;
}
const availableWidgets = widgetDefinitions?.flatMap((i) => i.type);
const currentUserId = convertToGraphQLId(TYPENAME_USER, gon?.current_user_id);
const baseURL = getBaseURL();
const widgets = [];
widgets.push({
type: WIDGET_TYPE_DESCRIPTION,
description: null,
descriptionHtml: '',
lastEditedAt: null,
lastEditedBy: null,
__typename: 'WorkItemWidgetDescription',
});
widgets.push({
type: WIDGET_TYPE_PARTICIPANTS,
participants: {
nodes: [
{
id: currentUserId,
avatarUrl: gon?.current_user_avatar_url,
username: gon?.current_username,
name: gon?.current_user_fullname,
webUrl: `${baseURL}/${gon?.current_username}`,
webPath: `/${gon?.current_username}`,
__typename: 'UserCore',
},
],
__typename: 'UserCoreConnection',
},
__typename: 'WorkItemWidgetParticipants',
});
workItemAttributesWrapperOrder.forEach((widgetName) => {
if (availableWidgets.includes(widgetName)) {
if (widgetName === WIDGET_TYPE_ASSIGNEES) {
const assigneesWidgetData = widgetDefinitions.find(
(definition) => definition.type === WIDGET_TYPE_ASSIGNEES,
);
widgets.push({
type: 'ASSIGNEES',
allowsMultipleAssignees: assigneesWidgetData.allowsMultipleAssignees,
canInviteMembers: assigneesWidgetData.canInviteMembers,
assignees: {
nodes: [],
__typename: 'UserCoreConnection',
},
__typename: 'WorkItemWidgetAssignees',
});
}
if (widgetName === WIDGET_TYPE_LABELS) {
const labelsWidgetData = widgetDefinitions.find(
(definition) => definition.type === WIDGET_TYPE_LABELS,
);
widgets.push({
type: 'LABELS',
allowsScopedLabels: labelsWidgetData.allowsScopedLabels,
labels: {
nodes: [],
__typename: 'LabelConnection',
},
__typename: 'WorkItemWidgetLabels',
});
}
if (widgetName === WIDGET_TYPE_WEIGHT) {
widgets.push({
type: 'WEIGHT',
weight: null,
__typename: 'WorkItemWidgetWeight',
});
}
if (widgetName === WIDGET_TYPE_ROLLEDUP_DATES) {
widgets.push({
type: 'ROLLEDUP_DATES',
dueDate: null,
dueDateFixed: null,
dueDateIsFixed: null,
startDate: null,
startDateFixed: null,
startDateIsFixed: null,
__typename: 'WorkItemWidgetRolledupDates',
});
}
if (widgetName === WIDGET_TYPE_MILESTONE) {
widgets.push({
type: 'MILESTONE',
milestone: null,
__typename: 'WorkItemWidgetMilestone',
});
}
if (widgetName === WIDGET_TYPE_ITERATION) {
widgets.push({
iteration: null,
type: 'ITERATION',
__typename: 'WorkItemWidgetIteration',
});
}
if (widgetName === WIDGET_TYPE_START_AND_DUE_DATE) {
widgets.push({
type: 'START_AND_DUE_DATE',
dueDate: null,
startDate: null,
__typename: 'WorkItemWidgetStartAndDueDate',
});
}
if (widgetName === WIDGET_TYPE_PROGRESS) {
widgets.push({
type: 'PROGRESS',
progress: null,
updatedAt: null,
__typename: 'WorkItemWidgetProgress',
});
}
if (widgetName === WIDGET_TYPE_HEALTH_STATUS) {
widgets.push({
type: 'HEALTH_STATUS',
healthStatus: null,
__typename: 'WorkItemWidgetHealthStatus',
});
}
if (widgetName === WIDGET_TYPE_COLOR) {
widgets.push({
type: 'COLOR',
color: '#1068bf',
textColor: '#FFFFFF',
__typename: 'WorkItemWidgetColor',
});
}
if (widgetName === WIDGET_TYPE_HIERARCHY) {
widgets.push({
type: 'HIERARCHY',
hasChildren: false,
parent: null,
children: {
nodes: [],
__typename: 'WorkItemConnection',
},
__typename: 'WorkItemWidgetHierarchy',
});
}
if (widgetName === WIDGET_TYPE_TIME_TRACKING) {
widgets.push({
type: 'TIME_TRACKING',
timeEstimate: 0,
timelogs: {
nodes: [],
__typename: 'WorkItemTimelogConnection',
},
totalTimeSpent: 0,
__typename: 'WorkItemWidgetTimeTracking',
});
}
}
});
const issuesListApolloProvider = new VueApollo({
defaultClient: await issuesListClient(),
});
const cacheProvider = document.querySelector('.js-issues-list-app')
? issuesListApolloProvider
: apolloProvider;
cacheProvider.clients.defaultClient.cache.writeQuery({
query: isGroup ? groupWorkItemByIidQuery : workItemByIidQuery,
variables: {
fullPath: newWorkItemFullPath(fullPath),
iid: NEW_WORK_ITEM_IID,
},
data: {
workspace: {
id: newWorkItemFullPath(fullPath),
workItem: {
id: NEW_WORK_ITEM_GID,
iid: NEW_WORK_ITEM_IID,
archived: false,
title: '',
state: 'OPEN',
description: null,
confidential: false,
createdAt: null,
updatedAt: null,
closedAt: null,
webUrl: `${baseURL}/groups/gitlab-org/-/work_items/new`,
reference: '',
createNoteEmail: null,
namespace: {
id: newWorkItemFullPath(fullPath),
fullPath,
name: newWorkItemFullPath(fullPath),
__typename: 'Namespace', // eslint-disable-line @gitlab/require-i18n-strings
},
author: {
id: currentUserId,
avatarUrl: gon?.current_user_avatar_url,
username: gon?.current_username,
name: gon?.current_user_fullname,
webUrl: `${baseURL}/${gon?.current_username}`,
webPath: `/${gon?.current_username}`,
__typename: 'UserCore',
},
workItemType: {
id: 'mock-work-item-type-id',
name: workItemType,
iconName: 'issue-type-epic',
__typename: 'WorkItemType',
},
userPermissions: {
deleteWorkItem: true,
updateWorkItem: true,
adminParentLink: true,
setWorkItemMetadata: true,
createNote: true,
adminWorkItemLink: true,
__typename: 'WorkItemPermissions',
},
widgets,
__typename: 'WorkItem',
},
__typename: 'Namespace', // eslint-disable-line @gitlab/require-i18n-strings
},
},
});
};

View File

@ -36,3 +36,18 @@ type LocalWorkItemPayload {
extend type Mutation {
localUpdateWorkItem(input: LocalUpdateWorkItemInput!): LocalWorkItemPayload
}
input LocalUpdateNewWorkItemInput {
fullPath: String!
isGroup: Boolean!
healthStatus: String
assignees: [LocalUserInput]
color: String
title: String
description: String
confidential: Boolean
}
extend type Mutation {
updateNewWorkItem(input: LocalUpdateNewWorkItemInput!): LocalWorkItemPayload
}

View File

@ -0,0 +1,3 @@
mutation updateNewWorkItem($input: LocalUpdateNewWorkItemInput) {
updateNewWorkItem(input: $input) @client
}

View File

@ -1,8 +1,8 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import WorkItemsListApp from 'ee_else_ce/work_items/list/components/work_items_list_app.vue';
import { apolloProvider } from '~/graphql_shared/issuable_client';
export const mountWorkItemsListApp = () => {
const el = document.querySelector('.js-work-items-list-root');
@ -22,14 +22,13 @@ export const mountWorkItemsListApp = () => {
isSignedIn,
showNewIssueLink,
workItemType,
canCreateEpic,
} = el.dataset;
return new Vue({
el,
name: 'WorkItemsListRoot',
apolloProvider: new VueApollo({
defaultClient: createDefaultClient(),
}),
apolloProvider,
provide: {
fullPath,
hasEpicsFeature: parseBoolean(hasEpicsFeature),
@ -40,6 +39,7 @@ export const mountWorkItemsListApp = () => {
isGroup: true,
showNewIssueLink: parseBoolean(showNewIssueLink),
workItemType,
canCreateEpic: parseBoolean(canCreateEpic),
},
render: (createComponent) => createComponent(WorkItemsListApp),
});

View File

@ -106,3 +106,8 @@ export const workItemRoadmapPath = (fullPath, iid) => {
const domain = gon.relative_url_root || '';
return `${domain}/groups/${fullPath}/-/roadmap?epic_iid=${iid}`;
};
export const newWorkItemFullPath = (fullPath) => {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `${fullPath}-id`;
};

View File

@ -63,7 +63,7 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController
# TODO https://gitlab.com/gitlab-org/gitlab/-/issues/388541
# type_id is a misnomer. QuickActions::TargetService actually requires an iid.
QuickActions::TargetService
.new(container: project, current_user: current_user)
.new(container: project, current_user: current_user, params: params)
.execute(target_type, params[:type_id])
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module MergeRequests
class DiscussionsResolvedEvent < Gitlab::EventStore::Event
def schema
{
'type' => 'object',
'required' => %w[
current_user_id
merge_request_id
],
'properties' => {
'current_user_id' => { 'type' => 'integer' },
'merge_request_id' => { 'type' => 'integer' }
}
}
end
end
end

View File

@ -120,7 +120,12 @@ module SidebarsHelper
end
def work_items_modal_data(group)
{ full_path: group.full_path } if group
return unless group
{
full_path: group.full_path,
has_issuable_health_status_feature: group.licensed_feature_available?(:issuable_health_status).to_s
}
end
def super_sidebar_nav_panel(

View File

@ -74,36 +74,28 @@ module WikiHelper
def wiki_empty_state_messages(wiki)
case wiki.container
when Project
writable_body = s_("WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on.")
writable_body = s_("WikiEmpty|Use GitLab Wiki to collaborate on documentation in a project or group. You can store wiki pages written in markup formats like Markdown or AsciiDoc in a separate Git repository, and access the wiki through Git, the GitLab web interface, or the API.")
writable_body += s_("WikiEmpty| Have a Confluence wiki already? Use that instead.") if show_enable_confluence_integration?(wiki.container)
{
writable: {
title: s_('WikiEmpty|The wiki lets you write documentation for your project'),
title: s_('WikiEmpty|Get started with wikis'),
body: writable_body
},
issuable: {
title: s_('WikiEmpty|This project has no wiki pages'),
body: s_('WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}.')
},
readonly: {
title: s_('WikiEmpty|This project has no wiki pages'),
body: s_('WikiEmpty|You must be a project member in order to add wiki pages.')
title: s_('WikiEmpty|This wiki doesn\'t have any content yet'),
body: s_('WikiEmpty|You can use GitLab Wiki to collaborate on documentation in a project or group. You can store wiki pages written in markup formats like Markdown or AsciiDoc in a separate Git repository, and access the wiki through Git, the GitLab web interface, or the API.')
}
}
when Group
{
writable: {
title: s_('WikiEmpty|The wiki lets you write documentation for your group'),
body: s_("WikiEmpty|A wiki is where you can store all the details about your group. This can include why you've created it, its principles, how to use it, and so on.")
},
issuable: {
title: s_('WikiEmpty|This group has no wiki pages'),
body: s_('WikiEmptyIssueMessage|You must be a group member in order to add wiki pages. If you have suggestions for how to improve the wiki for this group, consider opening an issue in the %{issues_link}.')
title: s_('WikiEmpty|Get started with wikis'),
body: s_("WikiEmpty|Use GitLab Wiki to collaborate on documentation in a project or group. You can store wiki pages written in markup formats like Markdown or AsciiDoc in a separate Git repository, and access the wiki through Git, the GitLab web interface, or the API.")
},
readonly: {
title: s_('WikiEmpty|This group has no wiki pages'),
body: s_('WikiEmpty|You must be a group member in order to add wiki pages.')
title: s_('WikiEmpty|This wiki doesn\'t have any content yet'),
body: s_('WikiEmpty|You can use GitLab Wiki to collaborate on documentation in a project or group. You can store wiki pages written in markup formats like Markdown or AsciiDoc in a separate Git repository, and access the wiki through Git, the GitLab web interface, or the API.')
}
}
else

View File

@ -30,7 +30,7 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
reached_max_pipeline_hierarchy_size: 'The downstream pipeline tree is too large',
project_deleted: 'The job belongs to a deleted project',
user_blocked: 'The user who created this job is blocked',
ci_quota_exceeded: 'No more CI minutes available',
ci_quota_exceeded: 'No more compute minutes available',
no_matching_runner: 'No matching runner available',
trace_size_exceeded: 'The job log size limit was reached',
builds_disabled: 'The CI/CD is disabled for this project',

View File

@ -83,7 +83,15 @@ module Discussions
def process_auto_merge
return unless discussions_ready_to_merge?
AutoMergeProcessWorker.perform_async(merge_request.id)
if Feature.enabled?(:merge_when_checks_pass, merge_request.project)
Gitlab::EventStore.publish(
MergeRequests::DiscussionsResolvedEvent.new(
data: { current_user_id: current_user.id, merge_request_id: merge_request.id }
)
)
else
AutoMergeProcessWorker.perform_async(merge_request.id)
end
end
def discussions_ready_to_merge?

View File

@ -19,6 +19,14 @@ module QuickActions
# rubocop: disable CodeReuse/ActiveRecord
def work_item(type_iid)
if type_iid.blank?
parent = group_container? ? { namespace: group } : { project: project, namespace: project.project_namespace }
return WorkItem.new(
work_item_type_id: params[:work_item_type_id] || WorkItems::Type.default_issue_type.id,
**parent
)
end
WorkItems::WorkItemsFinder.new(current_user, **parent_params).find_by(iid: type_iid)
end
# rubocop: enable CodeReuse/ActiveRecord

View File

@ -46,5 +46,5 @@
= f.fields_for :credit_card_validation do |ff|
= ff.gitlab_ui_checkbox_component :credit_card_validated_at,
s_('AdminUsers|Validate user account'),
help_text: s_('AdminUsers|A user can validate themselves by inputting a credit/debit card, or an admin can manually validate a user. Validated users can use free CI minutes on instance runners.'),
help_text: s_('AdminUsers|A user can validate themselves by inputting a credit/debit card, or an admin can manually validate a user. Validated users can use free compute minutes on instance runners.'),
checkbox_options: { checked: @user.credit_card_validated_at.present? }

View File

@ -6,6 +6,8 @@
- if Feature.disabled?(:page_specific_styles, current_user)
- add_page_specific_style('page_bundles/commit_description')
- add_page_specific_style('page_bundles/work_items')
%head{ omit_og ? { } : { prefix: "og: http://ogp.me/ns#" } }
%meta{ charset: "utf-8" }
%meta{ 'http-equiv' => 'X-UA-Compatible', content: 'IE=edge' }

View File

@ -17,7 +17,7 @@
%p
- link = link_to('', help_page_path('user/project/working_with_projects', anchor: 'rename-a-repository'), target: '_blank', rel: 'noopener noreferrer')
= safe_format(_("A projects repository name defines its URL (the one you use to access the project via a browser) and its place on the file disk where GitLab is installed. %{link_start}Learn more.%{link_end}"), tag_pair(link, :link_start, :link_end))
%p= _('When you transfer your project to a group, you can easily manage multiple projects, view usage quotas for storage, pipeline minutes, and users, and start a trial or upgrade to a paid tier.')
%p= _('When you transfer your project to a group, you can easily manage multiple projects, view usage quotas for storage, compute minutes, and users, and start a trial or upgrade to a paid tier.')
%p
= _("Don't have a group?")
= link_to _('Create one'), new_group_path, target: '_blank'

View File

@ -19,14 +19,6 @@
- secondary_button_text = s_('WikiEmpty|Enable the Confluence Wiki integration')
- secondary_button_link = edit_project_settings_integration_path(@project, :confluence)
- elsif @project && can?(current_user, :read_issue, @project)
- title = messages.dig(:issuable, :title)
- issues_link = link_to s_('WikiEmptyIssueMessage|issue tracker'), project_issues_path(@project)
- description = messages.dig(:issuable, :body).html_safe % { issues_link: issues_link }
- if show_new_issue_link?(@project)
- primary_button_text = s_('WikiEmpty|Suggest wiki improvement')
- primary_button_link = new_project_issue_path(@project)
= render Pajamas::EmptyStateComponent.new(svg_path: illustration,
title: title,
primary_button_text: primary_button_text,

View File

@ -0,0 +1,19 @@
---
description: User clicks a group result in the command palette
internal_events: true
action: click_group_result_in_command_palette
identifiers:
- namespace
- user
product_section: core_platform
product_stage: data_stores
product_group: global_search
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151657
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -2,8 +2,6 @@
data_category: operational
key_path: counts_monthly.deployments
description: Total deployments count for recent 28 days
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.clusters_applications_cert_managers
description: Count user ids from GitLab Managed clusters with Cert Manager enabled
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.clusters_applications_helm
description: Count user ids from GitLab Managed clusters with Helm enabled
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.clusters_applications_ingress
description: Count user ids from GitLab Managed clusters with Ingress enabled
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.clusters_applications_knative
description: Count user ids from GitLab Managed clusters with Knative enabled
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -3,8 +3,6 @@ data_category: optional
key_path: usage_activity_by_stage_monthly.configure.clusters_management_project
description: Number of Kubernetes clusters with clusters management project being
set
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.clusters_disabled
description: Number of user ids from GitLab Managed disabled clusters
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.clusters_enabled
description: Number of distict user ids from GitLab Managed clusters currently enabled
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.clusters_platforms_gke
description: Number of user ids from GitLab Managed clusters provisioned with GitLab on GCE GKE
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.clusters_platforms_eks
description: Number of user ids from GitLab Managed clusters provisioned with GitLab on AWS EKS
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.clusters_platforms_user
description: Number of user ids from GitLab Managed clusters that are user provisioned
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.instance_clusters_disabled
description: Number of users from GitLab Managed disabled clusters attached to the instance
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.instance_clusters_enabled
description: Number of user ids from GitLab Managed enabled clusters attached to the instance
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.group_clusters_disabled
description: Number of user ids GitLab Managed disabled clusters attached to groups
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.group_clusters_enabled
description: Count disctinct user ids from GitLab Managed enabled clusters attached to groups
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: usage_activity_by_stage_monthly.configure.project_clusters_disabled
description: Number of user ids from GitLab Managed disabled clusters attached to projects
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: operational
key_path: usage_activity_by_stage_monthly.configure.project_clusters_enabled
description: Number of user ids from GitLab Managed enabled clusters attached to projects
product_section: ops
product_stage: configure
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -2,8 +2,6 @@
data_category: operational
key_path: usage_activity_by_stage_monthly.verify.ci_pipeline_config_auto_devops
description: Distinct users that ran an auto DevOps pipeline without a .gitlab-ci.yml file.
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: redis_hll_counters.quickactions.i_quickactions_rebase_monthly
description: Count of MAU using the `/rebase` quick action on a Merge Request
product_section: dev
product_stage: source_code
product_group: source_code
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: operational
key_path: counts_monthly.successful_deployments
description: Total successful deployments
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: operational
key_path: counts_monthly.failed_deployments
description: Total failed deployments
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: operational
key_path: usage_activity_by_stage_monthly.release.deployments
description: Unique users triggering deployments
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: operational
key_path: usage_activity_by_stage_monthly.release.failed_deployments
description: Disinct users who initiated a failed deployment.
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: operational
key_path: usage_activity_by_stage_monthly.release.releases
description: Unique users creating release tags
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: operational
key_path: usage_activity_by_stage_monthly.release.successful_deployments
description: Disinct users who initiated a successful deployment.
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: operational
key_path: redis_hll_counters.terraform.p_terraform_state_api_unique_users_monthly
description: Monthly active users of GitLab Managed Terraform states
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: operational
key_path: redis_hll_counters.ci_templates.p_ci_templates_implicit_auto_devops_monthly
description: Count of pipelines with implicit Auto DevOps runs
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: redis_hll_counters.ci_templates.p_ci_templates_implicit_auto_devops_build_monthly
description: Count of pipelines with implicit Auto Build runs
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: removed

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: redis_hll_counters.ci_templates.p_ci_templates_implicit_auto_devops_deploy_monthly
description: Count of pipelines with implicit Auto Deploy runs
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: removed

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: redis_hll_counters.ci_templates.p_ci_templates_5_min_production_app_monthly
description: Number of projects using 5 min production app CI template in last 7 days.
product_section: seg
product_stage: deploy
product_group: 5-min-app
value_type: number
status: removed

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: redis_hll_counters.ci_templates.p_ci_templates_auto_devops_monthly
description: Count of pipelines using the Auto DevOps template
product_section: ops
product_stage: configure
product_group: environments
value_type: number
status: active

View File

@ -3,8 +3,6 @@ data_category: optional
key_path: redis_hll_counters.ci_templates.p_ci_templates_aws_cf_deploy_ec2_monthly
description: Count of projects using `AWS/CF-Provision-and-Deploy-EC2.gitlab-ci.yml`
template in last 28 days.
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: removed

View File

@ -3,8 +3,6 @@ data_category: optional
key_path: redis_hll_counters.ci_templates.p_ci_templates_aws_deploy_ecs_monthly
description: Count of projects using `AWS/Deploy-ECS.gitlab-ci.yml` template in last
28 days.
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: redis_hll_counters.ci_templates.p_ci_templates_auto_devops_build_monthly
description: Count of pipelines using the Auto Build template
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: removed

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: redis_hll_counters.ci_templates.p_ci_templates_auto_devops_deploy_monthly
description: Count of pipelines using the stable Auto Deploy template
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: removed

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: redis_hll_counters.ci_templates.p_ci_templates_auto_devops_deploy_latest_monthly
description: Count of pipelines using the latest Auto Deploy template
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: removed

View File

@ -2,8 +2,6 @@
data_category: optional
key_path: redis_hll_counters.ci_templates.p_ci_templates_terraform_base_latest_monthly
description: Count of pipelines that include the terraform base template from GitLab
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -2,8 +2,6 @@
data_category: operational
key_path: redis_hll_counters.ci_templates.ci_templates_total_unique_counts_monthly
description: Total count of pipelines runs
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: removed

View File

@ -2,8 +2,6 @@
key_path: usage_activity_by_stage_monthly.release.releases_with_milestones
description: Unique users creating releases with milestones associated
performance_indicator_type: []
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.ci_users.ci_users_executing_deployment_job_monthly
description: Monthly counts of times users have executed deployment jobs
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.ci_users.ci_users_executing_verify_environment_job_monthly
description: Monthly counts of times users have executed verify jobs
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.kubernetes_agent.agent_users_using_ci_tunnel_monthly
description: MAU of the Agent for Kubernetes CI/CD Tunnel
product_section: ops
product_stage: deploy
product_group: environments
product_category: deployment_management
value_type: number

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.environments.users_visiting_environments_pages_monthly
description: Monthly count of unique users visiting environments pages
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.work_items.users_updating_work_item_iteration_monthly
description: Unique users updating a work item's iteration
product_section: team planning
product_stage: dev
product_group: project_management
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_terraform_module_monthly
description: Count of pipelines using the Terraform Module template
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_terraform_module_base_monthly
description: Count of pipelines using the Terraform Module Base template
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.kubernetes_agent.k8s_api_proxy_requests_unique_agents_via_ci_access_monthly
description: MAU of the unique Agents using the CI/CD Tunnel via Ci Access
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.kubernetes_agent.k8s_api_proxy_requests_unique_agents_via_user_access_monthly
description: MAU of the unique Agents using the CI/CD Tunnel via User Access
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.kubernetes_agent.k8s_api_proxy_requests_unique_users_via_ci_access_monthly
description: MAU of the unique Users using the CI/CD Tunnel via Ci Access
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.kubernetes_agent.k8s_api_proxy_requests_unique_users_via_user_access_monthly
description: MAU of the unique Users using the CI/CD Tunnel via User Access
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.kubernetes_agent.flux_git_push_notified_unique_projects_monthly
description: MAU of the unique projects which were notified by agentk about new Git push events in order to reconcile their Flux workloads
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.kubernetes_agent.k8s_api_proxy_requests_unique_agents_via_pat_access_monthly
description: MAU of the unique Agents using the KAS Kubernetes API proxy via Personal Access Token Access
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.kubernetes_agent.k8s_api_proxy_requests_unique_users_via_pat_access_monthly
description: MAU of the unique Users using the KAS Kubernetes API proxy via Personal Access Token Access
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_diffblue_cover_monthly
description: Count of pipelines using the Diffblue Cover template
product_section: ci
product_stage: pipeline_authoring
product_group: pipeline_authoring
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_opentofu_base_latest_monthly
description: Count of pipelines that include the OpenTofu job template from GitLab
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_opentofu_latest_monthly
description: Count of pipelines that include the OpenTofu pipeline template from GitLab
product_section: ops
product_stage: deploy
product_group: environments
value_type: number
status: active

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_group_result_in_command_palette_monthly
description: Monthly count of unique users User clicks a group result in the command palette
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151657
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_group_result_in_command_palette
unique: user.id

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_project_result_in_command_palette_monthly
description: Monthly count of unique users User clicks a project result in the command palette
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_regex_button_in_search_page_input_monthly
description: Monthly count of unique users who click regular expression button in search bar
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_search_button_to_activate_command_palette_monthly
description: Monthly count of unique users User activates the command palette with the search button
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number

View File

@ -1,8 +1,6 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_user_result_in_command_palette_monthly
description: Monthly count of unique users User clicks a user result in the command palette
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number

View File

@ -1,8 +1,6 @@
---
key_path: count_distinct_user_id_from_i_quickactions_remove_email_multiple_28d
description: Unique users using the /remove_email quick action to remove multiple email participants from an issue within 28 days
product_section: seg
product_stage: service
product_group: respond
performance_indicator_type: []
value_type: number

View File

@ -1,8 +1,6 @@
---
key_path: count_distinct_user_id_from_i_quickactions_remove_email_single_28d
description: Unique users using the /remove_email quick action to remove a single email participant from an issue within 28 days
product_section: seg
product_stage: service
product_group: respond
performance_indicator_type: []
value_type: number

Some files were not shown because too many files have changed in this diff Show More