Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5395cd1a29
commit
6238e8c035
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
6250b300df6b3559e377ac6d218bba674ee045bf
|
||||
ed8e69891a82913e19430e094c192833ea5cb066
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -197,6 +197,15 @@ export const config = {
|
|||
MergeRequestApprovalState: {
|
||||
merge: true,
|
||||
},
|
||||
WorkItemType: {
|
||||
fields: {
|
||||
widgetDefinitions: {
|
||||
merge(existing = [], incoming) {
|
||||
return [...existing, ...incoming];
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -21,7 +21,7 @@ query getMergeRequestsCount(
|
|||
$environmentName: String
|
||||
$not: MergeRequestsResolverNegatedParams
|
||||
) {
|
||||
project(fullPath: $fullPath) {
|
||||
namespace: project(fullPath: $fullPath) {
|
||||
id
|
||||
openedMergeRequests: mergeRequests(
|
||||
state: opened
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ fragment WorkItem on WorkItem {
|
|||
webUrl
|
||||
reference(full: true)
|
||||
createNoteEmail
|
||||
project {
|
||||
id
|
||||
}
|
||||
namespace {
|
||||
id
|
||||
fullPath
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ fragment WorkItemHierarchy on WorkItem {
|
|||
setWorkItemMetadata
|
||||
createNote
|
||||
adminWorkItemLink
|
||||
markNoteAsInternal
|
||||
}
|
||||
widgets {
|
||||
type
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ module Groups
|
|||
current_user: current_user,
|
||||
parent_group: parent,
|
||||
params: safe_params
|
||||
).execute.page(params[:page])
|
||||
).execute
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 |
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -105,3 +105,5 @@ module Sidebars
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Sidebars::Admin::Menus::AdminOverviewMenu.prepend_mod_with('Sidebars::Admin::Menus::AdminOverviewMenu')
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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}") }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
|
|
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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|
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue