Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-12-13 12:32:18 +00:00
parent 5395cd1a29
commit 6238e8c035
103 changed files with 2039 additions and 924 deletions

View File

@ -78,7 +78,7 @@ workflow:
QA_ALLOW_LOCAL_REQUESTS: "true"
QA_SUITE_STATUS_ENV_FILE: $CI_PROJECT_DIR/suite_status.env
QA_RUN_IN_PARALLEL: "true"
QA_PARALLEL_PROCESSES: 4
QA_PARALLEL_PROCESSES: 5
GITLAB_QA_ADMIN_ACCESS_TOKEN: $QA_ADMIN_ACCESS_TOKEN
before_script:
- echo "SUITE_RAN=true" > "$QA_SUITE_STATUS_ENV_FILE"

View File

@ -34,7 +34,8 @@ After your merge request has been approved according to our [approval guidelines
- You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation]
- [ ] Create each MR targeting the stable branch `X-Y-stable`, using the [Security merge request template].
- Every merge request will have its own set of to-dos, so make sure to complete those.
- [ ] On the "Related merge requests" section, ensure that `4` merge requests are associated: The one targeting `master` and the `3` backports.
- [ ] On the `Related merge requests` section, ensure that **ONLY** `4` merge requests are associated: **ONLY** one targeting `master` and the `3` backports.
- [ ] If there are more associated MRs, re-create another security issue and ensure there are only 4 merge requests associated with that one.
- [ ] If this issue requires less than `4` merge requests, add the ~"reduced backports" label.
## Assigning to a release

View File

@ -3603,6 +3603,7 @@ Gitlab/BoundedContexts:
- 'ee/lib/ee/event_filter.rb'
- 'ee/lib/ee/feature.rb'
- 'ee/lib/ee/feature/definition.rb'
- 'ee/lib/ee/sidebars/admin/menus/admin_overview_menu.rb'
- 'ee/lib/ee/sidebars/admin/menus/admin_settings_menu.rb'
- 'ee/lib/ee/sidebars/admin/menus/monitoring_menu.rb'
- 'ee/lib/ee/sidebars/admin/panel.rb'

View File

@ -1959,7 +1959,6 @@ Layout/LineLength:
- 'lib/api/commit_statuses.rb'
- 'lib/api/commits.rb'
- 'lib/api/composer_packages.rb'
- 'lib/api/concerns/packages/conan_endpoints.rb'
- 'lib/api/concerns/packages/debian_distribution_endpoints.rb'
- 'lib/api/concerns/packages/debian_package_endpoints.rb'
- 'lib/api/debian_group_packages.rb'

View File

@ -1 +1 @@
6250b300df6b3559e377ac6d218bba674ee045bf
ed8e69891a82913e19430e094c192833ea5cb066

View File

@ -1,18 +1,19 @@
<script>
import { GlEmptyState, GlIcon, GlLink, GlLoadingIcon, GlTableLite, GlTruncate } from '@gitlab/ui';
import { GlEmptyState, GlLink, GlLoadingIcon, GlTableLite, GlTruncate } from '@gitlab/ui';
import { createAlert } from '~/alert';
import { __, s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
import getCiCatalogResourceComponents from '../../graphql/queries/get_ci_catalog_resource_components.query.graphql';
export default {
components: {
GlEmptyState,
GlIcon,
GlLink,
GlLoadingIcon,
GlTableLite,
GlTruncate,
HelpIcon,
},
inputHelpLink: helpPagePath('ci/yaml/inputs', {
anchor: 'define-input-parameters-with-specinputs',
@ -127,7 +128,7 @@ export default {
:href="$options.inputHelpLink"
target="_blank"
>
<gl-icon name="question-o" :size="14" />
<help-icon />
</gl-link>
</div>
<gl-table-lite :items="component.inputs" :fields="$options.fields">

View File

@ -1,18 +1,19 @@
<script>
import { GlButton, GlFormCheckbox, GlIcon, GlLink, GlAlert } from '@gitlab/ui';
import { GlButton, GlFormCheckbox, GlLink, GlAlert } from '@gitlab/ui';
import CiLintResults from '~/ci/pipeline_editor/components/lint/ci_lint_results.vue';
import lintCiMutation from '~/ci/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql';
import SourceEditor from '~/vue_shared/components/source_editor.vue';
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
export default {
components: {
GlButton,
GlFormCheckbox,
GlIcon,
GlLink,
GlAlert,
CiLintResults,
SourceEditor,
HelpIcon,
},
props: {
endpoint: {
@ -110,8 +111,7 @@ export default {
>
<gl-form-checkbox v-model="dryRun" data-testid="ci-lint-dryrun"
>{{ __('Simulate a pipeline created for the default branch') }}
<gl-link :href="pipelineSimulationHelpPagePath" target="_blank"
><gl-icon name="question-o" variant="info" /></gl-link
<gl-link :href="pipelineSimulationHelpPagePath" target="_blank"><help-icon /></gl-link
></gl-form-checkbox>
</div>
<gl-button data-testid="ci-lint-clear" @click="clear">{{ __('Clear') }}</gl-button>

View File

@ -1,9 +1,10 @@
<script>
import { GlButton, GlButtonGroup, GlIcon, GlLink, GlPopover } from '@gitlab/ui';
import { GlButton, GlButtonGroup, GlLink, GlPopover } from '@gitlab/ui';
import { s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
export default {
i18n: {
@ -24,10 +25,10 @@ export default {
components: {
GlButton,
GlButtonGroup,
GlIcon,
GlLink,
GlPopover,
TimeagoTooltip,
HelpIcon,
},
mixins: [timeagoMixin],
props: {
@ -59,7 +60,7 @@ export default {
<div class="title gl-font-bold">
<span class="gl-mr-2">{{ $options.i18n.jobArtifacts }}</span>
<gl-link :href="$options.artifactsHelpPath" data-testid="artifacts-help-link">
<gl-icon id="artifacts-help" name="question-o" />
<help-icon id="artifacts-help" />
</gl-link>
<gl-popover
target="artifacts-help"
@ -85,7 +86,7 @@ export default {
rel="noopener noreferrer nofollow"
data-testid="artifact-expired-help-link"
>
<gl-icon name="question-o" />
<help-icon />
</gl-link>
</p>
<p v-else-if="isLocked" class="build-detail-row">

View File

@ -1,11 +1,12 @@
<script>
import { GlIcon, GlLink } from '@gitlab/ui';
import { GlLink } from '@gitlab/ui';
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
export default {
name: 'SidebarDetailRow',
components: {
GlIcon,
GlLink,
HelpIcon,
},
props: {
title: {
@ -52,7 +53,7 @@ export default {
target="_blank"
data-testid="job-sidebar-help-link"
>
<gl-icon class="gl-ml-2" name="question-o" variant="info" />
<help-icon class="gl-ml-2" />
</gl-link>
</span>
</p>

View File

@ -3,7 +3,6 @@ import {
GlAlert,
GlButton,
GlDisclosureDropdown,
GlIcon,
GlLoadingIcon,
GlLink,
GlTooltip,
@ -14,6 +13,7 @@ import {
import { s__, __ } from '~/locale';
import Tracking from '~/tracking';
import { helpPagePath } from '~/helpers/help_page_helper';
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
import { pipelineEditorTrackingOptions } from '../../constants';
import ValidatePipelinePopover from '../popovers/validate_pipeline_popover.vue';
import CiLintResults from '../lint/ci_lint_results.vue';
@ -60,13 +60,13 @@ export default {
GlAlert,
GlButton,
GlDisclosureDropdown,
GlIcon,
GlLoadingIcon,
GlLink,
GlSprintf,
GlTooltip,
GlEmptyState,
ValidatePipelinePopover,
HelpIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -205,14 +205,7 @@ export default {
disabled
/>
<validate-pipeline-popover />
<gl-icon
id="validate-pipeline-help"
name="question-o"
class="gl-ml-1 gl-fill-blue-500"
category="secondary"
variant="link"
:aria-label="$options.i18n.help"
/>
<help-icon id="validate-pipeline-help" class="gl-ml-1" :aria-label="$options.i18n.help" />
</div>
<div v-if="canResimulatePipeline">
<span class="gl-text-gray-400" data-testid="content-status">

View File

@ -197,6 +197,15 @@ export const config = {
MergeRequestApprovalState: {
merge: true,
},
WorkItemType: {
fields: {
widgetDefinitions: {
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
},
},
},
},
};

View File

@ -103,6 +103,7 @@ export default {
</gl-link>
</template>
<template #actions>
<slot name="actions"></slot>
<new-resource-dropdown
v-if="showNewIssueDropdown"
class="gl-mx-2 gl-mb-3 gl-self-center"
@ -111,16 +112,18 @@ export default {
:extract-projects="extractProjects"
:group-id="groupId"
/>
<gl-button
v-if="showNewIssueLink"
:href="newIssuePath"
variant="confirm"
class="gl-mx-2 gl-mb-3"
data-track-action="click_new_issue_project_issues_empty_list_page"
data-track-label="new_issue_project_issues_empty_list"
>
{{ __('New issue') }}
</gl-button>
<slot name="new-issue-button">
<gl-button
v-if="showNewIssueLink"
:href="newIssuePath"
variant="confirm"
class="gl-mx-2 gl-mb-3"
data-track-action="click_new_issue_project_issues_empty_list_page"
data-track-label="new_issue_project_issues_empty_list"
>
{{ __('New issue') }}
</gl-button>
</slot>
<gl-button
v-if="canCreateProjects"
:href="newProjectPath"

View File

@ -18,7 +18,7 @@ export default {
CollapsibleSection,
MergeRequest,
},
inject: ['mergeRequestsSearchDashboardPath'],
inject: ['mergeRequestsSearchDashboardPath', 'newListsEnabled'],
props: {
tabs: {
type: Array,
@ -37,6 +37,7 @@ export default {
},
queriesForTab(tab) {
return tab.lists
.flat()
.filter((l) => !l.hideCount)
.map((list) => ({ query: list.query, variables: list.variables }));
},
@ -57,95 +58,111 @@ export default {
<template #title>
<tab-title :title="tab.title" :queries="queriesForTab(tab)" :tab-key="tab.key" />
</template>
<merge-requests-query
v-for="(list, i) in tab.lists"
:key="`list_${i}`"
:query="list.query"
:variables="list.variables"
:hide-count="list.hideCount"
:class="{ '!gl-mt-3': i === 0 }"
>
<template #default="{ mergeRequests, count, hasNextPage, loadMore, loading, error }">
<collapsible-section
:count="count"
:has-merge-requests="mergeRequests.length > 0"
:title="list.title"
:help-content="list.helpContent"
:loading="loading"
>
<div>
<div class="gl-overflow-x-auto">
<table class="gl-w-full">
<colgroup>
<col style="width: 60px" />
<col style="width: 70px" />
<col style="width: 47%; min-width: 200px" />
<col style="width: 120px" />
<col style="width: 120px" />
<col style="min-width: 200px" />
</colgroup>
<thead class="gl-border-b gl-bg-subtle">
<tr>
<th class="gl-pb-3 gl-pl-5 gl-pr-3" :aria-label="__('Pipeline status')">
<gl-icon name="pipeline" />
<span class="gl-sr-only">{{ __('Pipeline status') }}</span>
</th>
<th class="gl-px-3 gl-pb-3" :aria-label="__('Approvals')">
<gl-icon name="approval" />
<span class="gl-sr-only">{{ __('Approvals') }}</span>
</th>
<th class="gl-px-3 gl-pb-3 gl-text-sm gl-text-subtle">
{{ __('Title') }}
</th>
<th class="gl-px-3 gl-pb-3 gl-text-center gl-text-sm gl-text-subtle">
{{ __('Assignee') }}
</th>
<th class="gl-px-3 gl-pb-3 gl-text-center gl-text-sm gl-text-subtle">
{{ __('Reviewers') }}
</th>
<th class="gl-pb-3 gl-pl-3 gl-pr-5 gl-text-right gl-text-sm gl-text-subtle">
{{ __('Activity') }}
</th>
</tr>
</thead>
<tbody>
<template v-if="mergeRequests.length">
<merge-request
v-for="(mergeRequest, index) in mergeRequests"
:key="mergeRequest.id"
:merge-request="mergeRequest"
:is-last="index === mergeRequests.length - 1"
data-testid="merge-request"
/>
</template>
<tr v-else>
<td colspan="6" :class="{ 'gl-py-6 gl-text-center': !error }">
<template v-if="loading">
{{ __('Loading...') }}
</template>
<template v-else-if="error">
<gl-alert variant="danger" :dismissible="false">
{{
__('There was an error fetching merge requests. Please try again.')
}}
</gl-alert>
</template>
</td>
</tr>
</tbody>
</table>
<div v-for="(lists, i) in tab.lists" :key="`lists_${i}`">
<div
v-if="i === 1 && newListsEnabled"
class="gl-mt-8 gl-rounded-base gl-bg-gray-50 gl-px-4 gl-py-2 gl-font-bold gl-text-subtle"
data-testid="merge-request-count-explanation"
>
{{ __('Items below are excluded from the active count') }}
</div>
<merge-requests-query
v-for="list in lists"
:key="`list_${list.id}`"
:query="list.query"
:variables="list.variables"
:hide-count="list.hideCount"
:class="{ '!gl-mt-3': i === 0 }"
>
<template #default="{ mergeRequests, count, hasNextPage, loadMore, loading, error }">
<collapsible-section
:count="count"
:has-merge-requests="mergeRequests.length > 0"
:title="list.title"
:help-content="list.helpContent"
:loading="loading"
>
<div>
<div class="gl-overflow-x-auto">
<table class="gl-w-full">
<colgroup>
<col style="width: 60px" />
<col style="width: 70px" />
<col style="width: 47%; min-width: 200px" />
<col style="width: 120px" />
<col style="width: 120px" />
<col style="min-width: 200px" />
</colgroup>
<thead class="gl-border-b gl-bg-subtle">
<tr>
<th class="gl-pb-3 gl-pl-5 gl-pr-3" :aria-label="__('Pipeline status')">
<gl-icon name="pipeline" />
<span class="gl-sr-only">{{ __('Pipeline status') }}</span>
</th>
<th class="gl-px-3 gl-pb-3" :aria-label="__('Approvals')">
<gl-icon name="approval" />
<span class="gl-sr-only">{{ __('Approvals') }}</span>
</th>
<th class="gl-px-3 gl-pb-3 gl-text-sm gl-text-subtle">
{{ __('Title') }}
</th>
<th class="gl-px-3 gl-pb-3 gl-text-center gl-text-sm gl-text-subtle">
{{ __('Assignee') }}
</th>
<th class="gl-px-3 gl-pb-3 gl-text-center gl-text-sm gl-text-subtle">
{{ __('Reviewers') }}
</th>
<th
class="gl-pb-3 gl-pl-3 gl-pr-5 gl-text-right gl-text-sm gl-text-subtle"
>
{{ __('Activity') }}
</th>
</tr>
</thead>
<tbody>
<template v-if="mergeRequests.length">
<merge-request
v-for="(mergeRequest, index) in mergeRequests"
:key="mergeRequest.id"
:merge-request="mergeRequest"
:is-last="index === mergeRequests.length - 1"
data-testid="merge-request"
/>
</template>
<tr v-else>
<td colspan="6" :class="{ 'gl-py-6 gl-text-center': !error }">
<template v-if="loading">
{{ __('Loading...') }}
</template>
<template v-else-if="error">
<gl-alert variant="danger" :dismissible="false">
{{
__(
'There was an error fetching merge requests. Please try again.',
)
}}
</gl-alert>
</template>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<template #pagination>
<div v-if="hasNextPage" class="crud-pagination-container gl-flex gl-justify-center">
<gl-button :loading="loading" data-testid="load-more" @click="loadMore">{{
__('Show more')
}}</gl-button>
</div>
</template>
</collapsible-section>
</template>
</merge-requests-query>
<template #pagination>
<div
v-if="hasNextPage"
class="crud-pagination-container gl-flex gl-justify-center"
>
<gl-button :loading="loading" data-testid="load-more" @click="loadMore">{{
__('Show more')
}}</gl-button>
</div>
</template>
</collapsible-section>
</template>
</merge-requests-query>
</div>
</gl-tab>
<template #tabs-end>
<li role="presentation" class="nav-item">

View File

@ -2,6 +2,7 @@ import { concatPagination } from '@apollo/client/utilities';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import VueRouter from 'vue-router';
import { parseBoolean } from '~/lib/utils/common_utils';
import createDefaultClient from '~/lib/graphql';
import App from './components/app.vue';
@ -64,7 +65,10 @@ export function initMergeRequestDashboard(el) {
},
),
}),
provide: { mergeRequestsSearchDashboardPath: el.dataset.mergeRequestsSearchDashboardPath },
provide: {
newListsEnabled: parseBoolean(el.dataset.newListsEnabled),
mergeRequestsSearchDashboardPath: el.dataset.mergeRequestsSearchDashboardPath,
},
render(createElement) {
return createElement(App, {
props: {

View File

@ -75,8 +75,6 @@ import MergeRequestReviewers from '~/issuable/components/merge_request_reviewers
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
import setSortPreferenceMutation from '~/issues/list/queries/set_sort_preference.mutation.graphql';
import issuableEventHub from '~/issues/list/eventhub';
import getMergeRequestsQuery from 'ee_else_ce/merge_requests/list/queries/get_merge_requests.query.graphql';
import getMergeRequestsCountsQuery from 'ee_else_ce/merge_requests/list/queries/get_merge_requests_counts.query.graphql';
import { AutocompleteCache } from '../../utils/autocomplete_cache';
import { i18n, BRANCH_LIST_REFRESH_INTERVAL } from '../constants';
import searchLabelsQuery from '../queries/search_labels.query.graphql';
@ -139,10 +137,13 @@ export default {
mergeTrainsPath: { default: undefined },
defaultBranch: { default: '' },
initialEmail: { default: '' },
getMergeRequestsQuery: { default: undefined },
getMergeRequestsCountsQuery: { default: undefined },
isProject: { default: true },
},
data() {
return {
projectId: null,
namespaceId: null,
branchCacheAges: {},
filterTokens: [],
mergeRequests: [],
@ -158,12 +159,14 @@ export default {
},
apollo: {
mergeRequests: {
query: getMergeRequestsQuery,
query() {
return this.getMergeRequestsQuery;
},
variables() {
return this.queryVariables;
},
update(data) {
return data.project.mergeRequests?.nodes ?? [];
return data.namespace.mergeRequests?.nodes ?? [];
},
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
nextFetchPolicy: fetchPolicies.CACHE_FIRST,
@ -172,31 +175,35 @@ export default {
if (!data) {
return;
}
this.projectId = getIdFromGraphQLId(data.project.id);
this.pageInfo = data.project.mergeRequests?.pageInfo ?? {};
this.namespaceId = getIdFromGraphQLId(data.namespace.id);
this.pageInfo = data.namespace.mergeRequests?.pageInfo ?? {};
},
error(error) {
this.mergeRequestsError = this.$options.i18n.errorFetchingMergeRequests;
Sentry.captureException(error);
},
skip() {
return !this.hasAnyMergeRequests || isEmpty(this.pageParams);
return !this.hasAnyMergeRequests || isEmpty(this.pageParams) || !this.getMergeRequestsQuery;
},
},
mergeRequestCounts: {
query: getMergeRequestsCountsQuery,
query() {
return this.getMergeRequestsCountsQuery;
},
variables() {
return this.queryVariables;
},
update(data) {
return data.project ?? {};
return data.namespace ?? {};
},
error(error) {
this.mergeRequestsError = this.$options.i18n.errorFetchingCounts;
Sentry.captureException(error);
},
skip() {
return !this.hasAnyMergeRequests || isEmpty(this.pageParams);
return (
!this.hasAnyMergeRequests || isEmpty(this.pageParams) || !this.getMergeRequestsCountsQuery
);
},
},
},
@ -249,7 +256,7 @@ export default {
dataType: 'user',
defaultUsers: [],
fullPath: this.fullPath,
isProject: true,
isProject: this.isProject,
recentSuggestionsStorageKey: `${this.fullPath}-merge-requests-recent-tokens-author`,
preloadedUsers,
multiselect: false,
@ -261,7 +268,7 @@ export default {
token: UserToken,
dataType: 'user',
fullPath: this.fullPath,
isProject: true,
isProject: this.isProject,
recentSuggestionsStorageKey: `${this.fullPath}-merge-requests-recent-tokens-assignee`,
preloadedUsers,
multiSelect: false,
@ -274,7 +281,7 @@ export default {
token: UserToken,
dataType: 'user',
fullPath: this.fullPath,
isProject: true,
isProject: this.isProject,
recentSuggestionsStorageKey: `${this.fullPath}-merge-requests-recent-tokens-reviewer`,
preloadedUsers,
multiSelect: false,
@ -289,7 +296,7 @@ export default {
defaultUsers: [],
operators: OPERATORS_IS,
fullPath: this.fullPath,
isProject: true,
isProject: this.isProject,
recentSuggestionsStorageKey: `${this.fullPath}-merge_requests-recent-tokens-merged_by`,
preloadedUsers,
multiselect: false,
@ -303,7 +310,7 @@ export default {
dataType: 'user',
operators: OPERATORS_IS,
fullPath: this.fullPath,
isProject: true,
isProject: this.isProject,
recentSuggestionsStorageKey: `${this.fullPath}-merge_requests-recent-tokens-approvers`,
preloadedUsers,
multiSelect: false,
@ -315,7 +322,7 @@ export default {
token: UserToken,
dataType: 'user',
fullPath: this.fullPath,
isProject: true,
isProject: this.isProject,
recentSuggestionsStorageKey: `${this.fullPath}-merge_requests-recent-tokens-approved_by`,
preloadedUsers,
multiSelect: false,
@ -328,7 +335,7 @@ export default {
recentSuggestionsStorageKey: `${this.fullPath}-merge-requests-recent-tokens-milestone`,
shouldSkipSort: true,
fullPath: this.fullPath,
isProject: true,
isProject: this.isProject,
multiselect: false,
unique: true,
},
@ -364,7 +371,7 @@ export default {
token: GlFilteredSearchToken,
operators: OPERATORS_IS,
fullPath: this.fullPath,
isProject: true,
isProject: this.isProject,
multiselect: false,
options: [
{ value: 'yes', title: this.$options.i18n.yes },
@ -378,7 +385,7 @@ export default {
icon: 'arrow-right',
token: BranchToken,
fullPath: this.fullPath,
isProject: true,
isProject: this.isProject,
fetchBranches: this.fetchTargetBranches,
},
{
@ -387,7 +394,7 @@ export default {
icon: 'branch',
token: BranchToken,
fullPath: this.fullPath,
isProject: true,
isProject: this.isProject,
fetchBranches: this.fetchSourceBranches,
},
{
@ -450,7 +457,7 @@ export default {
return (
this.$apollo.queries.mergeRequests.loading &&
!this.$apollo.provider.clients.defaultClient.readQuery({
query: getMergeRequestsQuery,
query: this.getMergeRequestsQuery,
variables: this.queryVariables,
})
);
@ -496,8 +503,8 @@ export default {
};
const url = typeUrls[branchType];
return url && this.projectId
? mergeUrlParams({ project_id: this.projectId }, url)
return url && this.namespaceId
? mergeUrlParams({ [this.isProject ? 'project_id' : 'group_id']: this.namespaceId }, url)
: typeUrls.other;
},
async updateBranchCache(branchType, path) {
@ -557,10 +564,10 @@ export default {
return this.$apollo
.query({
query: searchLabelsQuery,
variables: { fullPath: this.fullPath, search },
variables: { fullPath: this.fullPath, search, isProject: this.isProject },
fetchPolicy,
})
.then(({ data }) => data.project.labels.nodes)
.then(({ data }) => (data.project || data.group).labels.nodes)
.then((labels) =>
// TODO remove once we can search by title-only on the backend
// https://gitlab.com/gitlab-org/gitlab/-/issues/346353

View File

@ -6,7 +6,11 @@ import { defaultClient } from '~/graphql_shared/issuable_client';
import MergeRequestsListApp from './components/merge_requests_list_app.vue';
import MoreactionsDropdown from './components/more_actions_dropdown.vue';
export async function mountMergeRequestListsApp() {
export async function mountMergeRequestListsApp({
getMergeRequestsQuery,
getMergeRequestsCountsQuery,
isProject = true,
} = {}) {
const el = document.querySelector('.js-merge-request-list-root');
if (!el) {
@ -79,6 +83,9 @@ export async function mountMergeRequestListsApp() {
quickActionsHelpPath,
markdownHelpPath,
resetPath,
getMergeRequestsQuery,
getMergeRequestsCountsQuery,
isProject,
},
render: (createComponent) => createComponent(MergeRequestsListApp),
});

View File

@ -1,5 +1,5 @@
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "./merge_request.fragment.graphql"
#import "../merge_request.fragment.graphql"
query getMergeRequests(
$hideUsers: Boolean = false
@ -32,7 +32,7 @@ query getMergeRequests(
$firstPageSize: Int
$lastPageSize: Int
) {
project(fullPath: $fullPath) {
namespace: project(fullPath: $fullPath) {
id
mergeRequests(
sort: $sort

View File

@ -21,7 +21,7 @@ query getMergeRequestsCount(
$environmentName: String
$not: MergeRequestsResolverNegatedParams
) {
project(fullPath: $fullPath) {
namespace: project(fullPath: $fullPath) {
id
openedMergeRequests: mergeRequests(
state: opened

View File

@ -1,10 +1,12 @@
<script>
import { GlTableLite, GlEmptyState, GlLink } from '@gitlab/ui';
import { GlTableLite, GlEmptyState, GlLink, GlButton, GlModalDirective } from '@gitlab/ui';
import { FEATURE_NAME, FEATURE_FEEDBACK_ISSUE } from '~/ml/experiment_tracking/constants';
import * as constants from '~/ml/experiment_tracking/routes/experiments/index/constants';
import * as translations from '~/ml/experiment_tracking/routes/experiments/index/translations';
import ModelExperimentsHeader from '~/ml/experiment_tracking/components/model_experiments_header.vue';
import Pagination from '~/ml/experiment_tracking/components/pagination.vue';
import { MLFLOW_USAGE_MODAL_ID } from '../constants';
import MlflowModal from './mlflow_usage_modal.vue';
export default {
name: 'MlExperimentsIndexApp',
@ -14,6 +16,16 @@ export default {
GlTableLite,
GlEmptyState,
GlLink,
GlButton,
MlflowModal,
},
directives: {
GlModal: GlModalDirective,
},
provide() {
return {
mlflowTrackingUrl: this.mlflowTrackingUrl,
};
},
props: {
experiments: {
@ -28,6 +40,11 @@ export default {
type: String,
required: true,
},
mlflowTrackingUrl: {
type: String,
required: false,
default: '',
},
},
tableFields: constants.EXPERIMENTS_TABLE_FIELDS,
i18n: translations,
@ -47,6 +64,7 @@ export default {
FEATURE_FEEDBACK_ISSUE,
...constants,
},
mlflowModalId: MLFLOW_USAGE_MODAL_ID,
};
</script>
@ -69,12 +87,18 @@ export default {
<gl-empty-state
v-else
:title="$options.i18n.EMPTY_STATE_TITLE_LABEL"
:primary-button-text="$options.i18n.CREATE_NEW_LABEL"
:primary-button-link="$options.constants.CREATE_EXPERIMENT_HELP_PATH"
:svg-path="emptyStateSvgPath"
:svg-height="null"
:description="$options.i18n.EMPTY_STATE_DESCRIPTION_LABEL"
class="gl-py-8"
/>
>
<template #actions>
<gl-button v-gl-modal="$options.mlflowModalId" class="gl-mx-2 gl-mb-3 gl-mr-3">
{{ $options.i18n.CREATE_USING_MLFLOW_LABEL }}
</gl-button>
</template>
</gl-empty-state>
<mlflow-modal />
</div>
</template>

View File

@ -0,0 +1,73 @@
<script>
import { GlLink, GlModal, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import { CREATE_EXPERIMENT_HELP_PATH, MLFLOW_USAGE_MODAL_ID } from '../constants';
export default {
components: {
GlModal,
GlSprintf,
GlLink,
},
inject: ['mlflowTrackingUrl'],
computed: {
instruction() {
return {
label: s__('MlExperimentTracking|Creating an experiment'),
cmd: [
// eslint-disable-next-line @gitlab/require-i18n-strings
'import os',
'from mlflow import MlflowClient',
'',
`os.environ["MLFLOW_TRACKING_URI"] = "${this.mlflowTrackingUrl}"`,
'os.environ["MLFLOW_TRACKING_TOKEN"] = <your_gitlab_token>',
'',
'client = MlflowClient()',
'',
`client.create_experiment(name="<your_experiment_name>", tags={'key': 'value'})`,
].join('\n'),
};
},
},
MLFLOW_USAGE_MODAL_ID,
CREATE_EXPERIMENT_HELP_PATH,
};
</script>
<template>
<gl-modal
:modal-id="$options.MLFLOW_USAGE_MODAL_ID"
:title="s__('MlExperimentTracking|Using the MLflow client')"
hide-footer
no-focus-on-show
>
<p>{{ s__('MlExperimentTracking|Creating experiments using the MLflow client:') }}</p>
<div :key="instruction.label">
<label> {{ instruction.label }}</label>
<pre
class="code highlight gl-flex gl-border-none gl-p-2 gl-text-left gl-font-monospace"
data-testid="preview-code"
>
<code class="gl-grow">{{ instruction.cmd }}</code>
</pre>
</div>
<p>
<gl-sprintf
:message="
s__(
'MlExperimentTracking|To learn more about MLflow client compatibility, see %{linkStart}the documentation%{linkEnd}.',
)
"
>
<template #link="{ content }">
<gl-link :href="$options.CREATE_EXPERIMENT_HELP_PATH" target="_blank">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</p>
</gl-modal>
</template>

View File

@ -2,9 +2,9 @@ import { s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
export const CREATE_EXPERIMENT_HELP_PATH = helpPagePath(
'user/project/ml/experiment_tracking/index',
'user/project/ml/experiment_tracking/mlflow_client',
{
anchor: 'track-new-experiments-and-candidates',
anchor: 'model-experiments',
},
);
@ -15,3 +15,5 @@ export const EXPERIMENTS_TABLE_FIELDS = Object.freeze([
label: s__('MlExperimentTracking|Logged candidates for experiment'),
},
]);
export const MLFLOW_USAGE_MODAL_ID = 'experiment-tracking-mlflow-experiment-usage-model';

View File

@ -4,10 +4,14 @@ export const TITLE_LABEL = s__('MlExperimentTracking|Model experiments');
export const CREATE_NEW_LABEL = s__('MlExperimentTracking|Create a new experiment');
export const CREATE_USING_MLFLOW_LABEL = s__(
'MlExperimentTracking|Create an experiment using MLflow',
);
export const EMPTY_STATE_TITLE_LABEL = s__(
'MlExperimentTracking|Get started with model experiments!',
);
export const EMPTY_STATE_DESCRIPTION_LABEL = s__(
'MlExperimentTracking|Experiments keep track of comparable model candidates, and determine which parameters provides the best performance. Create experiments using the MLflow client',
'MlExperimentTracking|Experiments keep track of comparable model candidates, and determine which parameters provides the best performance.',
);

View File

@ -1,4 +1,7 @@
import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests';
import getMergeRequestsQuery from 'ee_else_ce/merge_requests/list/queries/project/get_merge_requests.query.graphql';
import getMergeRequestsCountsQuery from 'ee_else_ce/merge_requests/list/queries/project/get_merge_requests_counts.query.graphql';
import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
@ -23,4 +26,4 @@ addShortcutsExtension(ShortcutsNavigation);
initIssuableByEmail();
initCsvImportExportButtons();
mountMoreActionsDropdown();
mountMergeRequestListsApp();
mountMergeRequestListsApp({ getMergeRequestsQuery, getMergeRequestsCountsQuery });

View File

@ -8,11 +8,12 @@ const initIndexMlExperiments = () => {
return undefined;
}
const { experiments, pageInfo, emptyStateSvgPath } = element.dataset;
const { experiments, pageInfo, emptyStateSvgPath, mlflowTrackingUrl } = element.dataset;
const props = {
experiments: JSON.parse(experiments),
pageInfo: convertObjectPropsToCamelCase(JSON.parse(pageInfo)),
emptyStateSvgPath,
mlflowTrackingUrl,
};
return new Vue({

View File

@ -1,8 +1,9 @@
<script>
import { GlFormRadio, GlFormRadioGroup, GlIcon, GlLink, GlTooltipDirective } from '@gitlab/ui';
import { GlFormRadio, GlFormRadioGroup, GlLink, GlTooltipDirective } from '@gitlab/ui';
import { getWeekdayNames } from '~/lib/utils/datetime_utility';
import { __, s__, sprintf } from '~/locale';
import { DOCS_URL_IN_EE_DIR } from 'jh_else_ce/lib/utils/url_utility';
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
const KEY_EVERY_DAY = 'everyDay';
const KEY_EVERY_WEEK = 'everyWeek';
@ -19,8 +20,8 @@ export default {
components: {
GlFormRadio,
GlFormRadioGroup,
GlIcon,
GlLink,
HelpIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -163,10 +164,9 @@ export default {
>
{{ option.text }}
<gl-icon
<help-icon
v-if="showDailyLimitMessage(option)"
v-gl-tooltip.hover
name="question-o"
:title="scheduleDailyLimitMsg"
data-testid="daily-limit"
/>

View File

@ -265,6 +265,9 @@ export default {
workItemFullPath() {
return this.modalWorkItemFullPath || this.fullPath;
},
workItemProjectId() {
return this.workItem?.project?.id;
},
workItemLoading() {
return isEmpty(this.workItem) && this.$apollo.queries.workItem.loading;
},
@ -864,6 +867,8 @@ export default {
:work-item-iid="iid"
:work-item-full-path="workItemFullPath"
:work-item-type="workItem.workItemType.name"
:is-confidential-work-item="workItem.confidential"
:project-id="workItemProjectId"
/>
</div>
</div>

View File

@ -10,12 +10,15 @@ import {
} from '~/work_items/constants';
import { visitUrl } from '~/lib/utils/url_utility';
import { createBranchMRApiPathHelper } from '~/work_items/utils';
import { helpPagePath } from '~/helpers/help_page_helper';
import {
findInvalidBranchNameCharacters,
humanizeBranchValidationErrors,
} from '~/lib/utils/text_utility';
import getProjectRootRef from '~/work_items/graphql/get_project_root_ref.query.graphql';
import { s__, __ } from '~/locale';
import confidentialMergeRequestState from '~/confidential_merge_request/state';
import ProjectFormGroup from '~/confidential_merge_request/components/project_form_group.vue';
export default {
components: {
@ -23,6 +26,7 @@ export default {
GlFormInput,
GlFormGroup,
GlModal,
ProjectFormGroup,
},
i18n: {
sourceLabel: __('Source (branch or tag)'),
@ -40,6 +44,8 @@ export default {
checkingBranchValidity: __('Checking branch validity'),
},
createMRModalId: 'create-merge-request-modal',
mergeRequestHelpPagePath: helpPagePath('user/project/merge_requests/index.md'),
inject: ['groupPath'],
props: {
showModal: {
type: Boolean,
@ -72,6 +78,15 @@ export default {
type: String,
required: true,
},
projectId: {
type: String,
required: true,
},
isConfidentialWorkItem: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -132,26 +147,38 @@ export default {
return this.$options.i18n.branchNameExists;
},
modalTitle() {
return this.showBranchFlow
? this.$options.i18n.createBranch
: this.$options.i18n.createMergeRequest;
return this.createButtonText;
},
isSaveButtonDisabled() {
return (
this.invalidForm || (this.isConfidentialWorkItem && !this.canCreateConfidentialMergeRequest)
);
},
saveButtonAction() {
return {
text: this.createButtonText,
attributes: {
variant: 'confirm',
disabled: this.invalidForm,
disabled: this.isSaveButtonDisabled,
loading:
this.checkingSourceValidity || this.checkingBranchValidity || this.creatingBranch,
},
};
},
canCreateConfidentialMergeRequest() {
return (
this.isConfidentialWorkItem &&
Object.keys(confidentialMergeRequestState?.selectedProject).length > 0
);
},
cancelButtonAction() {
return {
text: this.$options.i18n.cancelLabel,
};
},
newForkPath() {
return `/${this.workItemFullPath}/-/forks/new`;
},
},
watch: {
showModal(newVal, oldVal) {
@ -190,7 +217,9 @@ export default {
async createBranch() {
try {
const endpoint = createBranchMRApiPathHelper.createBranch({
fullPath: this.workItemFullPath,
fullPath: this.isConfidentialWorkItem
? confidentialMergeRequestState.selectedProject.pathWithNamespace
: this.workItemFullPath,
workItemIid: this.workItemIid,
sourceBranch: this.defaultBranch,
targetBranch: this.branchName,
@ -198,7 +227,9 @@ export default {
this.creatingBranch = true;
const { data } = await axios.post(endpoint, {
confidential_issue_project_id: null,
confidential_issue_project_id: this.canCreateConfidentialMergeRequest
? this.projectId
: null,
});
this.$toast.show(__('Branch created.'), {
@ -224,7 +255,9 @@ export default {
async createMergeRequest() {
await this.createBranch();
const path = createBranchMRApiPathHelper.createMR({
fullPath: this.workItemFullPath,
fullPath: this.isConfidentialWorkItem
? confidentialMergeRequestState.selectedProject.pathWithNamespace
: this.workItemFullPath,
workItemIid: this.workItemIid,
sourceBranch: this.branchName,
targetBranch: this.defaultBranch,
@ -258,7 +291,11 @@ export default {
this.refCancelToken = axios.CancelToken.source();
const refsPath = createBranchMRApiPathHelper.getRefs({ fullPath: this.workItemFullPath });
const refsPath = createBranchMRApiPathHelper.getRefs({
fullPath: this.isConfidentialWorkItem
? confidentialMergeRequestState.selectedProject.pathWithNamespace
: this.workItemFullPath,
});
axios
.get(`${refsPath}${encodeURIComponent(refValue)}`, {
@ -325,6 +362,13 @@ export default {
@hide="hideModal"
>
<gl-form class="gl-text-left">
<project-form-group
v-if="isConfidentialWorkItem"
:namespace-path="groupPath"
:project-path="workItemFullPath"
:help-page-path="$options.mergeRequestHelpPagePath"
:new-fork-path="newForkPath"
/>
<gl-form-group
required
label-for="source-name-id"

View File

@ -36,11 +36,20 @@ export default {
type: String,
required: true,
},
projectId: {
type: String,
required: true,
},
workItemIid: {
type: String,
required: false,
default: null,
},
isConfidentialWorkItem: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -81,6 +90,11 @@ export default {
buttonText() {
return this.checkingBranchAvailibility
? __('Checking branch availability...')
: this.createMergeRequestButtonText;
},
createMergeRequestButtonText() {
return this.isConfidentialWorkItem
? __('Create confidential merge request')
: this.$options.i18n.createMergeRequest;
},
},
@ -128,6 +142,8 @@ export default {
:work-item-id="workItemId"
:work-item-type="workItemType"
:work-item-full-path="workItemFullPath"
:is-confidential-work-item="isConfidentialWorkItem"
:project-id="projectId"
@hideModal="toggleCreateModal(false)"
@fetchedPermissions="updatePermissions"
/>

View File

@ -149,6 +149,12 @@ export default {
showAddButton() {
return this.workItemsAlphaEnabled && this.canUpdate && this.showCreateOptions;
},
isConfidentialWorkItem() {
return this.workItem?.confidential;
},
projectId() {
return this.workItem?.project?.id;
},
addItemsActions() {
return [
{
@ -275,6 +281,8 @@ export default {
:work-item-id="workItemId"
:work-item-type="workItemTypeName"
:work-item-full-path="workItemFullPath"
:is-confidential-work-item="isConfidentialWorkItem"
:project-id="projectId"
@hideModal="toggleCreateModal(false)"
@fetchedPermissions="updatePermissions"
/>

View File

@ -561,6 +561,7 @@ export const setNewWorkItemCache = async (
webUrl: `${baseURL}/groups/gitlab-org/-/work_items/new`,
reference: '',
createNoteEmail: null,
project: null,
namespace: {
id: newWorkItemPath,
fullPath,
@ -608,5 +609,6 @@ export const optimisticUserPermissions = {
setWorkItemMetadata: false,
createNote: false,
adminWorkItemLink: false,
markNoteAsInternal: false,
__typename: 'WorkItemPermissions',
};

View File

@ -14,6 +14,9 @@ fragment WorkItem on WorkItem {
webUrl
reference(full: true)
createNoteEmail
project {
id
}
namespace {
id
fullPath

View File

@ -17,6 +17,7 @@ fragment WorkItemHierarchy on WorkItem {
setWorkItemMetadata
createNote
adminWorkItemLink
markNoteAsInternal
}
widgets {
type

View File

@ -30,6 +30,7 @@ export const initWorkItemsRoot = ({ workItemType, workspaceType } = {}) => {
canAdminLabel,
fullPath,
groupPath,
groupId,
hasIssueWeightsFeature,
iid,
issuesListPath,
@ -57,6 +58,8 @@ export const initWorkItemsRoot = ({ workItemType, workspaceType } = {}) => {
groupIssuesPath,
labelsFetchPath,
hasLinkedItemsEpicsFeature,
canCreateProjects,
newProjectPath,
} = el.dataset;
const isGroup = workspaceType === WORKSPACE_GROUP;
@ -102,6 +105,7 @@ export const initWorkItemsRoot = ({ workItemType, workspaceType } = {}) => {
canAdminLabel,
fullPath,
isGroup,
isProject: !isGroup,
hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature),
hasOkrsFeature: parseBoolean(hasOkrsFeature),
hasSubepicsFeature: parseBoolean(hasSubepicsFeature),
@ -115,6 +119,7 @@ export const initWorkItemsRoot = ({ workItemType, workspaceType } = {}) => {
newCommentTemplatePaths: JSON.parse(newCommentTemplatePaths),
reportAbusePath,
groupPath,
groupId,
initialSort,
isSignedIn: parseBoolean(isSignedIn),
workItemType: listWorkItemType,
@ -127,6 +132,9 @@ export const initWorkItemsRoot = ({ workItemType, workspaceType } = {}) => {
groupIssuesPath,
labelsFetchPath,
hasLinkedItemsEpicsFeature: parseBoolean(hasLinkedItemsEpicsFeature),
canCreateProjects: parseBoolean(canCreateProjects),
newIssuePath: '',
newProjectPath,
},
mounted() {
performanceMarkAndMeasure({

View File

@ -31,6 +31,8 @@ class GraphqlChannel < ApplicationCable::Channel # rubocop:disable Gitlab/Namesp
end
def unsubscribed
return if @subscription_ids.blank?
@subscription_ids.each do |sid|
GitlabSchema.subscriptions.delete_subscription(sid)
end

View File

@ -47,7 +47,7 @@ module Groups
current_user: current_user,
parent_group: parent,
params: safe_params
).execute.page(params[:page])
).execute
end
private

View File

@ -41,7 +41,6 @@ module Ci
end
def link_composite_identity!(jwt)
return unless Feature.enabled?(:composite_identity_in_ci, jwt.job&.user)
return unless jwt.scoped_user
# We prefer not to use `link_from_job` when we have the JWT because

View File

@ -29,19 +29,19 @@ class GroupDescendantsFinder
end
def execute
# The children array might be extended with the ancestors of projects and
# subgroups when filtering. In that case, take the maximum so the array does
# not get limited otherwise, allow paginating through all results.
#
all_required_elements = children
# First paginate and then include the ancestors of the filtered children to:
# - Avoid truncating children or preloaded ancestors due to per_page limit
# - Ensure correct pagination headers are returned
all_required_elements = Kaminari.paginate_array(children, total_count: paginator.total_count)
.page(page)
preloaded_ancestors = []
if params[:filter]
all_required_elements |= ancestors_of_filtered_subgroups
all_required_elements |= ancestors_of_filtered_projects
preloaded_ancestors |= ancestors_of_filtered_subgroups
preloaded_ancestors |= ancestors_of_filtered_projects
end
total_count = [all_required_elements.size, paginator.total_count].max
Kaminari.paginate_array(all_required_elements, total_count: total_count)
all_required_elements.concat(preloaded_ancestors - children)
end
private

View File

@ -83,6 +83,7 @@ class MergeRequestsFinder < IssuableFinder
items = by_source_project_id(items)
items = by_resource_event_state(items)
items = by_assignee_or_reviewer(items)
items = by_blob_path(items)
by_approved(items)
end
@ -280,6 +281,15 @@ class MergeRequestsFinder < IssuableFinder
)
end
def by_blob_path(items)
blob_path = params[:blob_path]
return items unless blob_path
return items.none unless params.project
items.by_blob_path(blob_path)
end
def parse_datetime(input)
# NOTE: Input from GraphQL query is a Time object already.
# Just return DateTime object for consistency instead of trying to parse it.

View File

@ -22,6 +22,10 @@ module ResolvesMergeRequests
args[:include_subgroups] = true
end
args.delete(:blob_path) if Feature.disabled?(:filter_blob_path, current_user)
validate_blob_path!(args)
rewrite_param_name(args, :reviewer_wildcard_id, :reviewer_id)
rewrite_param_name(args, :assignee_wildcard_id, :assignee_id)
@ -63,6 +67,29 @@ module ResolvesMergeRequests
params[new_name] = params.delete(old_name) if params && params[old_name].present?
end
def validate_blob_path!(args)
return if args[:blob_path].blank?
required_fields = {
target_branch: 'targetBranches',
state: 'state',
created_after: 'createdAfter'
}
required_fields.each do |key, field_name|
if args[key].blank?
raise Gitlab::Graphql::Errors::ArgumentError, "#{field_name} field must be specified to filter by blobPath"
end
end
# It's limited for performance reasons
created_after = args[:created_after].to_datetime
return if created_after.after?(30.days.ago)
raise Gitlab::Graphql::Errors::ArgumentError,
'createdAfter must be within the last 30 days to filter by blobPath'
end
def non_stable_cursor_sort?(sort)
NON_STABLE_CURSOR_SORTS.include?(sort)
end

View File

@ -87,9 +87,14 @@ module Resolvers
Available only when the feature flag `mr_approved_filter` is enabled.
DESC
argument :subscribed, Types::Issuables::SubscriptionStatusEnum,
description: 'Merge requests the current user is subscribed to.',
required: false
argument :blob_path, GraphQL::Types::String,
required: false,
experiment: { milestone: '17.7' },
description: <<~DESC
Path of the blob changed in merge request.
Requires state, targetBranches, and createdAfter arguments.
Available only when the feature flag `filter_blob_path` is enabled.
DESC
argument :created_after, Types::TimeType,
required: false,
@ -149,6 +154,9 @@ module Resolvers
description: 'Sort merge requests by the criteria.',
required: false,
default_value: :created_desc
argument :subscribed, Types::Issuables::SubscriptionStatusEnum,
description: 'Merge requests the current user is subscribed to.',
required: false
negated do
argument :approved_by, [GraphQL::Types::String],

View File

@ -439,91 +439,208 @@ module MergeRequestsHelper
{ new_comment_template_paths: new_comment_template_paths(@project.group, @project).to_json }
end
def merge_request_dashboard_data
def merge_request_dashboard_data_v2
{
tabs: [
{
title: _('Needs attention'),
title: 'Active',
key: '',
lists: [
{
id: 'returned_to_you',
title: _('Returned to you'),
helpContent: _('Reviewers left feedback, or requested changes from you, on these merge requests.'),
query: 'assignedMergeRequests',
variables: {
reviewStates: %w[REVIEWED REQUESTED_CHANGES]
[
{
id: 'returned_to_you',
title: _('Returned to you'),
helpContent: _('Reviewers left feedback, or requested changes from you, on these merge requests.'),
query: 'assignedMergeRequests',
variables: {
reviewStates: %w[REVIEWED REQUESTED_CHANGES]
}
},
{
id: 'reviews_requested',
title: _('Review requested'),
helpContent: _('These merge requests need a review from you.'),
query: 'reviewRequestedMergeRequests',
variables: {
reviewStates: %w[UNAPPROVED UNREVIEWED REVIEW_STARTED]
}
},
{
id: 'assigned_to_you',
title: _('Assigned to you'),
helpContent: _("You're assigned to these merge requests, but they don't have reviewers yet."),
query: 'assignedMergeRequests',
variables: {
reviewerWildcardId: 'NONE'
}
}
},
{
id: 'reviews_requested',
title: _('Reviews requested'),
helpContent: _('These merge requests need a review from you.'),
query: 'reviewRequestedMergeRequests',
variables: {
reviewStates: %w[UNAPPROVED UNREVIEWED REVIEW_STARTED]
],
[
{
id: 'waiting_for_author',
title: _('Waiting for author'),
hideCount: true,
helpContent: _(
'Your assigned merge requests that are waiting for approvals, ' \
'and reviews you have requested changes for.'
),
query: 'reviewRequestedMergeRequests',
variables: {
reviewStates: %w[REVIEWED REQUESTED_CHANGES]
}
},
{
id: 'waiting_for_reviewer',
title: _('Waiting for reviewer'),
hideCount: true,
helpContent: _(
'Your assigned merge requests that are waiting for approvals, ' \
'and reviews you have requested changes for.'
),
query: 'assignedMergeRequests',
variables: {
reviewStates: %w[UNREVIEWED UNAPPROVED REVIEW_STARTED]
}
},
{
id: 'approved_by_you',
title: _('Approved by you'),
hideCount: true,
helpContent: _("You've reviewed and approved these merge requests."),
query: 'reviewRequestedMergeRequests',
variables: {
reviewState: 'APPROVED'
}
},
{
id: 'approved_by_others',
title: _('Approved by others'),
hideCount: true,
helpContent: _('Includes all merge requests you are assigned to and a reviewer has approved.'),
query: 'assignedMergeRequests',
variables: {
reviewState: 'APPROVED'
}
}
},
{
id: 'assigned_to_you',
title: _('Assigned to you'),
helpContent: _("You're assigned to these merge requests, but they don't have reviewers yet."),
query: 'assignedMergeRequests',
variables: {
reviewerWildcardId: 'NONE'
}
}
]
]
},
{
title: _('Following'),
key: 'following',
title: 'Merged',
key: 'merged',
lists: [
{
id: 'waikting_for_others',
title: _('Waiting for others'),
helpContent: _(
'Your assigned merge requests that are waiting for approvals, ' \
'and reviews you have requested changes for.'
),
query: 'assigneeOrReviewerMergeRequests',
variables: {
reviewerReviewStates: %w[REVIEWED REQUESTED_CHANGES],
assignedReviewStates: %w[UNREVIEWED UNAPPROVED REVIEW_STARTED]
}
},
{
id: 'approved_by_you',
title: _('Approved by you'),
helpContent: _("You've reviewed and approved these merge requests."),
query: 'reviewRequestedMergeRequests',
variables: {
reviewState: 'APPROVED'
}
},
{
id: 'approved_by_others',
title: _('Approved by others'),
helpContent: _('Includes all merge requests you are assigned to and a reviewer has approved.'),
query: 'assignedMergeRequests',
variables: {
reviewState: 'APPROVED'
}
},
{
[{
id: 'merged_recently',
title: _('Merged recently'),
helpContent: _('These merge requests merged after %{date}. You were an assignee or a reviewer.') % {
date: 2.weeks.ago.to_date.to_formatted_s(:long)
},
hideCount: true,
query: 'assigneeOrReviewerMergeRequests',
variables: {
state: 'merged',
mergedAfter: 2.weeks.ago.to_time.iso8601,
sort: 'MERGED_AT_DESC'
}
}
}]
]
}
]
}
end
def merge_request_dashboard_data
if ::Feature.enabled?(:merge_request_dashboard_new_lists, current_user, type: :wip)
return merge_request_dashboard_data_v2
end
{
tabs: [
{
title: _('Needs attention'),
key: '',
lists: [
[
{
id: 'returned_to_you',
title: _('Returned to you'),
helpContent: _('Reviewers left feedback, or requested changes from you, on these merge requests.'),
query: 'assignedMergeRequests',
variables: {
reviewStates: %w[REVIEWED REQUESTED_CHANGES]
}
},
{
id: 'reviews_requested',
title: _('Reviews requested'),
helpContent: _('These merge requests need a review from you.'),
query: 'reviewRequestedMergeRequests',
variables: {
reviewStates: %w[UNAPPROVED UNREVIEWED REVIEW_STARTED]
}
},
{
id: 'assigned_to_you',
title: _('Assigned to you'),
helpContent: _("You're assigned to these merge requests, but they don't have reviewers yet."),
query: 'assignedMergeRequests',
variables: {
reviewerWildcardId: 'NONE'
}
}
]
]
},
{
title: _('Following'),
key: 'following',
lists: [
[
{
id: 'waikting_for_others',
title: _('Waiting for others'),
helpContent: _(
'Your assigned merge requests that are waiting for approvals, ' \
'and reviews you have requested changes for.'
),
query: 'assigneeOrReviewerMergeRequests',
variables: {
reviewerReviewStates: %w[REVIEWED REQUESTED_CHANGES],
assignedReviewStates: %w[UNREVIEWED UNAPPROVED REVIEW_STARTED]
}
},
{
id: 'approved_by_you',
title: _('Approved by you'),
helpContent: _("You've reviewed and approved these merge requests."),
query: 'reviewRequestedMergeRequests',
variables: {
reviewState: 'APPROVED'
}
},
{
id: 'approved_by_others',
title: _('Approved by others'),
helpContent: _('Includes all merge requests you are assigned to and a reviewer has approved.'),
query: 'assignedMergeRequests',
variables: {
reviewState: 'APPROVED'
}
},
{
id: 'merged_recently',
title: _('Merged recently'),
helpContent: _('These merge requests merged after %{date}. You were an assignee or a reviewer.') % {
date: 2.weeks.ago.to_date.to_formatted_s(:long)
},
hideCount: true,
query: 'assigneeOrReviewerMergeRequests',
variables: {
state: 'merged',
mergedAfter: 2.weeks.ago.to_time.iso8601,
sort: 'MERGED_AT_DESC'
}
}
]
]
}
]

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Projects
module Ml
module MlflowHelper
def mlflow_tracking_url(project)
path = api_v4_projects_ml_mlflow_api_2_0_mlflow_registered_models_create_path(id: project.id)
path = path.delete_suffix('api/2.0/mlflow/registered-models/create')
expose_url(path)
end
end
end
end

View File

@ -138,14 +138,6 @@ module Projects
def to_json(data)
Gitlab::Json.generate(data.deep_transform_keys { |k| k.to_s.camelize(:lower) })
end
def mlflow_tracking_url(project)
path = api_v4_projects_ml_mlflow_api_2_0_mlflow_registered_models_create_path(id: project.id)
path = path.delete_suffix('api/2.0/mlflow/registered-models/create')
expose_url(path)
end
end
end
end

View File

@ -400,7 +400,7 @@ module SidebarsHelper
({ title: s_('Navigation|Preferences'), link: profile_preferences_path, icon: 'preferences' } if current_user)
]
if current_user&.can_admin_all_resources?
if display_admin_area_link?
links.append(
{ title: s_('Navigation|Admin area'), link: admin_root_path, icon: 'admin' }
)
@ -489,6 +489,10 @@ module SidebarsHelper
def terms_link
Gitlab::CurrentSettings.terms ? '/-/users/terms' : nil
end
def display_admin_area_link?
current_user&.can_admin_all_resources?
end
end
SidebarsHelper.prepend_mod_with('SidebarsHelper')

View File

@ -20,7 +20,10 @@ module WorkItemsHelper
default_branch: resource_parent.is_a?(Project) ? resource_parent.default_branch_or_main : nil,
initial_sort: current_user&.user_preference&.issues_sort,
is_signed_in: current_user.present?.to_s,
show_new_issue_link: can?(current_user, :create_work_item, group).to_s
show_new_issue_link: can?(current_user, :create_work_item, group).to_s,
can_create_projects: can?(current_user, :create_projects, group).to_s,
new_project_path: new_project_path(namespace_id: group&.id),
group_id: group&.id
}
end

View File

@ -491,6 +491,11 @@ class MergeRequest < ApplicationRecord
.merge(ResourceStateEvent.merged_with_no_event_source)
}
scope :by_blob_path, ->(path) do
joins(latest_merge_request_diff: :merge_request_diff_files)
.where(merge_request_diff_files: { old_path: path })
end
def self.total_time_to_merge
join_metrics
.where(

View File

@ -2562,6 +2562,10 @@ class User < ApplicationRecord
{}
end
def add_admin_note(new_note)
self.note = "#{new_note}\n#{self.note}"
end
protected
# override, from Devise::Validatable

View File

@ -20,6 +20,14 @@ class ServiceResponse
)
end
# This is used to help wrap old service responses that were just hashes
def self.from_legacy_hash(response)
return response if response.is_a?(ServiceResponse)
return ServiceResponse.new(**response) if response.is_a?(Hash)
raise ArgumentError, "argument must be a ServiceResponse or a Hash"
end
attr_reader :status, :message, :http_status, :payload, :reason
def initialize(status:, message: nil, payload: {}, http_status: nil, reason: nil)

View File

@ -26,7 +26,7 @@
= render 'shared/new_project_item_vue_select'
- if merge_request_dashboard_enabled?(current_user) && !current_page?(merge_requests_search_dashboard_path)
#js-merge-request-dashboard{ data: { base_path: merge_requests_dashboard_path, merge_requests_search_dashboard_path: merge_requests_search_dashboard_path(assignee_username: current_user.username), initial_data: merge_request_dashboard_data.to_json } }
#js-merge-request-dashboard{ data: { base_path: merge_requests_dashboard_path, new_lists_enabled: ::Feature.enabled?(:merge_request_dashboard_new_lists, current_user, type: :wip).to_s, merge_requests_search_dashboard_path: merge_requests_search_dashboard_path(assignee_username: current_user.username), initial_data: merge_request_dashboard_data.to_json } }
= gl_loading_icon(size: 'lg')
- if !merge_request_dashboard_enabled?(current_user) || current_page?(merge_requests_search_dashboard_path)

View File

@ -5,4 +5,5 @@
experiments: experiments_as_data(@project, @experiments),
page_info: formatted_page_info(@page_info),
empty_state_svg_path: image_path('illustrations/status/status-new-md.svg'),
mlflow_tracking_url: mlflow_tracking_url(@project),
} }

View File

@ -0,0 +1,10 @@
---
name: q_onbarding_updated
description: Amazon Q instance settings changed
introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/508250
introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175501
feature_category: ai_framework
milestone: '17.8'
saved_to_database: true
streamed: true
scope: [Instance]

View File

@ -1,8 +0,0 @@
---
name: hard_limit_daily_phone_verifications
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138627
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/433972
milestone: '16.8'
type: development
group: group::anti-abuse
default_enabled: false

View File

@ -1,9 +1,9 @@
---
name: composite_identity_in_ci
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/506641
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174051
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/507803
name: filter_blob_path
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/499245
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172997
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/505449
milestone: '17.7'
group: group::pipeline execution
group: group::source code
type: gitlab_com_derisk
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: telesign_intelligence
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137739
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432757
milestone: '16.7'
type: ops
group: group::anti-abuse
default_enabled: true

View File

@ -0,0 +1,9 @@
---
name: merge_request_dashboard_new_lists
feature_issue_url:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173399
rollout_issue_url:
milestone: '17.7'
group: group::code review
type: wip
default_enabled: false

View File

@ -980,6 +980,15 @@ Gitlab.ee do
Settings.cron_jobs['click_house_audit_events_sync_worker']['cron'] ||= "*/3 * * * *"
Settings.cron_jobs['click_house_audit_events_sync_worker']['job_class'] = 'ClickHouse::AuditEventsSyncWorker'
Settings.cron_jobs['gitlab_subscriptions_offline_cloud_license_provision_worker']['status'] = 'disabled'
Settings.cron_jobs['send_recurring_notifications_worker'] ||= {}
Settings.cron_jobs['send_recurring_notifications_worker']['cron'] ||= '0 7 * * *'
Settings.cron_jobs['send_recurring_notifications_worker']['job_class'] =
'ComplianceManagement::Pipl::SendRecurringNotificationsWorker'
Settings.cron_jobs['block_pipl_users_worker'] ||= {}
Settings.cron_jobs['block_pipl_users_worker']['cron'] ||= '0 8 * * *'
Settings.cron_jobs['block_pipl_users_worker']['job_class'] =
'ComplianceManagement::Pipl::BlockPiplUsersWorker'
end
Gitlab.jh do

View File

@ -186,13 +186,13 @@ successfully, you must replicate their data using some other means.
| Feature | Replicated (added in GitLab version) | Verified (added in GitLab version) | GitLab-managed object storage replication (added in GitLab version) | GitLab-managed object storage verification (added in GitLab version) | Notes |
|:----------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------|:------------------------------------------------------------------------------|:--------------------------------------------------------------------------------|:--------------------------------------------------------------------------------|:------|
| [Application data in PostgreSQL](../../postgresql/index.md) | **Yes** (10.2) | **Yes** (10.2) | Not applicable | Not applicable | |
| [Project repository](../../../user/project/repository/index.md) | **Yes** (10.2) | **Yes** (10.7) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | Migrated to [self-service framework](../../../development/geo/framework.md) in 16.2. See GitLab issue [#367925](https://gitlab.com/gitlab-org/gitlab/-/issues/367925) for more details.<br /><br />Behind feature flag `geo_project_repository_replication`, enabled by default in (16.3).<br /><br /> All projects, including [archived projects](../../../user/project/working_with_projects.md#archive-a-project), are replicated. |
| [Project wiki repository](../../../user/project/wiki/index.md) | **Yes** (10.2)<sup>2</sup> | **Yes** (10.7)<sup>2</sup> | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | Migrated to [self-service framework](../../../development/geo/framework.md) in 15.11. See GitLab issue [#367925](https://gitlab.com/gitlab-org/gitlab/-/issues/367925) for more details.<br /><br />Behind feature flag `geo_project_wiki_repository_replication`, enabled by default in (15.11). |
| [Group wiki repository](../../../user/project/wiki/group.md) | [**Yes** (13.10)](https://gitlab.com/gitlab-org/gitlab/-/issues/208147) | [**Yes** (16.3)](https://gitlab.com/gitlab-org/gitlab/-/issues/323897) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | Behind feature flag `geo_group_wiki_repository_replication`, enabled by default. |
| [Project repository](../../../user/project/repository/index.md) | **Yes** (10.2) | **Yes** (10.7) | Not applicable | Not applicable | Migrated to [self-service framework](../../../development/geo/framework.md) in 16.2. See GitLab issue [#367925](https://gitlab.com/gitlab-org/gitlab/-/issues/367925) for more details.<br /><br />Behind feature flag `geo_project_repository_replication`, enabled by default in (16.3).<br /><br /> All projects, including [archived projects](../../../user/project/working_with_projects.md#archive-a-project), are replicated. |
| [Project wiki repository](../../../user/project/wiki/index.md) | **Yes** (10.2)<sup>2</sup> | **Yes** (10.7)<sup>2</sup> | Not applicable | Not applicable | Migrated to [self-service framework](../../../development/geo/framework.md) in 15.11. See GitLab issue [#367925](https://gitlab.com/gitlab-org/gitlab/-/issues/367925) for more details.<br /><br />Behind feature flag `geo_project_wiki_repository_replication`, enabled by default in (15.11). |
| [Group wiki repository](../../../user/project/wiki/group.md) | [**Yes** (13.10)](https://gitlab.com/gitlab-org/gitlab/-/issues/208147) | [**Yes** (16.3)](https://gitlab.com/gitlab-org/gitlab/-/issues/323897) | Not applicable | Not applicable | Behind feature flag `geo_group_wiki_repository_replication`, enabled by default. |
| [Uploads](../../uploads.md) | **Yes** (10.2) | **Yes** (14.6) | [**Yes** (15.1)](https://gitlab.com/groups/gitlab-org/-/epics/5551) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | Replication is behind the feature flag `geo_upload_replication`, enabled by default. Verification was behind the feature flag `geo_upload_verification`, removed in 14.8. |
| [LFS objects](../../lfs/index.md) | **Yes** (10.2) | **Yes** (14.6) | [**Yes** (15.1)](https://gitlab.com/groups/gitlab-org/-/epics/5551) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | GitLab versions 11.11.x and 12.0.x are affected by [a bug that prevents any new LFS objects from replicating](https://gitlab.com/gitlab-org/gitlab/-/issues/32696).<br /><br />Replication is behind the feature flag `geo_lfs_object_replication`, enabled by default. Verification was behind the feature flag `geo_lfs_object_verification`, removed in 14.7. |
| [Personal snippets](../../../user/snippets.md) | **Yes** (10.2) | **Yes** (10.2) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | |
| [Project snippets](../../../user/snippets.md) | **Yes** (10.2) | **Yes** (10.2) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | |
| [Personal snippets](../../../user/snippets.md) | **Yes** (10.2) | **Yes** (10.2) | Not applicable | Not applicable | |
| [Project snippets](../../../user/snippets.md) | **Yes** (10.2) | **Yes** (10.2) | Not applicable | Not applicable | |
| [CI job artifacts](../../../ci/jobs/job_artifacts.md) | **Yes** (10.4) | **Yes** (14.10) | [**Yes** (15.1)](https://gitlab.com/groups/gitlab-org/-/epics/5551) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | Verification is behind the feature flag `geo_job_artifact_replication`, enabled by default in 14.10. |
| [CI Pipeline Artifacts](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/ci/pipeline_artifact.rb) | [**Yes** (13.11)](https://gitlab.com/gitlab-org/gitlab/-/issues/238464) | [**Yes** (13.11)](https://gitlab.com/gitlab-org/gitlab/-/issues/238464) | [**Yes** (15.1)](https://gitlab.com/groups/gitlab-org/-/epics/5551) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | Persists additional artifacts after a pipeline completes. |
| [CI Secure Files](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/ci/secure_file.rb) | [**Yes** (15.3)](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91430) | [**Yes** (15.3)](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91430) | [**Yes** (15.3)](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91430) | [**Yes** (16.4)<sup>3</sup>](https://gitlab.com/groups/gitlab-org/-/epics/8056) | Verification is behind the feature flag `geo_ci_secure_file_replication`, enabled by default in 15.3. |

View File

@ -31,10 +31,9 @@ Install one of the following GitLab-approved LLM models:
| Mistral | [Mixtral 8x7B-it v0.1](https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| Mistral | [Mixtral 8x22B-it v0.1](https://huggingface.co/mistralai/Mixtral-8x22B-Instruct-v0.1) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| Claude 3 | [Claude 3.5 Sonnet](https://www.anthropic.com/news/claude-3-5-sonnet) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| GPT | [GPT-3.5-Turbo](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-35) | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
| GPT | [GPT-4 Turbo](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4) | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
| GPT | [GPT-4o](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4o-and-gpt-4-turbo) | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
| GPT | [GPT-4o-mini](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4o-and-gpt-4-turbo) | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
| GPT | [GPT-4 Turbo](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| GPT | [GPT-4o](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4o-and-gpt-4-turbo) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| GPT | [GPT-4o-mini](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4o-and-gpt-4-turbo) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
The following models are under evaluation, and support is limited:

View File

@ -1692,6 +1692,8 @@ Input type: `AdminSidekiqQueuesDeleteJobsInput`
| <a id="mutationadminsidekiqqueuesdeletejobsremoteip"></a>`remoteIp` | [`String`](#string) | Delete jobs matching remote_ip in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsrootcallerid"></a>`rootCallerId` | [`String`](#string) | Delete jobs matching root_caller_id in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsrootnamespace"></a>`rootNamespace` | [`String`](#string) | Delete jobs matching root_namespace in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsscopeduser"></a>`scopedUser` | [`String`](#string) | Delete jobs matching scoped_user in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsscopeduserid"></a>`scopedUserId` | [`String`](#string) | Delete jobs matching scoped_user_id in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobssidekiqdestinationshardredis"></a>`sidekiqDestinationShardRedis` | [`String`](#string) | Delete jobs matching sidekiq_destination_shard_redis in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobssubscriptionplan"></a>`subscriptionPlan` | [`String`](#string) | Delete jobs matching subscription_plan in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsuser"></a>`user` | [`String`](#string) | Delete jobs matching user in the context metadata. |
@ -18254,6 +18256,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="addonuserassignedmergerequestsapprovedby"></a>`approvedBy` | [`[String!]`](#string) | Usernames of the approvers. |
| <a id="addonuserassignedmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="addonuserassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="addonuserassignedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="addonuserassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="addonuserassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="addonuserassignedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -18309,6 +18312,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="addonuserauthoredmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="addonuserauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="addonuserauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="addonuserauthoredmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="addonuserauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="addonuserauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="addonuserauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -18423,6 +18427,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="addonuserreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="addonuserreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="addonuserreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="addonuserreviewrequestedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="addonuserreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="addonuserreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="addonuserreviewrequestedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -19232,6 +19237,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="autocompleteduserassignedmergerequestsapprovedby"></a>`approvedBy` | [`[String!]`](#string) | Usernames of the approvers. |
| <a id="autocompleteduserassignedmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="autocompleteduserassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="autocompleteduserassignedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="autocompleteduserassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="autocompleteduserassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="autocompleteduserassignedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -19287,6 +19293,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="autocompleteduserauthoredmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="autocompleteduserauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="autocompleteduserauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="autocompleteduserauthoredmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="autocompleteduserauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="autocompleteduserauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="autocompleteduserauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -19413,6 +19420,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="autocompleteduserreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="autocompleteduserreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="autocompleteduserreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="autocompleteduserreviewrequestedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="autocompleteduserreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="autocompleteduserreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="autocompleteduserreviewrequestedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -21787,6 +21795,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="currentuserassignedmergerequestsapprovedby"></a>`approvedBy` | [`[String!]`](#string) | Usernames of the approvers. |
| <a id="currentuserassignedmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="currentuserassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="currentuserassignedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="currentuserassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="currentuserassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="currentuserassignedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -21845,6 +21854,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="currentuserassigneeorreviewermergerequestsapprovedby"></a>`approvedBy` | [`[String!]`](#string) | Usernames of the approvers. |
| <a id="currentuserassigneeorreviewermergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="currentuserassigneeorreviewermergerequestsassignedreviewstates"></a>`assignedReviewStates` | [`[MergeRequestReviewState!]`](#mergerequestreviewstate) | Reviewer states for merge requests the current user is assigned to. |
| <a id="currentuserassigneeorreviewermergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="currentuserassigneeorreviewermergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="currentuserassigneeorreviewermergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="currentuserassigneeorreviewermergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -21899,6 +21909,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="currentuserauthoredmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="currentuserauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="currentuserauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="currentuserauthoredmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="currentuserauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="currentuserauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="currentuserauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -22013,6 +22024,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="currentuserreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="currentuserreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="currentuserreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="currentuserreviewrequestedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="currentuserreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="currentuserreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="currentuserreviewrequestedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -25552,6 +25564,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="groupmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="groupmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="groupmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="groupmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="groupmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="groupmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="groupmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -27777,6 +27790,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="mergerequestassigneeassignedmergerequestsapprovedby"></a>`approvedBy` | [`[String!]`](#string) | Usernames of the approvers. |
| <a id="mergerequestassigneeassignedmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="mergerequestassigneeassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestassigneeassignedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="mergerequestassigneeassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="mergerequestassigneeassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="mergerequestassigneeassignedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -27832,6 +27846,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="mergerequestassigneeauthoredmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="mergerequestassigneeauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestassigneeauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="mergerequestassigneeauthoredmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="mergerequestassigneeauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="mergerequestassigneeauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="mergerequestassigneeauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -27946,6 +27961,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="mergerequestassigneereviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestassigneereviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="mergerequestassigneereviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestassigneereviewrequestedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="mergerequestassigneereviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="mergerequestassigneereviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="mergerequestassigneereviewrequestedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -28183,6 +28199,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="mergerequestauthorassignedmergerequestsapprovedby"></a>`approvedBy` | [`[String!]`](#string) | Usernames of the approvers. |
| <a id="mergerequestauthorassignedmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="mergerequestauthorassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestauthorassignedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="mergerequestauthorassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="mergerequestauthorassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="mergerequestauthorassignedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -28238,6 +28255,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="mergerequestauthorauthoredmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="mergerequestauthorauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestauthorauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="mergerequestauthorauthoredmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="mergerequestauthorauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="mergerequestauthorauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="mergerequestauthorauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -28352,6 +28370,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="mergerequestauthorreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestauthorreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="mergerequestauthorreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestauthorreviewrequestedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="mergerequestauthorreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="mergerequestauthorreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="mergerequestauthorreviewrequestedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -28635,6 +28654,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="mergerequestparticipantassignedmergerequestsapprovedby"></a>`approvedBy` | [`[String!]`](#string) | Usernames of the approvers. |
| <a id="mergerequestparticipantassignedmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="mergerequestparticipantassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestparticipantassignedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="mergerequestparticipantassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="mergerequestparticipantassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="mergerequestparticipantassignedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -28690,6 +28710,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="mergerequestparticipantauthoredmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="mergerequestparticipantauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestparticipantauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="mergerequestparticipantauthoredmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="mergerequestparticipantauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="mergerequestparticipantauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="mergerequestparticipantauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -28804,6 +28825,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="mergerequestparticipantreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestparticipantreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="mergerequestparticipantreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestparticipantreviewrequestedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="mergerequestparticipantreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="mergerequestparticipantreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="mergerequestparticipantreviewrequestedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -29060,6 +29082,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="mergerequestreviewerassignedmergerequestsapprovedby"></a>`approvedBy` | [`[String!]`](#string) | Usernames of the approvers. |
| <a id="mergerequestreviewerassignedmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="mergerequestreviewerassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestreviewerassignedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="mergerequestreviewerassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="mergerequestreviewerassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="mergerequestreviewerassignedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -29115,6 +29138,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="mergerequestreviewerauthoredmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="mergerequestreviewerauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestreviewerauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="mergerequestreviewerauthoredmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="mergerequestreviewerauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="mergerequestreviewerauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="mergerequestreviewerauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -29229,6 +29253,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="mergerequestreviewerreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestreviewerreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="mergerequestreviewerreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestreviewerreviewrequestedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="mergerequestreviewerreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="mergerequestreviewerreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="mergerequestreviewerreviewrequestedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -32560,6 +32585,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="projectmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="projectmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="projectmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="projectmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="projectmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="projectmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="projectmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -35865,6 +35891,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="usercoreassignedmergerequestsapprovedby"></a>`approvedBy` | [`[String!]`](#string) | Usernames of the approvers. |
| <a id="usercoreassignedmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="usercoreassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="usercoreassignedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="usercoreassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="usercoreassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="usercoreassignedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -35920,6 +35947,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="usercoreauthoredmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="usercoreauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="usercoreauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="usercoreauthoredmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="usercoreauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="usercoreauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="usercoreauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -36034,6 +36062,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="usercorereviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="usercorereviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="usercorereviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="usercorereviewrequestedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="usercorereviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="usercorereviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="usercorereviewrequestedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -43473,6 +43502,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="userassignedmergerequestsapprovedby"></a>`approvedBy` | [`[String!]`](#string) | Usernames of the approvers. |
| <a id="userassignedmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="userassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="userassignedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="userassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="userassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="userassignedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -43528,6 +43558,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="userauthoredmergerequestsapprover"></a>`approver` | [`[String!]`](#string) | Usernames of possible approvers. |
| <a id="userauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="userauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="userauthoredmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="userauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="userauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="userauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
@ -43642,6 +43673,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="userreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="userreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
| <a id="userreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="userreviewrequestedmergerequestsblobpath"></a>`blobPath` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.7. **Status**: Experiment. Path of the blob changed in merge request. Requires state, targetBranches, and createdAfter arguments. Available only when the feature flag `filter_blob_path` is enabled. |
| <a id="userreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
| <a id="userreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
| <a id="userreviewrequestedmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |

View File

@ -506,6 +506,29 @@ Here is a summary, which is also reflected in other sections.
mentioning them; this ensures they see it if their notification level is
set to "mentioned" and other people understand they don't have to respond.
### Recommendations for MR authors to get their changes merged faster
1. Make sure to follow best practices.
- Write efficient instructions, add screenshots, steps to validate, etc.
- Read and address any comments added by `dangerbot`.
- Follow the [acceptance checklist](#acceptance-checklist).
1. Follow GitLab patterns, even if you think there's a better way.
- Discussions often delay merging code. If a discussion is getting too long, consider following the documented approach or the maintainer's suggestion, then open a separate MR to implement your approach as part of our best practices and have the discussions there.
1. Consider splitting big MRs into smaller ones. Around `200` lines is a good goal.
- Smaller MRs reduce cognitive load for authors and reviewers.
- Reviewers tend to pick up smaller MRs to review first (a large number of files can be scary).
- Discussions on one particular part of the code will not block other parts of the code from being merged.
- Smaller MRs are often simpler, and you can consider skipping the first review and [sending directly to the maintainer](#getting-your-merge-request-reviewed-approved-and-merged), or skipping one of the suggested competency areas (frontend or backend, for example).
- Mocks can be a good approach, even though they add another MR later; replacing a mock with a server request is usually a quick MR to review.
- Be sure that any UI with mocked data is behind a [feature flag](../development/feature_flags/index.md).
- Pull common dependencies into the first MRs to avoid excessive rebases.
- For sequential MRs use [stacked diffs](../user/project/merge_requests/stacked_diffs.md).
- For dependent MRs (for example, `A` -> `B` -> `C`), have their branches target each other instead of `master`. For example, have `C` target `B`, `B` target `A`, and `A` target `master`. This way each MR will have only their corresponding `diff`.
- ⚠️ Split MRs with caution: MRs that are **too** small increase the number of total reviews, which can cause the opposite effect.
1. Minimize the number of reviewers in a single MR.
- Example: A DB reviewer can also review backend and or tests. A FullStack engineer can do both frontend and backend reviews.
- Using mocks can make the first MRs be `frontend` only, and later we can request `backend` review for the server request (see "splitting MRs" above).
### Having your merge request reviewed
Keep in mind that code review is a process that can take multiple

View File

@ -49,6 +49,7 @@ Audit event types belong to the following product categories.
| Name | Description | Saved to database | Introduced in | Scope |
|:------------|:------------|:------------------|:---------|:--------------|:--------------|
| [`duo_features_enabled_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145509) | GitLab Duo Features enabled setting on group or project changed | **{check-circle}** Yes | GitLab [16.10](https://gitlab.com/gitlab-org/gitlab/-/issues/442485) | Group, Project |
| [`q_onbarding_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175501) | Amazon Q instance settings changed | **{check-circle}** Yes | GitLab [17.8](https://gitlab.com/gitlab-org/gitlab/-/issues/508250) | Instance |
### Audit events

View File

@ -15,6 +15,7 @@ DETAILS:
> - Paginated merge request discussions [enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/364497) in GitLab 15.2.
> - Paginated merge request discussions [enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/364497) in GitLab 15.3.
> - Paginated merge request discussions [generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/370075) in GitLab 15.8. Feature flag `paginated_mr_discussions` removed.
> - Comments and threads on Wiki pages [introduced](https://gitlab.com/groups/gitlab-org/-/epics/14461) in GitLab 17.7 [with a flag](../../administration/feature_flags.md) named `wiki_comments`. Disabled by default.
GitLab encourages communication through comments, threads, and
[suggesting changes for code](../project/merge_requests/reviews/suggestions.md).
@ -32,15 +33,16 @@ which the user can accept through the user interface.
You can create comments in places like:
- Commit diffs
- Commits
- Designs
- Epics
- Issues
- Merge requests
- Snippets
- Tasks
- OKRs
- Commit diffs.
- Commits.
- Designs.
- Epics.
- Issues.
- Merge requests.
- Snippets.
- Tasks.
- OKRs.
- Wiki pages. The `wiki_comments` feature flag must be enabled. For more information, see the history.
Each object can have as many as 5,000 comments.

View File

@ -193,10 +193,9 @@ Project permissions for [model registry](project/ml/model_registry/index.md) and
| View [models and versions](project/ml/model_registry/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members can only view models and versions in public projects with the **Everyone with access** visibility level. Non-members can't view internal projects, even if they're logged in. |
| View [model experiments](project/ml/experiment_tracking/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members can only view model experiments in public projects with the **Everyone with access** visibility level. Non-members can't view internal projects, even if they're logged in. |
| Create models, versions, and artifacts | | | | ✓ | ✓ | ✓ | You can also upload and download artifacts with the package registry API, which uses it's own set of permissions. |
| Edit models, versions, and artifacts | | | | ✓ | ✓ | ✓ | |
| Edit & delete models, versions, and artifacts | | | | ✓ | ✓ | ✓ | |
| Create experiments and candidates | | | | ✓ | ✓ | ✓ | |
| Edit experiments and candidates | | | | ✓ | ✓ | ✓ | |
| Delete experiments and candidates | | | | ✓ | ✓ | ✓ | |
| Edit & delete experiments and candidates | | | | ✓ | ✓ | ✓ | |
### Monitoring

View File

@ -12,7 +12,7 @@ module API
end
namespace 'packages/conan/v1' do
include ::API::Concerns::Packages::ConanEndpoints
include ::API::Concerns::Packages::Conan::V1Endpoints
end
end
end

View File

@ -17,7 +17,7 @@ module API
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
namespace ':id/packages/conan/v1' do
include ::API::Concerns::Packages::ConanEndpoints
include ::API::Concerns::Packages::Conan::V1Endpoints
end
end
end

View File

@ -0,0 +1,55 @@
# frozen_string_literal: true
# Conan Package Manager Client API
#
# These API endpoints are not consumed directly by users, so there is no documentation for the
# individual endpoints. They are called by the Conan package manager client when users run commands
# like `conan install` or `conan upload`. The usage of the GitLab Conan repository is documented here:
# https://docs.gitlab.com/ee/user/packages/conan_repository/#installing-a-package
#
# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798
module API
module Concerns
module Packages
module Conan
module SharedEndpoints
extend ActiveSupport::Concern
PACKAGE_REQUIREMENTS = {
package_name: API::NO_SLASH_URL_PART_REGEX,
package_version: API::NO_SLASH_URL_PART_REGEX,
package_username: API::NO_SLASH_URL_PART_REGEX,
package_channel: API::NO_SLASH_URL_PART_REGEX
}.freeze
FILE_NAME_REQUIREMENTS = {
file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex
CONAN_REVISION_USER_CHANNEL_REGEX = Gitlab::Regex.conan_recipe_user_channel_regex
CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES +
Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).uniq.freeze
included do
feature_category :package_registry
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::Packages::Conan::ApiHelpers
helpers ::API::Helpers::RelatedResourcesHelpers
rescue_from ActiveRecord::RecordInvalid do |e|
render_api_error!(e.message, 400)
end
before do
not_found! if Gitlab::FIPS.enabled?
require_packages_enabled!
end
end
end
end
end
end
end

View File

@ -0,0 +1,503 @@
# frozen_string_literal: true
# Conan Package Manager Client API
#
# These API endpoints are not consumed directly by users, so there is no documentation for the
# individual endpoints. They are called by the Conan package manager client when users run commands
# like `conan install` or `conan upload`. The usage of the GitLab Conan repository is documented here:
# https://docs.gitlab.com/ee/user/packages/conan_repository/#installing-a-package
#
# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798
module API
module Concerns
module Packages
module Conan
module V1Endpoints
extend ActiveSupport::Concern
include SharedEndpoints
included do
before do
authenticate_non_get!
end
desc 'Ping the Conan API' do
detail 'This feature was introduced in GitLab 12.2'
success code: 200
failure [
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'ping', urgency: :default do
header 'X-Conan-Server-Capabilities', [].join(',')
end
desc 'Search for packages' do
detail 'This feature was introduced in GitLab 12.4'
success code: 200
failure [
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :q, type: String, desc: 'Search query', documentation: { example: 'Hello*' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'conans/search', urgency: :low do
service = ::Packages::Conan::SearchService.new(search_project, current_user, query: params[:q]).execute
service.payload
end
namespace 'users' do
before do
authenticate!
end
format :txt
content_type :txt, 'text/plain'
desc 'Authenticate user against conan CLI' do
detail 'This feature was introduced in GitLab 12.2'
success code: 200
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'authenticate', urgency: :low do
unauthorized! unless token
token.to_jwt
end
desc 'Check for valid user credentials per conan CLI' do
detail 'This feature was introduced in GitLab 12.4'
success code: 200
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'check_credentials', urgency: :default do
authenticate!
:ok
end
end
params do
requires :package_name, type: String, regexp: SharedEndpoints::PACKAGE_COMPONENT_REGEX,
desc: 'Package name', documentation: { example: 'my-package' }
requires :package_version, type: String, regexp: SharedEndpoints::PACKAGE_COMPONENT_REGEX,
desc: 'Package version', documentation: { example: '1.0' }
requires :package_username, type: String, regexp: SharedEndpoints::CONAN_REVISION_USER_CHANNEL_REGEX,
desc: 'Package username', documentation: { example: 'my-group+my-project' }
requires :package_channel, type: String, regexp: SharedEndpoints::CONAN_REVISION_USER_CHANNEL_REGEX,
desc: 'Package channel', documentation: { example: 'stable' }
end
namespace 'conans/:package_name/:package_version/:package_username/:package_channel',
requirements: SharedEndpoints::PACKAGE_REQUIREMENTS do
after_validation do
check_username_channel
end
# Get the snapshot
#
# the snapshot is a hash of { filename: md5 hash }
# md5 hash is the hash of that file. This hash is used to diff the files existing on the client
# to determine which client files need to be uploaded if no recipe exists the snapshot is empty
desc 'Package Snapshot' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200, model: ::API::Entities::ConanPackage::ConanPackageSnapshot
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :conan_package_reference, type: String, desc: 'Conan package ID',
documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'packages/:conan_package_reference', urgency: :low do
authorize_read_package!(project)
presenter = ::Packages::Conan::PackagePresenter.new(
package,
current_user,
project,
conan_package_reference: params[:conan_package_reference]
)
present presenter, with: ::API::Entities::ConanPackage::ConanPackageSnapshot
end
desc 'Recipe Snapshot' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200, model: ::API::Entities::ConanPackage::ConanRecipeSnapshot
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get urgency: :low do
authorize_read_package!(project)
presenter = ::Packages::Conan::PackagePresenter.new(package, current_user, project)
present presenter, with: ::API::Entities::ConanPackage::ConanRecipeSnapshot
end
# Get the manifest
# returns the download urls for the existing recipe in the registry
#
# the manifest is a hash of { filename: url }
# where the url is the download url for the file
desc 'Package Digest' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200, model: ::API::Entities::ConanPackage::ConanPackageManifest
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :conan_package_reference, type: String, desc: 'Conan package ID',
documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'packages/:conan_package_reference/digest', urgency: :low do
present_package_download_urls
end
desc 'Recipe Digest' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200, model: ::API::Entities::ConanPackage::ConanRecipeManifest
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'digest', urgency: :low do
present_recipe_download_urls
end
# Get the download urls
#
# returns the download urls for the existing recipe or package in the registry
#
# the manifest is a hash of { filename: url }
# where the url is the download url for the file
desc 'Package Download Urls' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200, model: ::API::Entities::ConanPackage::ConanPackageManifest
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :conan_package_reference, type: String, desc: 'Conan package ID',
documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'packages/:conan_package_reference/download_urls', urgency: :low do
present_package_download_urls
end
desc 'Recipe Download Urls' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200, model: ::API::Entities::ConanPackage::ConanRecipeManifest
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'download_urls', urgency: :low do
present_recipe_download_urls
end
# Get the upload urls
#
# request body contains { filename: filesize } where the filename is the
# name of the file the conan client is requesting to upload
#
# returns { filename: url }
# where the url is the upload url for the file that the conan client will use
desc 'Package Upload Urls' do
detail 'This feature was introduced in GitLab 12.4'
success code: 200, model: ::API::Entities::ConanPackage::ConanUploadUrls
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :conan_package_reference, type: String, desc: 'Conan package ID',
documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
post 'packages/:conan_package_reference/upload_urls', urgency: :low do
authorize_read_package!(project)
status 200
present package_upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls
end
desc 'Recipe Upload Urls' do
detail 'This feature was introduced in GitLab 12.4'
success code: 200, model: ::API::Entities::ConanPackage::ConanUploadUrls
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
post 'upload_urls', urgency: :low do
authorize_read_package!(project)
status 200
present recipe_upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls
end
desc 'Delete Package' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
delete urgency: :low do
authorize!(:destroy_package, project)
track_package_event('delete_package', :conan, category: 'API::ConanPackages', project: project,
namespace: project.namespace)
package.destroy
end
end
params do
requires :package_name, type: String, regexp: SharedEndpoints::PACKAGE_COMPONENT_REGEX,
desc: 'Package name', documentation: { example: 'my-package' }
requires :package_version, type: String, regexp: SharedEndpoints::PACKAGE_COMPONENT_REGEX,
desc: 'Package version', documentation: { example: '1.0' }
requires :package_username, type: String, regexp: SharedEndpoints::CONAN_REVISION_USER_CHANNEL_REGEX,
desc: 'Package username', documentation: { example: 'my-group+my-project' }
requires :package_channel, type: String, regexp: SharedEndpoints::CONAN_REVISION_USER_CHANNEL_REGEX,
desc: 'Package channel', documentation: { example: 'stable' }
requires :recipe_revision, type: String, regexp: Gitlab::Regex.conan_revision_regex,
desc: 'Conan Recipe Revision', documentation: { example: '0' }
end
namespace 'files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision',
requirements: SharedEndpoints::PACKAGE_REQUIREMENTS do
before do
authenticate_non_get!
end
after_validation do
check_username_channel
end
params do
requires :file_name, type: String, desc: 'Package file name', values: SharedEndpoints::CONAN_FILES,
documentation: { example: 'conanfile.py' }
end
namespace 'export/:file_name',
requirements: SharedEndpoints::FILE_NAME_REQUIREMENTS do
desc 'Download recipe files' do
detail 'This feature was introduced in GitLab 12.6'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get urgency: :low do
download_package_file(:recipe_file)
end
desc 'Upload recipe package files' do
detail 'This feature was introduced in GitLab 12.6'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :file, type: ::API::Validations::Types::WorkhorseFile,
desc: 'The package file to be published (generated by Multipart middleware)',
documentation: { type: 'file' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put urgency: :low do
upload_package_file(:recipe_file)
end
desc 'Workhorse authorize the conan recipe file' do
detail 'This feature was introduced in GitLab 12.6'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put 'authorize', urgency: :low do
authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size)
end
end
params do
requires :conan_package_reference, type: String, desc: 'Conan Package ID',
documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' }
requires :package_revision, type: String, desc: 'Conan Package Revision',
documentation: { example: '0' }
requires :file_name, type: String, desc: 'Package file name', values: SharedEndpoints::CONAN_FILES,
documentation: { example: 'conaninfo.txt' }
end
namespace 'package/:conan_package_reference/:package_revision/:file_name',
requirements: SharedEndpoints::FILE_NAME_REQUIREMENTS do
desc 'Download package files' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200
failure [
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get urgency: :low do
download_package_file(:package_file)
end
desc 'Workhorse authorize the conan package file' do
detail 'This feature was introduced in GitLab 12.6'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put 'authorize', urgency: :low do
authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size)
end
desc 'Upload package files' do
detail 'This feature was introduced in GitLab 12.6'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :file, type: ::API::Validations::Types::WorkhorseFile,
desc: 'The package file to be published (generated by Multipart middleware)',
documentation: { type: 'file' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put urgency: :low do
upload_package_file(:package_file)
end
end
end
end
end
end
end
end
end

View File

@ -1,506 +0,0 @@
# frozen_string_literal: true
# Conan Package Manager Client API
#
# These API endpoints are not consumed directly by users, so there is no documentation for the
# individual endpoints. They are called by the Conan package manager client when users run commands
# like `conan install` or `conan upload`. The usage of the GitLab Conan repository is documented here:
# https://docs.gitlab.com/ee/user/packages/conan_repository/#installing-a-package
#
# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798
module API
module Concerns
module Packages
module ConanEndpoints
extend ActiveSupport::Concern
PACKAGE_REQUIREMENTS = {
package_name: API::NO_SLASH_URL_PART_REGEX,
package_version: API::NO_SLASH_URL_PART_REGEX,
package_username: API::NO_SLASH_URL_PART_REGEX,
package_channel: API::NO_SLASH_URL_PART_REGEX
}.freeze
FILE_NAME_REQUIREMENTS = {
file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex
CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex
CONAN_REVISION_USER_CHANNEL_REGEX = Gitlab::Regex.conan_recipe_user_channel_regex
CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).uniq.freeze
included do
feature_category :package_registry
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::Packages::Conan::ApiHelpers
helpers ::API::Helpers::RelatedResourcesHelpers
rescue_from ActiveRecord::RecordInvalid do |e|
render_api_error!(e.message, 400)
end
before do
not_found! if Gitlab::FIPS.enabled?
require_packages_enabled!
# Personal access token will be extracted from Bearer or Basic authorization
# in the overridden find_personal_access_token or find_user_from_job_token helpers
authenticate_non_get!
end
desc 'Ping the Conan API' do
detail 'This feature was introduced in GitLab 12.2'
success code: 200
failure [
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'ping', urgency: :default do
header 'X-Conan-Server-Capabilities', [].join(',')
end
desc 'Search for packages' do
detail 'This feature was introduced in GitLab 12.4'
success code: 200
failure [
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :q, type: String, desc: 'Search query', documentation: { example: 'Hello*' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'conans/search', urgency: :low do
service = ::Packages::Conan::SearchService.new(search_project, current_user, query: params[:q]).execute
service.payload
end
namespace 'users' do
before do
authenticate!
end
format :txt
content_type :txt, 'text/plain'
desc 'Authenticate user against conan CLI' do
detail 'This feature was introduced in GitLab 12.2'
success code: 200
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'authenticate', urgency: :low do
unauthorized! unless token
token.to_jwt
end
desc 'Check for valid user credentials per conan CLI' do
detail 'This feature was introduced in GitLab 12.4'
success code: 200
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'check_credentials', urgency: :default do
authenticate!
:ok
end
end
params do
requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name', documentation: { example: 'my-package' }
requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version', documentation: { example: '1.0' }
requires :package_username, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package username', documentation: { example: 'my-group+my-project' }
requires :package_channel, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package channel', documentation: { example: 'stable' }
end
namespace 'conans/:package_name/:package_version/:package_username/:package_channel', requirements: PACKAGE_REQUIREMENTS do
after_validation do
check_username_channel
end
# Get the snapshot
#
# the snapshot is a hash of { filename: md5 hash }
# md5 hash is the hash of that file. This hash is used to diff the files existing on the client
# to determine which client files need to be uploaded if no recipe exists the snapshot is empty
desc 'Package Snapshot' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200, model: ::API::Entities::ConanPackage::ConanPackageSnapshot
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :conan_package_reference, type: String, desc: 'Conan package ID', documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'packages/:conan_package_reference', urgency: :low do
authorize_read_package!(project)
presenter = ::Packages::Conan::PackagePresenter.new(
package,
current_user,
project,
conan_package_reference: params[:conan_package_reference]
)
present presenter, with: ::API::Entities::ConanPackage::ConanPackageSnapshot
end
desc 'Recipe Snapshot' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200, model: ::API::Entities::ConanPackage::ConanRecipeSnapshot
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get urgency: :low do
authorize_read_package!(project)
presenter = ::Packages::Conan::PackagePresenter.new(package, current_user, project)
present presenter, with: ::API::Entities::ConanPackage::ConanRecipeSnapshot
end
# Get the manifest
# returns the download urls for the existing recipe in the registry
#
# the manifest is a hash of { filename: url }
# where the url is the download url for the file
desc 'Package Digest' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200, model: ::API::Entities::ConanPackage::ConanPackageManifest
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :conan_package_reference, type: String, desc: 'Conan package ID', documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'packages/:conan_package_reference/digest', urgency: :low do
present_package_download_urls
end
desc 'Recipe Digest' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200, model: ::API::Entities::ConanPackage::ConanRecipeManifest
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'digest', urgency: :low do
present_recipe_download_urls
end
# Get the download urls
#
# returns the download urls for the existing recipe or package in the registry
#
# the manifest is a hash of { filename: url }
# where the url is the download url for the file
desc 'Package Download Urls' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200, model: ::API::Entities::ConanPackage::ConanPackageManifest
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :conan_package_reference, type: String, desc: 'Conan package ID', documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'packages/:conan_package_reference/download_urls', urgency: :low do
present_package_download_urls
end
desc 'Recipe Download Urls' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200, model: ::API::Entities::ConanPackage::ConanRecipeManifest
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get 'download_urls', urgency: :low do
present_recipe_download_urls
end
# Get the upload urls
#
# request body contains { filename: filesize } where the filename is the
# name of the file the conan client is requesting to upload
#
# returns { filename: url }
# where the url is the upload url for the file that the conan client will use
desc 'Package Upload Urls' do
detail 'This feature was introduced in GitLab 12.4'
success code: 200, model: ::API::Entities::ConanPackage::ConanUploadUrls
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :conan_package_reference, type: String, desc: 'Conan package ID', documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
post 'packages/:conan_package_reference/upload_urls', urgency: :low do
authorize_read_package!(project)
status 200
present package_upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls
end
desc 'Recipe Upload Urls' do
detail 'This feature was introduced in GitLab 12.4'
success code: 200, model: ::API::Entities::ConanPackage::ConanUploadUrls
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
post 'upload_urls', urgency: :low do
authorize_read_package!(project)
status 200
present recipe_upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls
end
desc 'Delete Package' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
delete urgency: :low do
authorize!(:destroy_package, project)
track_package_event('delete_package', :conan, category: 'API::ConanPackages', project: project, namespace: project.namespace)
package.destroy
end
end
params do
requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name', documentation: { example: 'my-package' }
requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version', documentation: { example: '1.0' }
requires :package_username, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package username', documentation: { example: 'my-group+my-project' }
requires :package_channel, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package channel', documentation: { example: 'stable' }
requires :recipe_revision, type: String, regexp: CONAN_REVISION_REGEX, desc: 'Conan Recipe Revision', documentation: { example: '0' }
end
namespace 'files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision', requirements: PACKAGE_REQUIREMENTS do
before do
authenticate_non_get!
end
after_validation do
check_username_channel
end
params do
requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES, documentation: { example: 'conanfile.py' }
end
namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do
desc 'Download recipe files' do
detail 'This feature was introduced in GitLab 12.6'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get urgency: :low do
download_package_file(:recipe_file)
end
desc 'Upload recipe package files' do
detail 'This feature was introduced in GitLab 12.6'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put urgency: :low do
upload_package_file(:recipe_file)
end
desc 'Workhorse authorize the conan recipe file' do
detail 'This feature was introduced in GitLab 12.6'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put 'authorize', urgency: :low do
authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size)
end
end
params do
requires :conan_package_reference, type: String, desc: 'Conan Package ID', documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' }
requires :package_revision, type: String, desc: 'Conan Package Revision', documentation: { example: '0' }
requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES, documentation: { example: 'conaninfo.txt' }
end
namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do
desc 'Download package files' do
detail 'This feature was introduced in GitLab 12.5'
success code: 200
failure [
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get urgency: :low do
download_package_file(:package_file)
end
desc 'Workhorse authorize the conan package file' do
detail 'This feature was introduced in GitLab 12.6'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put 'authorize', urgency: :low do
authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size)
end
desc 'Upload package files' do
detail 'This feature was introduced in GitLab 12.6'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
params do
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' }
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put urgency: :low do
upload_package_file(:package_file)
end
end
end
end
end
end
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -13,6 +13,8 @@ module Gitlab
:organization_id,
:user,
:user_id,
:scoped_user,
:scoped_user_id,
:project,
:root_namespace,
:client_id,
@ -52,6 +54,7 @@ module Gitlab
Attribute.new(:project, Project),
Attribute.new(:namespace, Namespace),
Attribute.new(:user, User),
Attribute.new(:scoped_user, User),
Attribute.new(:runner, ::Ci::Runner),
Attribute.new(:caller_id, String),
Attribute.new(:remote_ip, String),
@ -128,6 +131,7 @@ module Gitlab
# rubocop: disable Metrics/CyclomaticComplexity -- inherently leads to higher cyclomatic due to
# all the conditional assignments, the added complexity from adding more abstractions like
# `assign_hash_if_value` is not worth the tradeoff.
# rubocop: disable Metrics/PerceivedComplexity -- same as above
def to_lazy_hash
{}.tap do |hash|
assign_hash_if_value(hash, :caller_id)
@ -150,6 +154,8 @@ module Gitlab
hash[:user] = -> { username } if include_user?
hash[:user_id] = -> { user_id } if include_user?
hash[:scoped_user] = -> { scoped_user&.username } if include_scoped_user?
hash[:scoped_user_id] = -> { scoped_user&.id } if include_scoped_user?
hash[:project] = -> { project_path } if include_project?
hash[:organization_id] = -> { organization&.id } if set_values.include?(:organization)
hash[:root_namespace] = -> { root_namespace_path } if include_namespace?
@ -161,6 +167,7 @@ module Gitlab
end
# rubocop: enable Metrics/CyclomaticComplexity
# rubocop: enable Metrics/AbcSize
# rubocop: enable Metrics/PerceivedComplexity
def use
Labkit::Context.with_context(to_lazy_hash) { yield }
@ -228,6 +235,10 @@ module Gitlab
set_values.include?(:user) || set_values.include?(:job)
end
def include_scoped_user?
set_values.include?(:scoped_user)
end
def include_project?
set_values.include?(:project) || set_values.include?(:runner) || set_values.include?(:job)
end

View File

@ -60,6 +60,8 @@ module Gitlab
ADMIN_MODE_SCOPE = :admin_mode
ADMIN_SCOPES = [SUDO_SCOPE, ADMIN_MODE_SCOPE, READ_SERVICE_PING_SCOPE].freeze
Q_SCOPES = [API_SCOPE, REPOSITORY_SCOPES].flatten.freeze
# Default scopes for OAuth applications that don't define their own
DEFAULT_SCOPES = [API_SCOPE].freeze

View File

@ -26,8 +26,6 @@ module Gitlab
end
def self.link_from_job(job)
return unless Feature.enabled?(:composite_identity_in_ci, job&.user)
fabricate(job.user).tap do |identity|
identity.link!(job.scoped_user) if identity&.composite?
end
@ -85,8 +83,12 @@ module Gitlab
return self unless composite_identity_enabled?
return self unless scope_user
##
# TODO: consider extracting linking to ::Gitlab::Auth::Identities::Link#create!
#
validate_link!(scope_user)
store_identity_link!(scope_user)
append_log!(scope_user)
self
end
@ -143,6 +145,10 @@ module Gitlab
raise TooManyIdentitiesLinkedError if composite_identities.size > 1
end
def append_log!(scope_user)
::Gitlab::ApplicationContext.push(scoped_user: scope_user)
end
def composite_identities
@request_store.store[COMPOSITE_IDENTITY_USERS_KEY] ||= Set.new
end

View File

@ -107,7 +107,7 @@ module Gitlab
end
def create_placeholder_user(import_source_user)
return namespace_import_user if placeholder_user_limit_exceeded?
return namespace_import_user if placeholder_user_limit_exceeded? || namespace.user_namespace?
Gitlab::Import::PlaceholderUserCreator.new(import_source_user).execute
end

View File

@ -105,3 +105,5 @@ module Sidebars
end
end
end
Sidebars::Admin::Menus::AdminOverviewMenu.prepend_mod_with('Sidebars::Admin::Menus::AdminOverviewMenu')

View File

@ -5920,6 +5920,9 @@ msgstr ""
msgid "AmazonQ|Amazon Q Configuration"
msgstr ""
msgid "AmazonQ|Amazon Q Settings have been saved."
msgstr ""
msgid "AmazonQ|Amazon Q will be turned off by default, but still be available to any groups or projects that have previously enabled it."
msgstr ""
@ -6004,6 +6007,9 @@ msgstr ""
msgid "AmazonQ|Something went wrong retrieving the identity provider payload."
msgstr ""
msgid "AmazonQ|Something went wrong saving Amazon Q settings."
msgstr ""
msgid "AmazonQ|Status"
msgstr ""
@ -30575,6 +30581,9 @@ msgstr ""
msgid "Items are already linked"
msgstr ""
msgid "Items below are excluded from the active count"
msgstr ""
msgid "Iteration"
msgstr ""
@ -35084,6 +35093,9 @@ msgstr ""
msgid "MlExperimentTracking|Create a new experiment"
msgstr ""
msgid "MlExperimentTracking|Create an experiment using MLflow"
msgstr ""
msgid "MlExperimentTracking|Create new candidates"
msgstr ""
@ -35093,6 +35105,12 @@ msgstr ""
msgid "MlExperimentTracking|Created at"
msgstr ""
msgid "MlExperimentTracking|Creating an experiment"
msgstr ""
msgid "MlExperimentTracking|Creating experiments using the MLflow client:"
msgstr ""
msgid "MlExperimentTracking|Delete candidate"
msgstr ""
@ -35126,7 +35144,7 @@ msgstr ""
msgid "MlExperimentTracking|Experiment removed"
msgstr ""
msgid "MlExperimentTracking|Experiments keep track of comparable model candidates, and determine which parameters provides the best performance. Create experiments using the MLflow client"
msgid "MlExperimentTracking|Experiments keep track of comparable model candidates, and determine which parameters provides the best performance."
msgstr ""
msgid "MlExperimentTracking|Filter candidates"
@ -35168,6 +35186,12 @@ msgstr ""
msgid "MlExperimentTracking|No name"
msgstr ""
msgid "MlExperimentTracking|To learn more about MLflow client compatibility, see %{linkStart}the documentation%{linkEnd}."
msgstr ""
msgid "MlExperimentTracking|Using the MLflow client"
msgstr ""
msgid "MlExperimentTracking|Version"
msgstr ""
@ -47087,6 +47111,9 @@ msgstr ""
msgid "Review changes"
msgstr ""
msgid "Review requested"
msgstr ""
msgid "Review requested from %{reviewerName}"
msgstr ""
@ -62143,12 +62170,18 @@ msgstr ""
msgid "Waiting for approvals"
msgstr ""
msgid "Waiting for author"
msgstr ""
msgid "Waiting for merge (open and assigned)"
msgstr ""
msgid "Waiting for others"
msgstr ""
msgid "Waiting for reviewer"
msgstr ""
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""

View File

@ -188,6 +188,73 @@ RSpec.describe Groups::ChildrenController, feature_category: :groups_and_project
expect(response).to have_gitlab_http_status(:ok)
end
context 'when items more than Kaminari.config.default_per_page' do
let_it_be(:filter) { 'filtered-group' }
let_it_be(:per_page) { 2 }
let_it_be(:params) { { group_id: group.to_param, filter: filter } }
let_it_be(:subgroups) { Array.new(per_page) { create(:group, parent: group) } }
let_it_be(:sub_subgroups) { subgroups.map { |subgroup| create(:group, parent: subgroup) } }
let_it_be(:matching_descendants) do
sub_subgroups.map.with_index do |sub_subgroup, index|
Array.new(per_page) do |descendant_index|
formatted_index = "#{index}#{descendant_index}"
create(:group, :public, parent: sub_subgroup, name: "#{filter}-#{formatted_index}")
end
end.flatten
end
before do
allow(Kaminari.config).to receive(:default_per_page).and_return(per_page)
end
it 'does not throw ArgumentError for N+1 queries' do
get :index, params: params, format: :json
expect(response).to have_gitlab_http_status(:ok)
end
it 'paginates correctly' do
expected_ids = [subgroups.last, sub_subgroups.last, matching_descendants.last(2)].flatten.pluck(:id)
get :index, params: params.merge(page: 2), format: :json
result_ids = descendant_ids(json_response)
expect(result_ids).to match_array(expected_ids)
end
context 'with a single page' do
let_it_be(:params) { params.merge(per_page: matching_descendants.size) }
it 'returns the correct pagination headers with per_page' do
get :index, params: params, format: :json
expect(response.header).to match(hash_including(
"X-Per-Page" => "4",
"X-Page" => "1",
"X-Next-Page" => "",
"X-Prev-Page" => "",
"X-Total" => "4",
"X-Total-Pages" => "1"
))
end
end
def descendant_ids(data)
return [] if data.blank?
data = Array.wrap(data)
ids = []
data.each do |item|
ids << item['id']
ids << descendant_ids(item['children'])
end
ids.flatten
end
end
it 'includes pagination headers' do
2.times { |i| create(:group, :public, parent: public_subgroup, name: "filterme#{i}") }

View File

@ -39,20 +39,6 @@ RSpec.describe Ci::AuthJobFinder, feature_category: :continuous_integration do
expect(::Gitlab::Auth::Identity.new(job.user)).not_to be_linked
end
end
context 'when job user supports composite identity' do
before do
allow(job.user).to receive(:has_composite_identity?).and_return(true)
end
it 'links the scoped user as composite identity' do
expect(job.scoped_user).to eq(scoped_user)
execute
expect(::Gitlab::Auth::Identity.new(job.user)).to be_linked
end
end
end
it 'raises error if the job is not running' do

View File

@ -263,6 +263,34 @@ RSpec.describe GroupDescendantsFinder, feature_category: :groups_and_projects do
expect(finder.execute).not_to include(group)
end
end
context 'when items more than Kaminari.config.default_per_page' do
let_it_be(:filter) { 'filtered-group' }
let_it_be(:per_page) { 2 }
let_it_be(:params) { { filter: filter } }
let_it_be(:subgroups) { Array.new(per_page) { create(:group, parent: group) } }
let_it_be(:sub_subgroups) { subgroups.map { |subgroup| create(:group, parent: subgroup) } }
let_it_be(:matching_descendants) do
sub_subgroups.map.with_index do |sub_subgroup, index|
Array.new(per_page) do |descendant_index|
formatted_index = "#{index}#{descendant_index}"
create(:group, :public, parent: sub_subgroup, name: "#{filter}-#{formatted_index}")
end
end.flatten
end
before do
allow(Kaminari.config).to receive(:default_per_page).and_return(per_page)
end
it 'returns the correct descendants with their ancestors' do
expect(finder.execute).to contain_exactly(
subgroups.first,
sub_subgroups.first,
*matching_descendants.first(2)
)
end
end
end
end
end

View File

@ -399,6 +399,44 @@ RSpec.describe MergeRequestsFinder, feature_category: :code_review_workflow do
end
end
context 'filter by blob path' do
let(:params) { { project_id: project_id, blob_path: blob_path } }
let_it_be(:merge_request) { create(:merge_request, :opened, source_project: project1, target_project: project1, source_branch: 'feature', target_branch: 'master') }
let_it_be(:merge_request_diff) { create(:merge_request_diff, merge_request: merge_request) }
let_it_be(:merge_request_diff_file) { create(:merge_request_diff_file, merge_request_diff: merge_request_diff) }
let(:project_id) { project1 }
let(:target_branch) { merge_request.target_branch }
let(:blob_path) { merge_request_diff_file.old_path }
it 'returns merge requests with blobs by requested path' do
merge_requests = described_class.new(user, params).execute
expect(merge_requests).to contain_exactly(merge_request)
end
context 'when blob path is not found' do
let(:blob_path) { 'unknown' }
it 'does not return merge requests' do
merge_requests = described_class.new(user, params).execute
expect(merge_requests).to be_empty
end
end
context 'when project_id is not set' do
let(:project_id) { nil }
it 'does not return merge requests' do
merge_requests = described_class.new(user, params).execute
expect(merge_requests).to be_empty
end
end
end
describe '.scalar_params' do
it 'contains scalar params related to merge requests' do
scalar_params = described_class.scalar_params

View File

@ -1,12 +1,5 @@
import Vue from 'vue';
import {
GlAlert,
GlDisclosureDropdown,
GlEmptyState,
GlIcon,
GlLoadingIcon,
GlPopover,
} from '@gitlab/ui';
import { GlAlert, GlDisclosureDropdown, GlEmptyState, GlLoadingIcon, GlPopover } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import MockAdapter from 'axios-mock-adapter';
@ -18,6 +11,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { resolvers } from '~/ci/pipeline_editor/graphql/resolvers';
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
import CiLintResults from '~/ci/pipeline_editor/components/lint/ci_lint_results.vue';
import CiValidate, { i18n } from '~/ci/pipeline_editor/components/validate/ci_validate.vue';
import ValidatePipelinePopover from '~/ci/pipeline_editor/components/popovers/validate_pipeline_popover.vue';
@ -77,7 +71,7 @@ describe('Pipeline Editor Validate Tab', () => {
const findCta = () => wrapper.findByTestId('simulate-pipeline-button');
const findLintButton = () => wrapper.findByTestId('lint-button');
const findDisabledCtaTooltip = () => wrapper.findByTestId('cta-tooltip');
const findHelpIcon = () => wrapper.findComponent(GlIcon);
const findHelpIcon = () => wrapper.findComponent(HelpIcon);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findPipelineSource = () => wrapper.findComponent(GlDisclosureDropdown);

View File

@ -21,10 +21,9 @@ describe('Merge requests app component', () => {
const findMergeRequests = () => wrapper.findAllComponents(MergeRequest);
const findLoadMoreButton = () => wrapper.findByTestId('load-more');
const findCountExplanation = () => wrapper.findByTestId('merge-request-count-explanation');
function createComponent(
props = { query: 'reviewRequestedMergeRequests', variables: { state: 'opened' } },
) {
function createComponent(lists = null, provide = { newListsEnabled: false }) {
assigneeQueryMock = jest.fn().mockResolvedValue({
data: {
currentUser: {
@ -70,18 +69,20 @@ describe('Merge requests app component', () => {
tabs: [
{
title: 'Needs attention',
lists: [
{
title: 'Assigned merge requests',
query: 'assignedMergeRequests',
variables: { state: 'opened' },
},
lists: lists || [
[
{
title: 'Assigned merge requests',
query: 'assignedMergeRequests',
variables: { state: 'opened' },
},
],
],
},
],
...props,
},
provide: {
...provide,
mergeRequestsSearchDashboardPath: '/search',
},
stubs: {
@ -112,4 +113,53 @@ describe('Merge requests app component', () => {
expect.objectContaining({ afterCursor: 'endCursor' }),
);
});
describe('when newListsEnabled is true', () => {
it('with 1 list does not render active count explanation', async () => {
createComponent(
[
[
{
title: 'Assigned merge requests',
query: 'assignedMergeRequests',
variables: { state: 'opened' },
},
],
],
{ newListsEnabled: true },
);
await waitForPromises();
expect(findMergeRequests()).toHaveLength(1);
expect(findCountExplanation().exists()).toBe(false);
});
it('renders active count explanation when more than 1 list', async () => {
createComponent(
[
[
{
title: 'Assigned merge requests',
query: 'assignedMergeRequests',
variables: { state: 'opened' },
},
],
[
{
title: 'Assigned merge requests',
query: 'assignedMergeRequests',
variables: { state: 'opened' },
},
],
],
{ newListsEnabled: true },
);
await waitForPromises();
expect(findMergeRequests()).toHaveLength(2);
expect(findCountExplanation().exists()).toBe(true);
});
});
});

View File

@ -40,8 +40,8 @@ import { mergeRequestListTabs } from '~/vue_shared/issuable/list/constants';
import { getSortOptions } from '~/issues/list/utils';
import MergeRequestsListApp from '~/merge_requests/list/components/merge_requests_list_app.vue';
import { BRANCH_LIST_REFRESH_INTERVAL } from '~/merge_requests/list/constants';
import getMergeRequestsQuery from 'ee_else_ce/merge_requests/list/queries/get_merge_requests.query.graphql';
import getMergeRequestsCountQuery from 'ee_else_ce/merge_requests/list/queries/get_merge_requests_counts.query.graphql';
import getMergeRequestsQuery from 'ee_else_ce/merge_requests/list/queries/project/get_merge_requests.query.graphql';
import getMergeRequestsCountsQuery from 'ee_else_ce/merge_requests/list/queries/project/get_merge_requests_counts.query.graphql';
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
import MergeRequestReviewers from '~/issuable/components/merge_request_reviewers.vue';
import issuableEventHub from '~/issues/list/eventhub';
@ -66,7 +66,7 @@ function createComponent({
getQueryResponseMock = jest.fn().mockResolvedValue(response);
getCountsQueryResponseMock = jest.fn().mockResolvedValue(getCountsQueryResponse);
const apolloProvider = createMockApollo([
[getMergeRequestsCountQuery, getCountsQueryResponseMock],
[getMergeRequestsCountsQuery, getCountsQueryResponseMock],
[getMergeRequestsQuery, getQueryResponseMock],
]);
router = new VueRouter({ mode: 'history' });
@ -91,6 +91,8 @@ function createComponent({
canBulkUpdate: true,
environmentNamesPath: '',
defaultBranch: 'main',
getMergeRequestsCountsQuery,
getMergeRequestsQuery,
...provide,
},
apolloProvider,
@ -124,7 +126,7 @@ describe('Merge requests list app', () => {
sortOptions: getSortOptions({ hasManualSort: false }),
initialSortBy: 'CREATED_DESC',
issuableSymbol: '!',
issuables: getQueryResponse.data.project.mergeRequests.nodes,
issuables: getQueryResponse.data.namespace.mergeRequests.nodes,
tabs: mergeRequestListTabs,
currentTab: 'opened',
tabCounts: {
@ -162,7 +164,7 @@ describe('Merge requests list app', () => {
describe('with no projectId', () => {
it('uses the generic "all branches" endpoint', async () => {
const queryResponse = getQueryResponse;
queryResponse.data.project.id = null;
queryResponse.data.namespace.id = null;
createComponent({ response: queryResponse });
@ -183,7 +185,7 @@ describe('Merge requests list app', () => {
'selects the correct path ($branchPath) given the arguments $fetchArgs',
async ({ branchPath, fetchArgs }) => {
const queryResponse = getQueryResponse;
queryResponse.data.project.id = projectId;
queryResponse.data.namespace.id = projectId;
createComponent({ response: queryResponse });
await waitForPromises();
@ -198,7 +200,7 @@ describe('Merge requests list app', () => {
describe('cache expiration', () => {
const queryResponse = getQueryResponse;
queryResponse.data.project.id = projectId;
queryResponse.data.namespace.id = projectId;
beforeEach(() => {
axiosMock.resetHistory();
@ -510,7 +512,7 @@ describe('Merge requests list app', () => {
'$existsText cannot merge badge when state is $state and mergeRequest has $cannotMergeProperties',
async ({ state, cannotMergeProperties, exists }) => {
const response = JSON.parse(JSON.stringify(getQueryResponse));
Object.assign(response.data.project.mergeRequests.nodes[0], {
Object.assign(response.data.namespace.mergeRequests.nodes[0], {
state,
...cannotMergeProperties,
});
@ -531,7 +533,7 @@ describe('Merge requests list app', () => {
expect(wrapper.findComponent(ApprovalCount).exists()).toBe(true);
expect(wrapper.findComponent(ApprovalCount).props('mergeRequest')).toEqual(
getQueryResponse.data.project.mergeRequests.nodes[0],
getQueryResponse.data.namespace.mergeRequests.nodes[0],
);
});
@ -544,7 +546,7 @@ describe('Merge requests list app', () => {
expect(reviewersEl.exists()).toBe(true);
expect(reviewersEl.props()).toMatchObject({
reviewers: getQueryResponse.data.project.mergeRequests.nodes[0].reviewers.nodes,
reviewers: getQueryResponse.data.namespace.mergeRequests.nodes[0].reviewers.nodes,
iconSize: 16,
maxVisible: 4,
});
@ -630,7 +632,7 @@ describe('Merge requests list app', () => {
'$existsText target branch link when default branch: $defaultBranch and targetBranch: $targetBranch',
async ({ defaultBranch, targetBranch, exists }) => {
const response = JSON.parse(JSON.stringify(getQueryResponse));
Object.assign(response.data.project.mergeRequests.nodes[0], {
Object.assign(response.data.namespace.mergeRequests.nodes[0], {
targetBranch,
});

View File

@ -1,6 +1,6 @@
export const getQueryResponse = {
data: {
project: {
namespace: {
id: '1',
__typename: 'Project',
mergeRequests: {
@ -100,7 +100,7 @@ export const getQueryResponse = {
export const getCountsQueryResponse = {
data: {
project: {
namespace: {
id: 1,
openedMergeRequests: { count: 1 },
mergedMergeRequests: { count: 1 },

View File

@ -1,9 +1,12 @@
import { GlEmptyState, GlLink, GlTableLite } from '@gitlab/ui';
import { GlEmptyState, GlLink, GlTableLite, GlButton } from '@gitlab/ui';
import MlExperimentsIndexApp from '~/ml/experiment_tracking/routes/experiments/index';
import ModelExperimentsHeader from '~/ml/experiment_tracking/components/model_experiments_header.vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { TITLE_LABEL } from '~/ml/experiment_tracking/routes/experiments/index/translations';
import Pagination from '~/ml/experiment_tracking/components/pagination.vue';
import { MLFLOW_USAGE_MODAL_ID } from '~/ml/experiment_tracking/routes/experiments/index/constants';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import {
startCursor,
firstExperiment,
@ -15,6 +18,7 @@ import {
let wrapper;
const createWrapper = (defaultExperiments = [], pageInfo = defaultPageInfo) => {
wrapper = mountExtended(MlExperimentsIndexApp, {
directives: { GlModal: createMockDirective('gl-modal') },
propsData: { experiments: defaultExperiments, pageInfo, emptyStateSvgPath: 'path' },
});
};
@ -30,6 +34,8 @@ const hrefInRowAndColumn = (row, col) =>
findColumnInRow(row, col).findComponent(GlLink).attributes().href;
const findTitleHeader = () => wrapper.findComponent(ModelExperimentsHeader);
const findDocsButton = () => wrapper.findAllComponents(GlButton).at(0);
describe('MlExperimentsIndex', () => {
describe('empty state', () => {
beforeEach(() => createWrapper());
@ -49,6 +55,11 @@ describe('MlExperimentsIndex', () => {
it('renders header', () => {
expect(findTitleHeader().exists()).toBe(true);
});
it('creates button to docs', () => {
expect(findDocsButton().text()).toBe('Create an experiment using MLflow');
expect(getBinding(findDocsButton().element, 'gl-modal').value).toBe(MLFLOW_USAGE_MODAL_ID);
});
});
describe('Title header', () => {

View File

@ -0,0 +1,41 @@
import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
import MlflowUsageModal from '~/ml/experiment_tracking/routes/experiments/index/components/mlflow_usage_modal.vue';
import { MLFLOW_USAGE_MODAL_ID } from '~/ml/experiment_tracking/routes/experiments/index/constants';
let wrapper;
const createWrapper = () => {
wrapper = shallowMount(MlflowUsageModal, {
provide: { mlflowTrackingUrl: 'path/to/mlflow' },
});
};
const findModal = () => wrapper.findComponent(GlModal);
describe('ml/experiment_tracking/routes/experiments/index/components/mlflow_usage_modal.vue', () => {
beforeEach(() => {
createWrapper();
});
it('renders the modal with correct props', () => {
expect(findModal().props()).toMatchObject({
title: 'Using the MLflow client',
modalId: MLFLOW_USAGE_MODAL_ID,
});
});
it('renders the text', () => {
const text = findModal().text();
const expectedLines = [
'Creating experiments using the MLflow client:',
// 'Creating an experiment',
'import os',
'from mlflow import MlflowClient',
'os.environ["MLFLOW_TRACKING_URI"] = "path/to/mlflow"',
'os.environ["MLFLOW_TRACKING_TOKEN"] = <your_gitlab_token>',
'client = MlflowClient()',
"client.create_experiment(name=\"<your_experiment_name>\", tags={'key': 'value'})",
];
expectedLines.forEach((line) => expect(text).toContain(line));
});
});

View File

@ -11,6 +11,7 @@ import WorkItemCreateBranchMergeRequestModal from '~/work_items/components/work_
import getProjectRootRef from '~/work_items/graphql/get_project_root_ref.query.graphql';
import { createAlert } from '~/alert';
import { visitUrl } from '~/lib/utils/url_utility';
import ProjectFormGroup from '~/confidential_merge_request/components/project_form_group.vue';
jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility', () => ({
@ -18,6 +19,12 @@ jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(),
}));
jest.mock('~/confidential_merge_request/state', () => ({
selectedProject: {
pathWithNamespace: 'fullPath-fork-new',
},
}));
describe('CreateBranchMergeRequestModal', () => {
Vue.use(VueApollo);
@ -48,6 +55,9 @@ describe('CreateBranchMergeRequestModal', () => {
showModal = true,
workItemType = 'Issue',
workItemFullPath = 'fullPath',
groupPath = 'groupPath',
projectId = 'gid://gitlab/Project/2',
isConfidentialWorkItem = false,
} = {}) => {
mockApollo = createMockApollo([[getProjectRootRef, projectRefHandler]]);
@ -61,6 +71,11 @@ describe('CreateBranchMergeRequestModal', () => {
showMergeRequestFlow,
showModal,
workItemFullPath,
projectId,
isConfidentialWorkItem,
},
provide: {
groupPath,
},
mocks: {
$toast: {
@ -73,6 +88,8 @@ describe('CreateBranchMergeRequestModal', () => {
const findForm = () => wrapper.findComponent(GlForm);
const findGlModal = () => wrapper.findComponent(GlModal);
const firePrimaryEvent = () => findGlModal().vm.$emit('primary', { preventDefault: jest.fn() });
const findPrimaryButton = () => findGlModal().props('actionPrimary');
const findPrivateForksSelector = () => wrapper.findComponent(ProjectFormGroup);
beforeEach(() => {
mock = new MockAdapter(axios);
@ -186,5 +203,60 @@ describe('CreateBranchMergeRequestModal', () => {
'/fullPath/-/merge_requests/new?merge_request%5Bissue_iid%5D=1&merge_request%5Bsource_branch%5D=suggested_branch_name&merge_request%5Btarget_branch%5D=master',
);
});
describe('confidential merge request', () => {
beforeEach(() => {
createWrapper({
showBranchFlow: false,
showMergeRequestFlow: true,
isConfidentialWorkItem: true,
});
return waitForPromises();
});
it('shows the private forks selector', () => {
expect(findPrivateForksSelector().exists()).toBe(true);
});
it('passes the required props to the private forks selector', () => {
expect(findPrivateForksSelector().props()).toMatchObject({
namespacePath: 'groupPath',
projectPath: 'fullPath',
helpPagePath: '/help/user/project/merge_requests/index.md',
newForkPath: '/fullPath/-/forks/new',
});
});
it('create merge request button should be enabled when there is a private fork selected', () => {
expect(findPrimaryButton().attributes.disabled).toEqual(false);
});
it('replaces the create branch and create merge request paths with forkPath with passing of work item project id', async () => {
jest.spyOn(axios, 'post');
mock
.onPost(
'/fullPath/-/branches?branch_name=suggested_branch_name&format=json&issue_iid=1&ref=master',
)
.reply(200, { data: { url: 'http://test.com/branch' } });
firePrimaryEvent();
await waitForPromises();
expect(axios.post).toHaveBeenCalledWith(
`/fullPath-fork-new/-/branches?branch_name=suggested_branch_name&format=json&issue_iid=1&ref=master`,
{
confidential_issue_project_id: 'gid://gitlab/Project/2',
},
);
await waitForPromises();
await nextTick();
expect(visitUrl).toHaveBeenCalledWith(
'/fullPath-fork-new/-/merge_requests/new?merge_request%5Bissue_iid%5D=1&merge_request%5Bsource_branch%5D=suggested_branch_name&merge_request%5Btarget_branch%5D=master',
);
});
});
});
});

View File

@ -18,6 +18,8 @@ describe('WorkItemCreateBranchMergeRequestSplitButton', () => {
workItemType: 'issue',
workItemId: '1',
workItemIid: '100',
projectId: 'gid://gitlab/Project/7',
isConfidentialWorkItem: false,
...props,
},
});
@ -51,6 +53,13 @@ describe('WorkItemCreateBranchMergeRequestSplitButton', () => {
expect(findMainButton().attributes('icon')).toBe('merge-request');
});
it('renders the main buttion with correct text when a confidential work item', async () => {
wrapper = createComponent({ isConfidentialWorkItem: true });
await findCreateModal().vm.$emit('fetchedPermissions', true);
expect(findMainButton().text()).toBe('Create confidential merge request');
});
it('hides the button when the user does not have permission to create merge requests', async () => {
await findCreateModal().vm.$emit('fetchedPermissions', false);

View File

@ -188,6 +188,10 @@ export const workItemQueryResponse = {
webPath: '/root',
__typename: 'UserCore',
},
project: {
id: 'gid://gitlab/Project/7',
__typename: 'Project',
},
namespace: {
__typename: 'Project',
id: '1',
@ -305,6 +309,10 @@ export const updateWorkItemMutationResponse = {
author: {
...mockAssignees[0],
},
project: {
id: 'gid://gitlab/Project/7',
__typename: 'Project',
},
namespace: {
__typename: 'Project',
id: '1',
@ -436,6 +444,10 @@ export const convertWorkItemMutationResponse = {
author: {
...mockAssignees[0],
},
project: {
id: 'gid://gitlab/Project/7',
__typename: 'Project',
},
namespace: {
__typename: 'Project',
id: '1',
@ -1271,6 +1283,10 @@ export const workItemResponseFactory = ({
updatedAt,
closedAt: null,
author,
project: {
id: 'gid://gitlab/Project/7',
__typename: 'Project',
},
namespace: {
__typename: 'Project',
id: '1',
@ -1635,6 +1651,10 @@ export const createWorkItemMutationResponse = {
author: {
...mockAssignees[0],
},
project: {
id: 'gid://gitlab/Project/7',
__typename: 'Project',
},
namespace: {
__typename: 'Project',
id: '1',
@ -2911,6 +2931,10 @@ export const changeWorkItemParentMutationResponse = {
author: {
...mockAssignees[0],
},
project: {
id: 'gid://gitlab/Project/7',
__typename: 'Project',
},
namespace: {
__typename: 'Project',
id: '1',
@ -5327,6 +5351,7 @@ export const createWorkItemQueryResponse = {
webUrl: 'http://127.0.0.1:3000/groups/gitlab-org/-/work_items/new',
reference: 'gitlab-org#56',
createNoteEmail: null,
project: null,
namespace: {
id: 'full-path-epic-id',
fullPath: 'full-path',

View File

@ -563,6 +563,77 @@ RSpec.describe Resolvers::MergeRequestsResolver, feature_category: :code_review_
end
end
context 'with blob path argument' do
subject(:resolve_query) { resolve_mr(project, blob_path: blob_path, target_branches: target_branches, state: state, created_after: created_after) }
let(:blob_path) { 'files/ruby/feature.rb' }
let(:state) { 'opened' }
let(:target_branches) { ['master'] }
let(:created_after) { 5.days.ago.to_s }
it 'filters merge requests by blob path' do
is_expected.to contain_exactly(merge_request_1)
end
context 'when state is not provided' do
let(:state) { nil }
it 'raises an ArgumentError' do
expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'state field must be specified to filter by blobPath') do
resolve_query
end
end
end
context 'when target_branches are not provided' do
let(:target_branches) { nil }
it 'raises an ArgumentError' do
expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'targetBranches field must be specified to filter by blobPath') do
resolve_query
end
end
end
context 'when created_after is not provided' do
let(:created_after) { nil }
it 'raises an ArgumentError' do
expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'createdAfter field must be specified to filter by blobPath') do
resolve_query
end
end
end
context 'when created_after is too much in the past' do
let(:created_after) { 31.days.ago }
it 'raises an ArgumentError' do
expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'createdAfter must be within the last 30 days to filter by blobPath') do
resolve_query
end
end
end
context 'when there are no merge requests that changed requested blob' do
let(:blob_path) { 'unknown' }
it 'does not find anything' do
is_expected.to be_empty
end
context 'when feature flag "filter_blob_path" is disabled' do
before do
stub_feature_flags(filter_blob_path: false)
end
it 'ignores requested blob path' do
is_expected.to contain_exactly(merge_request_1)
end
end
end
end
# subscribed filtering handled in request spec, spec/requests/api/graphql/merge_requests/merge_requests_spec.rb
describe 'combinations' do

View File

@ -107,6 +107,7 @@ RSpec.describe BoardsHelper do
allow(helper).to receive(:can?).with(user, :create_saved_replies, project.group).and_return(false)
allow(helper).to receive(:can?).with(user, :create_work_item, project.group).and_return(false)
allow(helper).to receive(:can?).with(user, :bulk_admin_epic, project).and_return(false)
allow(helper).to receive(:can?).with(user, :create_projects, project.group).and_return(false)
end
it 'returns board type as parent' do
@ -165,6 +166,7 @@ RSpec.describe BoardsHelper do
allow(helper).to receive(:can?).with(user, :create_saved_replies, base_group).and_return(false)
allow(helper).to receive(:can?).with(user, :create_work_item, base_group).and_return(false)
allow(helper).to receive(:can?).with(user, :bulk_admin_epic, base_group).and_return(false)
allow(helper).to receive(:can?).with(user, :create_projects, base_group).and_return(false)
end
it 'returns correct path for base group' do

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
require 'rspec'
require 'spec_helper'
require 'mime/types'
RSpec.describe Projects::Ml::MlflowHelper, feature_category: :mlops do
let_it_be(:project) { build_stubbed(:project) }
describe '#mlflow_tracking_url' do
it 'generates the correct data' do
expect(helper.mlflow_tracking_url(project)).to eq("http://localhost/api/v4/projects/#{project.id}/ml/mlflow/")
end
end
end

View File

@ -162,4 +162,50 @@ RSpec.describe Gitlab::Auth::Identity, :request_store, feature_category: :system
.to eq([primary_user.id, scoped_user.id])
end
end
describe '#link!' do
subject(:identity) { described_class.new(primary_user) }
context 'when user has not been linked already' do
it 'links primary identity to scoped identity' do
expect(identity).not_to be_linked
identity.link!(scoped_user)
expect(identity).to be_linked
expect(identity.scoped_user).to eq(scoped_user)
end
end
context 'when primary user has already been linked' do
let(:another_user) { create(:user) }
before do
identity.link!(scoped_user)
end
context 'when linking with another user' do
it 'raises an exception' do
expect { identity.link!(another_user) }
.to raise_error(described_class::IdentityLinkMismatchError)
.and not_change { identity.scoped_user }
end
end
context 'when linking with the same user' do
it 'is idempotent' do
expect { identity.link!(scoped_user) }.not_to raise_error
end
end
end
it 'appends scoped user details to application structured log' do
identity.link!(scoped_user)
expect(Gitlab::ApplicationContext.current).to include({
'meta.scoped_user' => scoped_user.username,
'meta.scoped_user_id' => scoped_user.id
})
end
end
end

View File

@ -572,28 +572,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build, feature_category: :pipeline_co
it 'does not propagate composite identity by default' do
expect(seed_attributes[:options].key?(:scoped_user_id)).to be(false)
end
context 'when pipeline user supports composite identity' do
before do
allow(user).to receive(:has_composite_identity?).and_return(true)
end
it 'does not propagate composite identity if composite user is not linked' do
expect(seed_attributes[:options].key?(:scoped_user_id)).to be(false)
end
context 'when composite identity is linked' do
let(:scoped_user) { create(:user) }
before do
::Gitlab::Auth::Identity.fabricate(user).link!(scoped_user)
end
it 'propagates the composite identity as `scoped_user_id` in the options' do
expect(seed_attributes[:options][:scoped_user_id]).to be(scoped_user.id)
end
end
end
end
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::PullRequests::ReviewRequestImporter, :clean_gitlab_redis_shared_state, feature_category: :importers do
include Import::UserMappingHelper
let_it_be(:project) { create(:project, :with_import_url, :import_user_mapping_enabled) }
let_it_be(:project) { create(:project, :with_import_url, :import_user_mapping_enabled, :in_group) }
let_it_be(:reviewer) { create(:user, username: 'alice') }
let_it_be(:source_user) do
create(

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Import::SourceUserMapper, :request_store, feature_category: :importers do
let_it_be(:namespace) { create(:namespace) }
let_it_be(:namespace) { create(:group) }
let_it_be(:import_type) { 'github' }
let_it_be(:source_hostname) { 'https://github.com' }
@ -200,6 +200,20 @@ RSpec.describe Gitlab::Import::SourceUserMapper, :request_store, feature_categor
end
end
context 'when namespace is a personal namespace' do
let_it_be(:namespace) { create(:namespace) }
let_it_be(:import_user) { create(:namespace_import_user, namespace: namespace).import_user }
it 'does not create any placeholder users and assigns the import user' do
expect { find_or_create_source_user }
.to change { Import::SourceUser.count }.by(1)
new_import_source_user = Import::SourceUser.last
expect(new_import_source_user.placeholder_user).to eq(import_user)
end
end
context 'when ActiveRecord::RecordNotUnique exception is raised during the source user creation' do
before do
allow_next_instance_of(::Import::SourceUser) do |source_user|

View File

@ -10,9 +10,8 @@ RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter, :clean_gitlab_redis_s
:project,
:with_import_url,
:import_user_mapping_enabled,
import_type: ::Import::SOURCE_GITEA,
namespace: create(:namespace, path: 'octocat')
)
:in_group,
import_type: ::Import::SOURCE_GITEA)
end
let_it_be(:source_user_mapper) do
@ -258,10 +257,9 @@ RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter, :clean_gitlab_redis_s
create(
:project,
:with_import_url,
:in_group,
:import_user_mapping_enabled,
import_type: ::Import::SOURCE_GITHUB,
namespace: create(:namespace, path: 'octocat-github')
)
import_type: ::Import::SOURCE_GITHUB)
end
let_it_be(:source_user_mapper) do

View File

@ -10,6 +10,7 @@ RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter, :clean_gitlab_r
:project,
:repository,
:with_import_url,
:in_group,
:import_user_mapping_enabled,
import_type: ::Import::SOURCE_GITEA
)
@ -351,6 +352,7 @@ RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter, :clean_gitlab_r
:project,
:repository,
:with_import_url,
:in_group,
:import_user_mapping_enabled,
import_type: ::Import::SOURCE_GITHUB
)

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::LegacyGithubImport::UserFormatter, feature_category: :importers do
include Import::GiteaHelper
let_it_be(:project) { create(:project, :import_user_mapping_enabled, import_type: 'gitea') }
let_it_be(:project) { create(:project, :import_user_mapping_enabled, :in_group, import_type: 'gitea') }
let_it_be(:source_user_mapper) do
Gitlab::Import::SourceUserMapper.new(

View File

@ -211,6 +211,15 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
end
end
describe '.by_blob_path' do
let(:path) { 'bar/branch-test.txt' }
it 'returns MRs that modified blob by provided path' do
expect(described_class.by_blob_path(path))
.to match_array([merge_request4])
end
end
describe '.no_review_requested_to' do
it 'returns MRs that the user has not been requested to review' do
expect(described_class.no_review_requested_to(user1))

View File

@ -2082,6 +2082,33 @@ RSpec.describe User, feature_category: :user_profile do
end
end
describe '#add_admin_note' do
let_it_be(:user) { create(:user) }
let(:note) { "Some note" }
subject(:add_admin_note) { user.add_admin_note(note) }
it 'adds the new note' do
add_admin_note
expect(user.note).to eq("#{note}\n")
end
context "when notes already exist" do
let(:existing_note) { "Existing note" }
before do
user.update!(note: existing_note)
end
it 'adds the new note' do
add_admin_note
expect(user.note).to eq("#{note}\n#{existing_note}")
end
end
end
describe '#confirm' do
let(:expired_confirmation_sent_at) { Date.today - described_class.confirm_within - 7.days }
let(:extant_confirmation_sent_at) { Date.today }

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