Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5adf6557e2
commit
63c306d960
|
@ -22,35 +22,6 @@ Graphql/Descriptions:
|
|||
- 'ee/app/graphql/types/vulnerability_report_type_enum.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_severity_enum.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_state_enum.rb'
|
||||
- 'app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb'
|
||||
- 'app/graphql/mutations/alert_management/alerts/set_assignees.rb'
|
||||
- 'app/graphql/mutations/alert_management/base.rb'
|
||||
- 'app/graphql/mutations/alert_management/http_integration/create.rb'
|
||||
- 'app/graphql/mutations/alert_management/http_integration/destroy.rb'
|
||||
- 'app/graphql/mutations/alert_management/http_integration/http_integration_base.rb'
|
||||
- 'app/graphql/mutations/alert_management/http_integration/reset_token.rb'
|
||||
- 'app/graphql/mutations/alert_management/http_integration/update.rb'
|
||||
- 'app/graphql/mutations/alert_management/prometheus_integration/create.rb'
|
||||
- 'app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb'
|
||||
- 'app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb'
|
||||
- 'app/graphql/mutations/alert_management/prometheus_integration/update.rb'
|
||||
- 'app/graphql/mutations/alert_management/update_alert_status.rb'
|
||||
- 'app/graphql/mutations/award_emojis/base.rb'
|
||||
- 'app/graphql/mutations/boards/common_mutation_arguments.rb'
|
||||
- 'app/graphql/mutations/boards/create.rb'
|
||||
- 'app/graphql/mutations/boards/destroy.rb'
|
||||
- 'app/graphql/mutations/boards/lists/destroy.rb'
|
||||
- 'app/graphql/mutations/boards/update.rb'
|
||||
- 'app/graphql/mutations/ci/ci_cd_settings_update.rb'
|
||||
- 'app/graphql/mutations/ci/job/base.rb'
|
||||
- 'app/graphql/mutations/ci/job/play.rb'
|
||||
- 'app/graphql/mutations/ci/job/retry.rb'
|
||||
- 'app/graphql/mutations/ci/job_token_scope/add_project.rb'
|
||||
- 'app/graphql/mutations/ci/job_token_scope/remove_project.rb'
|
||||
- 'app/graphql/mutations/ci/pipeline/base.rb'
|
||||
- 'app/graphql/mutations/ci/pipeline/retry.rb'
|
||||
- 'app/graphql/mutations/ci/runner/update.rb'
|
||||
- 'app/graphql/mutations/ci/runners_registration_token/reset.rb'
|
||||
- 'app/graphql/mutations/commits/create.rb'
|
||||
- 'app/graphql/mutations/concerns/mutations/assignable.rb'
|
||||
- 'app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb'
|
||||
|
|
|
@ -8,8 +8,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
|||
import createDefaultClient from '~/lib/graphql';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
// eslint-disable-next-line import/no-deprecated
|
||||
import { mergeUrlParams, urlParamsToObject, getUrlParamsArray } from '~/lib/utils/url_utility';
|
||||
import { mergeUrlParams, queryToObject, getUrlParamsArray } from '~/lib/utils/url_utility';
|
||||
import { ListType, flashAnimationDuration } from '../constants';
|
||||
import eventHub from '../eventhub';
|
||||
import ListAssignee from '../models/assignee';
|
||||
|
@ -597,8 +596,7 @@ const boardsStore = {
|
|||
|
||||
getListIssues(list, emptyIssues = true) {
|
||||
const data = {
|
||||
// eslint-disable-next-line import/no-deprecated
|
||||
...urlParamsToObject(this.filter.path),
|
||||
...queryToObject(this.filter.path, { gatherArrays: true }),
|
||||
page: list.page,
|
||||
};
|
||||
|
||||
|
|
|
@ -14,9 +14,11 @@ import {
|
|||
} from '~/behaviors/shortcuts/keybindings';
|
||||
import createFlash from '~/flash';
|
||||
import { isSingleViewStyle } from '~/helpers/diffs_helper';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { updateHistory } from '~/lib/utils/url_utility';
|
||||
import { __ } from '~/locale';
|
||||
import MrWidgetHowToMergeModal from '~/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue';
|
||||
import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
|
||||
|
||||
import notesEventHub from '../../notes/event_hub';
|
||||
|
@ -52,7 +54,6 @@ import CommitWidget from './commit_widget.vue';
|
|||
import CompareVersions from './compare_versions.vue';
|
||||
import DiffFile from './diff_file.vue';
|
||||
import HiddenFilesWarning from './hidden_files_warning.vue';
|
||||
import MergeConflictWarning from './merge_conflict_warning.vue';
|
||||
import NoChanges from './no_changes.vue';
|
||||
import PreRenderer from './pre_renderer.vue';
|
||||
import TreeList from './tree_list.vue';
|
||||
|
@ -65,7 +66,6 @@ export default {
|
|||
DiffFile,
|
||||
NoChanges,
|
||||
HiddenFilesWarning,
|
||||
MergeConflictWarning,
|
||||
CollapsedFilesWarning,
|
||||
CommitWidget,
|
||||
TreeList,
|
||||
|
@ -77,6 +77,7 @@ export default {
|
|||
DynamicScrollerItem,
|
||||
PreRenderer,
|
||||
VirtualScrollerScrollSync,
|
||||
MrWidgetHowToMergeModal,
|
||||
},
|
||||
alerts: {
|
||||
ALERT_OVERFLOW_HIDDEN,
|
||||
|
@ -164,6 +165,21 @@ export default {
|
|||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
sourceProjectDefaultUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
sourceProjectFullPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
isForked: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const treeWidth =
|
||||
|
@ -203,6 +219,8 @@ export default {
|
|||
'mrReviews',
|
||||
'renderTreeList',
|
||||
'showWhitespace',
|
||||
'targetBranchName',
|
||||
'branchName',
|
||||
]),
|
||||
...mapGetters('diffs', [
|
||||
'whichCollapsedTypes',
|
||||
|
@ -596,6 +614,9 @@ export default {
|
|||
},
|
||||
minTreeWidth: MIN_TREE_WIDTH,
|
||||
maxTreeWidth: MAX_TREE_WIDTH,
|
||||
howToMergeDocsPath: helpPagePath('user/project/merge_requests/reviews/index.md', {
|
||||
anchor: 'checkout-merge-requests-locally-through-the-head-ref',
|
||||
}),
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -615,12 +636,6 @@ export default {
|
|||
:plain-diff-path="plainDiffPath"
|
||||
:email-patch-path="emailPatchPath"
|
||||
/>
|
||||
<merge-conflict-warning
|
||||
v-if="visibleWarning == $options.alerts.ALERT_MERGE_CONFLICT"
|
||||
:limited="isLimitedContainer"
|
||||
:resolution-path="conflictResolutionPath"
|
||||
:mergeable="canMerge"
|
||||
/>
|
||||
<collapsed-files-warning
|
||||
v-if="visibleWarning == $options.alerts.ALERT_COLLAPSED_FILES"
|
||||
:limited="isLimitedContainer"
|
||||
|
@ -733,6 +748,15 @@ export default {
|
|||
<no-changes v-else :changes-empty-state-illustration="changesEmptyStateIllustration" />
|
||||
</div>
|
||||
</div>
|
||||
<mr-widget-how-to-merge-modal
|
||||
:is-fork="isForked"
|
||||
:can-merge="canMerge"
|
||||
:source-branch="branchName"
|
||||
:source-project-path="sourceProjectFullPath"
|
||||
:target-branch="targetBranchName"
|
||||
:source-project-default-url="sourceProjectDefaultUrl"
|
||||
:reviewing-docs-path="$options.howToMergeDocsPath"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
<script>
|
||||
import { GlButton, GlLoadingIcon, GlSafeHtmlDirective as SafeHtml, GlSprintf } from '@gitlab/ui';
|
||||
import {
|
||||
GlButton,
|
||||
GlLoadingIcon,
|
||||
GlSafeHtmlDirective as SafeHtml,
|
||||
GlSprintf,
|
||||
GlAlert,
|
||||
GlModalDirective,
|
||||
} from '@gitlab/ui';
|
||||
import { escape } from 'lodash';
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { IdState } from 'vendor/vue-virtual-scroller';
|
||||
|
@ -19,7 +26,7 @@ import {
|
|||
EVT_PERF_MARK_FIRST_DIFF_FILE_SHOWN,
|
||||
} from '../constants';
|
||||
import eventHub from '../event_hub';
|
||||
import { DIFF_FILE, GENERIC_ERROR } from '../i18n';
|
||||
import { DIFF_FILE, GENERIC_ERROR, CONFLICT_TEXT } from '../i18n';
|
||||
import { collapsedType, getShortShaFromFile } from '../utils/diff_file';
|
||||
import DiffContent from './diff_content.vue';
|
||||
import DiffFileHeader from './diff_file_header.vue';
|
||||
|
@ -31,9 +38,11 @@ export default {
|
|||
GlButton,
|
||||
GlLoadingIcon,
|
||||
GlSprintf,
|
||||
GlAlert,
|
||||
},
|
||||
directives: {
|
||||
SafeHtml,
|
||||
GlModalDirective,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin(), IdState({ idProp: (vm) => vm.file.file_hash })],
|
||||
props: {
|
||||
|
@ -93,7 +102,12 @@ export default {
|
|||
genericError: GENERIC_ERROR,
|
||||
},
|
||||
computed: {
|
||||
...mapState('diffs', ['currentDiffFileId', 'codequalityDiff']),
|
||||
...mapState('diffs', [
|
||||
'currentDiffFileId',
|
||||
'codequalityDiff',
|
||||
'conflictResolutionPath',
|
||||
'canMerge',
|
||||
]),
|
||||
...mapGetters(['isNotesFetched']),
|
||||
...mapGetters('diffs', ['getDiffFileDiscussions', 'isVirtualScrollingEnabled']),
|
||||
viewBlobHref() {
|
||||
|
@ -312,6 +326,7 @@ export default {
|
|||
this.idState.forkMessageVisible = false;
|
||||
},
|
||||
},
|
||||
CONFLICT_TEXT,
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -390,6 +405,55 @@ export default {
|
|||
<div v-else v-safe-html="errorMessage" class="nothing-here-block"></div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<gl-alert
|
||||
v-if="file.conflict_type"
|
||||
variant="danger"
|
||||
:dismissible="false"
|
||||
data-testid="conflictsAlert"
|
||||
>
|
||||
{{ $options.CONFLICT_TEXT[file.conflict_type] }}
|
||||
<template v-if="!canMerge">
|
||||
{{ __('Ask someone with write access to resolve it.') }}
|
||||
</template>
|
||||
<gl-sprintf
|
||||
v-else-if="conflictResolutionPath"
|
||||
:message="
|
||||
__(
|
||||
'You can %{gitlabLinkStart}resolve conflicts on GitLab%{gitlabLinkEnd} or %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}.',
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #gitlabLink="{ content }">
|
||||
<gl-button
|
||||
:href="conflictResolutionPath"
|
||||
variant="link"
|
||||
class="gl-vertical-align-text-bottom"
|
||||
>{{ content }}</gl-button
|
||||
>
|
||||
</template>
|
||||
<template #resolveLocally="{ content }">
|
||||
<gl-button
|
||||
v-gl-modal-directive="'modal-merge-info'"
|
||||
variant="link"
|
||||
class="gl-vertical-align-text-bottom"
|
||||
>{{ content }}</gl-button
|
||||
>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<gl-sprintf
|
||||
v-else
|
||||
:message="__('You can %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}.')"
|
||||
>
|
||||
<template #resolveLocally="{ content }">
|
||||
<gl-button
|
||||
v-gl-modal-directive="'modal-merge-info'"
|
||||
variant="link"
|
||||
class="gl-vertical-align-text-bottom"
|
||||
>{{ content }}</gl-button
|
||||
>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</gl-alert>
|
||||
<div
|
||||
v-if="showWarning"
|
||||
class="collapsed-file-warning gl-p-7 gl-bg-orange-50 gl-text-center gl-rounded-bottom-left-base gl-rounded-bottom-right-base"
|
||||
|
|
|
@ -134,12 +134,6 @@ export default {
|
|||
interopRightAttributes(props) {
|
||||
return getInteropNewSideAttributes(props.line.right);
|
||||
},
|
||||
conflictText: memoize(
|
||||
(line) => {
|
||||
return line.type === CONFLICT_MARKER_THEIR ? 'HEAD//our changes' : 'origin//their changes';
|
||||
},
|
||||
(line) => line.type,
|
||||
),
|
||||
lineContent: (line) => {
|
||||
if (line.isConflictMarker) {
|
||||
return line.type === CONFLICT_MARKER_THEIR ? 'HEAD//our changes' : 'origin//their changes';
|
||||
|
|
|
@ -25,3 +25,25 @@ export const SETTINGS_DROPDOWN = {
|
|||
fileByFile: __('Show one file at a time'),
|
||||
preferences: __('Preferences'),
|
||||
};
|
||||
|
||||
export const CONFLICT_TEXT = {
|
||||
both_modified: __('Conflict: This file was modified in both the source and target branches.'),
|
||||
modified_source_removed_target: __(
|
||||
'Conflict: This file was modified in the source branch, but removed in the target branch.',
|
||||
),
|
||||
modified_target_removed_source: __(
|
||||
'Conflict: This file was removed in the source branch, but modified in the target branch.',
|
||||
),
|
||||
renamed_same_file: __(
|
||||
'Conflict: This file was renamed differently in the source and target branches.',
|
||||
),
|
||||
removed_source_renamed_target: __(
|
||||
'Conflict: This file was removed in the source branch, but renamed in the target branch.',
|
||||
),
|
||||
removed_target_renamed_source: __(
|
||||
'Conflict: This file was renamed in the source branch, but removed in the target branch.',
|
||||
),
|
||||
both_added: __(
|
||||
'Conflict: This file was added both in the source and target branches, but with different contents.',
|
||||
),
|
||||
};
|
||||
|
|
|
@ -83,6 +83,9 @@ export default function initDiffsApp(store) {
|
|||
showWhitespaceDefault: parseBoolean(dataset.showWhitespaceDefault),
|
||||
viewDiffsFileByFile: parseBoolean(dataset.fileByFileDefault),
|
||||
defaultSuggestionCommitMessage: dataset.defaultSuggestionCommitMessage,
|
||||
sourceProjectDefaultUrl: dataset.sourceProjectDefaultUrl,
|
||||
sourceProjectFullPath: dataset.sourceProjectFullPath,
|
||||
isForked: parseBoolean(dataset.isForked),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -147,6 +150,9 @@ export default function initDiffsApp(store) {
|
|||
fileByFileUserPreference: this.viewDiffsFileByFile,
|
||||
defaultSuggestionCommitMessage: this.defaultSuggestionCommitMessage,
|
||||
rehydratedMrReviews: getReviewsForMergeRequest(mrPath),
|
||||
sourceProjectDefaultUrl: this.sourceProjectDefaultUrl,
|
||||
sourceProjectFullPath: this.sourceProjectFullPath,
|
||||
isForked: this.isForked,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -18,7 +18,7 @@ module Mutations
|
|||
argument :queue_name,
|
||||
GraphQL::Types::String,
|
||||
required: true,
|
||||
description: 'The name of the queue to delete jobs from.'
|
||||
description: 'Name of the queue to delete jobs from.'
|
||||
|
||||
field :result,
|
||||
Types::Admin::SidekiqQueues::DeleteJobsResponseType,
|
||||
|
|
|
@ -9,12 +9,12 @@ module Mutations
|
|||
argument :assignee_usernames,
|
||||
[GraphQL::Types::String],
|
||||
required: true,
|
||||
description: 'The usernames to assign to the alert. Replaces existing assignees by default.'
|
||||
description: 'Usernames to assign to the alert. Replaces existing assignees by default.'
|
||||
|
||||
argument :operation_mode,
|
||||
Types::MutationOperationModeEnum,
|
||||
required: false,
|
||||
description: 'The operation to perform. Defaults to REPLACE.'
|
||||
description: 'Operation to perform. Defaults to REPLACE.'
|
||||
|
||||
def resolve(args)
|
||||
alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
|
||||
|
|
|
@ -7,26 +7,26 @@ module Mutations
|
|||
|
||||
argument :project_path, GraphQL::Types::ID,
|
||||
required: true,
|
||||
description: "The project the alert to mutate is in."
|
||||
description: "Project the alert to mutate is in."
|
||||
|
||||
argument :iid, GraphQL::Types::String,
|
||||
required: true,
|
||||
description: "The IID of the alert to mutate."
|
||||
description: "IID of the alert to mutate."
|
||||
|
||||
field :alert,
|
||||
Types::AlertManagement::AlertType,
|
||||
null: true,
|
||||
description: "The alert after mutation."
|
||||
description: "Alert after mutation."
|
||||
|
||||
field :todo,
|
||||
Types::TodoType,
|
||||
null: true,
|
||||
description: "The to-do item after mutation."
|
||||
description: "To-do item after mutation."
|
||||
|
||||
field :issue,
|
||||
Types::IssueType,
|
||||
null: true,
|
||||
description: "The issue created after mutation."
|
||||
description: "Issue created after mutation."
|
||||
|
||||
authorize :update_alert_management_alert
|
||||
|
||||
|
|
|
@ -10,11 +10,11 @@ module Mutations
|
|||
|
||||
argument :project_path, GraphQL::Types::ID,
|
||||
required: true,
|
||||
description: 'The project to create the integration in.'
|
||||
description: 'Project to create the integration in.'
|
||||
|
||||
argument :name, GraphQL::Types::String,
|
||||
required: true,
|
||||
description: 'The name of the integration.'
|
||||
description: 'Name of the integration.'
|
||||
|
||||
argument :active, GraphQL::Types::Boolean,
|
||||
required: true,
|
||||
|
|
|
@ -8,7 +8,7 @@ module Mutations
|
|||
|
||||
argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration],
|
||||
required: true,
|
||||
description: "The ID of the integration to remove."
|
||||
description: "ID of the integration to remove."
|
||||
|
||||
def resolve(id:)
|
||||
integration = authorized_find!(id: id)
|
||||
|
|
|
@ -7,7 +7,7 @@ module Mutations
|
|||
field :integration,
|
||||
Types::AlertManagement::HttpIntegrationType,
|
||||
null: true,
|
||||
description: "The HTTP integration."
|
||||
description: "HTTP integration."
|
||||
|
||||
authorize :admin_operations
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ module Mutations
|
|||
|
||||
argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration],
|
||||
required: true,
|
||||
description: "The ID of the integration to mutate."
|
||||
description: "ID of the integration to mutate."
|
||||
|
||||
def resolve(id:)
|
||||
integration = authorized_find!(id: id)
|
||||
|
|
|
@ -8,11 +8,11 @@ module Mutations
|
|||
|
||||
argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration],
|
||||
required: true,
|
||||
description: "The ID of the integration to mutate."
|
||||
description: "ID of the integration to mutate."
|
||||
|
||||
argument :name, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: "The name of the integration."
|
||||
description: "Name of the integration."
|
||||
|
||||
argument :active, GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
|
|
|
@ -10,7 +10,7 @@ module Mutations
|
|||
|
||||
argument :project_path, GraphQL::Types::ID,
|
||||
required: true,
|
||||
description: 'The project to create the integration in.'
|
||||
description: 'Project to create the integration in.'
|
||||
|
||||
argument :active, GraphQL::Types::Boolean,
|
||||
required: true,
|
||||
|
|
|
@ -7,7 +7,7 @@ module Mutations
|
|||
field :integration,
|
||||
Types::AlertManagement::PrometheusIntegrationType,
|
||||
null: true,
|
||||
description: "The newly created integration."
|
||||
description: "Newly created integration."
|
||||
|
||||
authorize :admin_project
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ module Mutations
|
|||
|
||||
argument :id, Types::GlobalIDType[::Integrations::Prometheus],
|
||||
required: true,
|
||||
description: "The ID of the integration to mutate."
|
||||
description: "ID of the integration to mutate."
|
||||
|
||||
def resolve(id:)
|
||||
integration = authorized_find!(id: id)
|
||||
|
|
|
@ -8,7 +8,7 @@ module Mutations
|
|||
|
||||
argument :id, Types::GlobalIDType[::Integrations::Prometheus],
|
||||
required: true,
|
||||
description: "The ID of the integration to mutate."
|
||||
description: "ID of the integration to mutate."
|
||||
|
||||
argument :active, GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
|
|
|
@ -7,7 +7,7 @@ module Mutations
|
|||
|
||||
argument :status, Types::AlertManagement::StatusEnum,
|
||||
required: true,
|
||||
description: 'The status to set the alert.'
|
||||
description: 'Status to set the alert.'
|
||||
|
||||
def resolve(project_path:, iid:, status:)
|
||||
alert = authorized_find!(project_path: project_path, iid: iid)
|
||||
|
|
|
@ -12,7 +12,7 @@ module Mutations
|
|||
argument :awardable_id,
|
||||
::Types::GlobalIDType[::Awardable],
|
||||
required: true,
|
||||
description: 'The global ID of the awardable resource.'
|
||||
description: 'Global ID of the awardable resource.'
|
||||
|
||||
argument :name,
|
||||
GraphQL::Types::String,
|
||||
|
@ -22,7 +22,7 @@ module Mutations
|
|||
field :award_emoji,
|
||||
Types::AwardEmojis::AwardEmojiType,
|
||||
null: true,
|
||||
description: 'The award emoji after mutation.'
|
||||
description: 'Award emoji after mutation.'
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ module Mutations
|
|||
argument :name,
|
||||
GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'The board name.'
|
||||
description: 'Board name.'
|
||||
argument :hide_backlog_list,
|
||||
GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
|
|
|
@ -12,7 +12,7 @@ module Mutations
|
|||
field :board,
|
||||
Types::BoardType,
|
||||
null: true,
|
||||
description: 'The board after mutation.'
|
||||
description: 'Board after mutation.'
|
||||
|
||||
authorize :admin_issue_board
|
||||
|
||||
|
|
|
@ -8,11 +8,11 @@ module Mutations
|
|||
field :board,
|
||||
Types::BoardType,
|
||||
null: true,
|
||||
description: 'The board after mutation.'
|
||||
description: 'Board after mutation.'
|
||||
argument :id,
|
||||
::Types::GlobalIDType[::Board],
|
||||
required: true,
|
||||
description: 'The global ID of the board to destroy.'
|
||||
description: 'Global ID of the board to destroy.'
|
||||
|
||||
authorize :admin_issue_board
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ module Mutations
|
|||
field :list,
|
||||
Types::BoardListType,
|
||||
null: true,
|
||||
description: 'The list after mutation.'
|
||||
description: 'List after mutation.'
|
||||
|
||||
argument :list_id, ::Types::GlobalIDType[::List],
|
||||
required: true,
|
||||
|
|
|
@ -10,12 +10,12 @@ module Mutations
|
|||
argument :id,
|
||||
::Types::GlobalIDType[::Board],
|
||||
required: true,
|
||||
description: 'The board global ID.'
|
||||
description: 'Board global ID.'
|
||||
|
||||
field :board,
|
||||
Types::BoardType,
|
||||
null: true,
|
||||
description: 'The board after mutation.'
|
||||
description: 'Board after mutation.'
|
||||
|
||||
authorize :admin_issue_board
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ module Mutations
|
|||
field :ci_cd_settings,
|
||||
Types::Ci::CiCdSettingType,
|
||||
null: false,
|
||||
description: 'The CI/CD settings after mutation.'
|
||||
description: 'CI/CD settings after mutation.'
|
||||
|
||||
def resolve(full_path:, **args)
|
||||
project = authorized_find!(full_path)
|
||||
|
|
|
@ -8,7 +8,7 @@ module Mutations
|
|||
|
||||
argument :id, JobID,
|
||||
required: true,
|
||||
description: 'The ID of the job to mutate.'
|
||||
description: 'ID of the job to mutate.'
|
||||
|
||||
def find_object(id: )
|
||||
# TODO: remove this line when the compatibility layer is removed
|
||||
|
|
|
@ -9,7 +9,7 @@ module Mutations
|
|||
field :job,
|
||||
Types::Ci::JobType,
|
||||
null: true,
|
||||
description: 'The job after the mutation.'
|
||||
description: 'Job after the mutation.'
|
||||
|
||||
authorize :update_build
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ module Mutations
|
|||
field :job,
|
||||
Types::Ci::JobType,
|
||||
null: true,
|
||||
description: 'The job after the mutation.'
|
||||
description: 'Job after the mutation.'
|
||||
|
||||
authorize :update_build
|
||||
|
||||
|
|
|
@ -12,16 +12,16 @@ module Mutations
|
|||
|
||||
argument :project_path, GraphQL::Types::ID,
|
||||
required: true,
|
||||
description: 'The project that the CI job token scope belongs to.'
|
||||
description: 'Project that the CI job token scope belongs to.'
|
||||
|
||||
argument :target_project_path, GraphQL::Types::ID,
|
||||
required: true,
|
||||
description: 'The project to be added to the CI job token scope.'
|
||||
description: 'Project to be added to the CI job token scope.'
|
||||
|
||||
field :ci_job_token_scope,
|
||||
Types::Ci::JobTokenScopeType,
|
||||
null: true,
|
||||
description: "The CI job token's scope of access."
|
||||
description: "CI job token's scope of access."
|
||||
|
||||
def resolve(project_path:, target_project_path:)
|
||||
project = authorized_find!(project_path)
|
||||
|
|
|
@ -12,16 +12,16 @@ module Mutations
|
|||
|
||||
argument :project_path, GraphQL::Types::ID,
|
||||
required: true,
|
||||
description: 'The project that the CI job token scope belongs to.'
|
||||
description: 'Project that the CI job token scope belongs to.'
|
||||
|
||||
argument :target_project_path, GraphQL::Types::ID,
|
||||
required: true,
|
||||
description: 'The project to be removed from the CI job token scope.'
|
||||
description: 'Project to be removed from the CI job token scope.'
|
||||
|
||||
field :ci_job_token_scope,
|
||||
Types::Ci::JobTokenScopeType,
|
||||
null: true,
|
||||
description: "The CI job token's scope of access."
|
||||
description: "CI job token's scope of access."
|
||||
|
||||
def resolve(project_path:, target_project_path:)
|
||||
project = authorized_find!(project_path)
|
||||
|
|
|
@ -8,7 +8,7 @@ module Mutations
|
|||
|
||||
argument :id, PipelineID,
|
||||
required: true,
|
||||
description: 'The ID of the pipeline to mutate.'
|
||||
description: 'ID of the pipeline to mutate.'
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ module Mutations
|
|||
field :pipeline,
|
||||
Types::Ci::PipelineType,
|
||||
null: true,
|
||||
description: 'The pipeline after mutation.'
|
||||
description: 'Pipeline after mutation.'
|
||||
|
||||
authorize :update_pipeline
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ module Mutations
|
|||
field :runner,
|
||||
Types::Ci::RunnerType,
|
||||
null: true,
|
||||
description: 'The runner after mutation.'
|
||||
description: 'Runner after mutation.'
|
||||
|
||||
def resolve(id:, **runner_attrs)
|
||||
runner = authorized_find!(id)
|
||||
|
|
|
@ -21,7 +21,7 @@ module Mutations
|
|||
field :token,
|
||||
GraphQL::Types::String,
|
||||
null: true,
|
||||
description: 'The runner token after mutation.'
|
||||
description: 'Runner token after mutation.'
|
||||
|
||||
def resolve(**args)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ResolvesIds
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def resolve_ids(ids, type)
|
||||
Array.wrap(ids).map do |id|
|
||||
next unless id.present?
|
||||
|
||||
# TODO: remove this line when the compatibility layer is removed
|
||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
|
||||
id = type.coerce_isolated_input(id)
|
||||
id.model_id
|
||||
end.compact
|
||||
end
|
||||
end
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
module ResolvesSnippets
|
||||
extend ActiveSupport::Concern
|
||||
include ResolvesIds
|
||||
|
||||
included do
|
||||
type Types::SnippetType.connection_type, null: true
|
||||
|
@ -27,22 +28,11 @@ module ResolvesSnippets
|
|||
|
||||
def snippet_finder_params(args)
|
||||
{
|
||||
ids: resolve_ids(args[:ids]),
|
||||
ids: resolve_ids(args[:ids], ::Types::GlobalIDType[::Snippet]),
|
||||
scope: args[:visibility]
|
||||
}.merge(options_by_type(args[:type]))
|
||||
end
|
||||
|
||||
def resolve_ids(ids, type = ::Types::GlobalIDType[::Snippet])
|
||||
Array.wrap(ids).map do |id|
|
||||
next unless id.present?
|
||||
|
||||
# TODO: remove this line when the compatibility layer is removed
|
||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
|
||||
id = type.coerce_isolated_input(id)
|
||||
id.model_id
|
||||
end.compact
|
||||
end
|
||||
|
||||
def options_by_type(type)
|
||||
case type
|
||||
when 'personal'
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
module Resolvers
|
||||
class SnippetsResolver < BaseResolver
|
||||
include ResolvesIds
|
||||
include ResolvesSnippets
|
||||
|
||||
ERROR_MESSAGE = 'Filtering by both an author and a project is not supported'
|
||||
|
|
|
@ -3,33 +3,50 @@
|
|||
module Resolvers
|
||||
class TimelogResolver < BaseResolver
|
||||
include LooksAhead
|
||||
include ResolvesIds
|
||||
|
||||
type ::Types::TimelogType.connection_type, null: false
|
||||
|
||||
argument :start_date, Types::TimeType,
|
||||
required: false,
|
||||
description: 'List time logs within a date range where the logged date is equal to or after startDate.'
|
||||
description: 'List timelogs within a date range where the logged date is equal to or after startDate.'
|
||||
|
||||
argument :end_date, Types::TimeType,
|
||||
required: false,
|
||||
description: 'List time logs within a date range where the logged date is equal to or before endDate.'
|
||||
description: 'List timelogs within a date range where the logged date is equal to or before endDate.'
|
||||
|
||||
argument :start_time, Types::TimeType,
|
||||
required: false,
|
||||
description: 'List time-logs within a time range where the logged time is equal to or after startTime.'
|
||||
description: 'List timelogs within a time range where the logged time is equal to or after startTime.'
|
||||
|
||||
argument :end_time, Types::TimeType,
|
||||
required: false,
|
||||
description: 'List time-logs within a time range where the logged time is equal to or before endTime.'
|
||||
description: 'List timelogs within a time range where the logged time is equal to or before endTime.'
|
||||
|
||||
argument :project_id, ::Types::GlobalIDType[::Project],
|
||||
required: false,
|
||||
description: 'List timelogs for a project.'
|
||||
|
||||
argument :group_id, ::Types::GlobalIDType[::Group],
|
||||
required: false,
|
||||
description: 'List timelogs for a group.'
|
||||
|
||||
argument :username, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'List timelogs for a user.'
|
||||
|
||||
def resolve_with_lookahead(**args)
|
||||
build_timelogs
|
||||
validate_args!(object, args)
|
||||
|
||||
timelogs = object&.timelogs || Timelog.limit(GitlabSchema.default_max_page_size)
|
||||
|
||||
if args.any?
|
||||
validate_args!(args)
|
||||
build_parsed_args(args)
|
||||
validate_time_difference!
|
||||
apply_time_filter
|
||||
args = parse_datetime_args(args)
|
||||
|
||||
timelogs = apply_user_filter(timelogs, args)
|
||||
timelogs = apply_project_filter(timelogs, args)
|
||||
timelogs = apply_time_filter(timelogs, args)
|
||||
timelogs = apply_group_filter(timelogs, args)
|
||||
end
|
||||
|
||||
apply_lookahead(timelogs)
|
||||
|
@ -37,30 +54,32 @@ module Resolvers
|
|||
|
||||
private
|
||||
|
||||
attr_reader :parsed_args, :timelogs
|
||||
|
||||
def preloads
|
||||
{
|
||||
note: [:note]
|
||||
}
|
||||
end
|
||||
|
||||
def validate_args!(args)
|
||||
if args[:start_time] && args[:start_date]
|
||||
def validate_args!(object, args)
|
||||
if args.empty? && object.nil?
|
||||
raise_argument_error('Provide at least one argument')
|
||||
elsif args[:start_time] && args[:start_date]
|
||||
raise_argument_error('Provide either a start date or time, but not both')
|
||||
elsif args[:end_time] && args[:end_date]
|
||||
raise_argument_error('Provide either an end date or time, but not both')
|
||||
end
|
||||
end
|
||||
|
||||
def build_parsed_args(args)
|
||||
def parse_datetime_args(args)
|
||||
if times_provided?(args)
|
||||
@parsed_args = args
|
||||
args
|
||||
else
|
||||
@parsed_args = args.except(:start_date, :end_date)
|
||||
parsed_args = args.except(:start_date, :end_date)
|
||||
|
||||
@parsed_args[:start_time] = args[:start_date].beginning_of_day if args[:start_date]
|
||||
@parsed_args[:end_time] = args[:end_date].end_of_day if args[:end_date]
|
||||
parsed_args[:start_time] = args[:start_date].beginning_of_day if args[:start_date]
|
||||
parsed_args[:end_time] = args[:end_date].end_of_day if args[:end_date]
|
||||
|
||||
parsed_args
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -68,23 +87,51 @@ module Resolvers
|
|||
args[:start_time] && args[:end_time]
|
||||
end
|
||||
|
||||
def validate_time_difference!
|
||||
return unless end_time_before_start_time?
|
||||
def validate_time_difference!(args)
|
||||
return unless end_time_before_start_time?(args)
|
||||
|
||||
raise_argument_error('Start argument must be before End argument')
|
||||
end
|
||||
|
||||
def end_time_before_start_time?
|
||||
times_provided?(parsed_args) && parsed_args[:end_time] < parsed_args[:start_time]
|
||||
def end_time_before_start_time?(args)
|
||||
times_provided?(args) && args[:end_time] < args[:start_time]
|
||||
end
|
||||
|
||||
def build_timelogs
|
||||
@timelogs = Timelog.in_group(object)
|
||||
def apply_project_filter(timelogs, args)
|
||||
return timelogs unless args[:project_id]
|
||||
|
||||
project = resolve_ids(args[:project_id], ::Types::GlobalIDType[::Project])
|
||||
timelogs.in_project(project)
|
||||
end
|
||||
|
||||
def apply_time_filter
|
||||
@timelogs = timelogs.at_or_after(parsed_args[:start_time]) if parsed_args[:start_time]
|
||||
@timelogs = timelogs.at_or_before(parsed_args[:end_time]) if parsed_args[:end_time]
|
||||
def apply_group_filter(timelogs, args)
|
||||
return timelogs unless args[:group_id]
|
||||
|
||||
group = Group.find_by_id(resolve_ids(args[:group_id], ::Types::GlobalIDType[::Group]))
|
||||
timelogs.in_group(group)
|
||||
end
|
||||
|
||||
def apply_user_filter(timelogs, args)
|
||||
return timelogs unless args[:username]
|
||||
|
||||
user = UserFinder.new(args[:username]).find_by_username!
|
||||
timelogs.for_user(user)
|
||||
end
|
||||
|
||||
def apply_time_filter(timelogs, args)
|
||||
return timelogs unless args[:start_time] || args[:end_time]
|
||||
|
||||
validate_time_difference!(args)
|
||||
|
||||
if args[:start_time]
|
||||
timelogs = timelogs.at_or_after(args[:start_time])
|
||||
end
|
||||
|
||||
if args[:end_time]
|
||||
timelogs = timelogs.at_or_before(args[:end_time])
|
||||
end
|
||||
|
||||
timelogs
|
||||
end
|
||||
|
||||
def raise_argument_error(message)
|
||||
|
|
|
@ -354,6 +354,13 @@ module Types
|
|||
description: 'The CI Job Tokens scope of access.',
|
||||
resolver: Resolvers::Ci::JobTokenScopeResolver
|
||||
|
||||
field :timelogs,
|
||||
Types::TimelogType.connection_type, null: true,
|
||||
description: 'Time logged on issues and merge requests in the project.',
|
||||
extras: [:lookahead],
|
||||
complexity: 5,
|
||||
resolver: ::Resolvers::TimelogResolver
|
||||
|
||||
def label(title:)
|
||||
BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args|
|
||||
LabelsFinder
|
||||
|
|
|
@ -131,6 +131,13 @@ module Types
|
|||
|
||||
field :ci_config, resolver: Resolvers::Ci::ConfigResolver, complexity: 126 # AUTHENTICATED_MAX_COMPLEXITY / 2 + 1
|
||||
|
||||
field :timelogs, Types::TimelogType.connection_type,
|
||||
null: true,
|
||||
description: 'Find timelogs visible to the current user.',
|
||||
extras: [:lookahead],
|
||||
complexity: 5,
|
||||
resolver: ::Resolvers::TimelogResolver
|
||||
|
||||
def design_management
|
||||
DesignManagementObject.new(nil)
|
||||
end
|
||||
|
|
|
@ -104,6 +104,13 @@ module Types
|
|||
Types::UserCalloutType.connection_type,
|
||||
null: true,
|
||||
description: 'User callouts that belong to the user.'
|
||||
field :timelogs,
|
||||
Types::TimelogType.connection_type,
|
||||
null: true,
|
||||
description: 'Time logged by the user.',
|
||||
extras: [:lookahead],
|
||||
complexity: 5,
|
||||
resolver: ::Resolvers::TimelogResolver
|
||||
|
||||
definition_methods do
|
||||
def resolve_type(object, context)
|
||||
|
|
|
@ -186,7 +186,10 @@ module MergeRequestsHelper
|
|||
show_suggest_popover: show_suggest_popover?.to_s,
|
||||
show_whitespace_default: @show_whitespace_default.to_s,
|
||||
file_by_file_default: @file_by_file_default.to_s,
|
||||
default_suggestion_commit_message: default_suggestion_commit_message
|
||||
default_suggestion_commit_message: default_suggestion_commit_message,
|
||||
source_project_default_url: @merge_request.source_project && default_url_to_repo(@merge_request.source_project),
|
||||
source_project_full_path: @merge_request.source_project&.full_path,
|
||||
is_forked: @project.forked?.to_s
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ module Ci
|
|||
|
||||
scope :ref_protected, -> { where(protected: true) }
|
||||
scope :queued_before, ->(time) { where(arel_table[:created_at].lt(time)) }
|
||||
scope :with_instance_runners, -> { where(instance_runners_enabled: true) }
|
||||
|
||||
def self.upsert_from_build!(build)
|
||||
entry = self.new(args_from_build(build))
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
module RestrictedSignup
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
def validate_admin_signup_restrictions(email)
|
||||
return if allowed_domain?(email)
|
||||
|
||||
if allowlist_present?
|
||||
return _('domain is not authorized for sign-up.')
|
||||
elsif denied_domain?(email)
|
||||
return _('is not from an allowed domain.')
|
||||
elsif restricted_email?(email)
|
||||
return _('is not allowed. Try again with a different email address, or contact your GitLab admin.')
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def denied_domain?(email)
|
||||
return false unless Gitlab::CurrentSettings.domain_denylist_enabled?
|
||||
|
||||
denied_domains = Gitlab::CurrentSettings.domain_denylist
|
||||
denied_domains.present? && domain_matches?(denied_domains, email)
|
||||
end
|
||||
|
||||
def allowlist_present?
|
||||
Gitlab::CurrentSettings.domain_allowlist.present?
|
||||
end
|
||||
|
||||
def allowed_domain?(email)
|
||||
allowed_domains = Gitlab::CurrentSettings.domain_allowlist
|
||||
allowlist_present? && domain_matches?(allowed_domains, email)
|
||||
end
|
||||
|
||||
def restricted_email?(email)
|
||||
return false unless Gitlab::CurrentSettings.email_restrictions_enabled?
|
||||
|
||||
restrictions = Gitlab::CurrentSettings.email_restrictions
|
||||
restrictions.present? && Gitlab::UntrustedRegexp.new(restrictions).match?(email)
|
||||
end
|
||||
|
||||
def domain_matches?(email_domains, email)
|
||||
signup_domain = Mail::Address.new(email).domain
|
||||
email_domains.any? do |domain|
|
||||
escaped = Regexp.escape(domain).gsub('\*', '.*?')
|
||||
regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
|
||||
signup_domain =~ regexp
|
||||
end
|
||||
end
|
||||
end
|
|
@ -730,6 +730,10 @@ class Group < Namespace
|
|||
end
|
||||
# rubocop: enable CodeReuse/ServiceClass
|
||||
|
||||
def timelogs
|
||||
Timelog.in_group(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def max_member_access(user_ids)
|
||||
|
|
|
@ -12,6 +12,7 @@ class Member < ApplicationRecord
|
|||
include Gitlab::Utils::StrongMemoize
|
||||
include FromUnion
|
||||
include UpdateHighestRole
|
||||
include RestrictedSignup
|
||||
|
||||
AVATAR_SIZE = 40
|
||||
ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10
|
||||
|
@ -42,6 +43,7 @@ class Member < ApplicationRecord
|
|||
scope: [:source_type, :source_id],
|
||||
allow_nil: true
|
||||
}
|
||||
validate :signup_email_valid?, on: :create, if: ->(member) { member.invite_email.present? }
|
||||
validates :user_id,
|
||||
uniqueness: {
|
||||
message: _('project bots cannot be added to other groups / projects')
|
||||
|
@ -433,6 +435,12 @@ class Member < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def signup_email_valid?
|
||||
error = validate_admin_signup_restrictions(invite_email)
|
||||
|
||||
errors.add(:user, error) if error
|
||||
end
|
||||
|
||||
def update_highest_role?
|
||||
return unless user_id.present?
|
||||
|
||||
|
|
|
@ -11,6 +11,9 @@ class NamespaceSetting < ApplicationRecord
|
|||
validate :allow_mfa_for_group
|
||||
validate :allow_resource_access_token_creation_for_group
|
||||
|
||||
before_save :set_prevent_sharing_groups_outside_hierarchy, if: -> { user_cap_enabled? }
|
||||
after_save :disable_project_sharing!, if: -> { user_cap_enabled? }
|
||||
|
||||
before_validation :normalize_default_branch_name
|
||||
|
||||
NAMESPACE_SETTINGS_PARAMS = [:default_branch_name, :delayed_project_removal,
|
||||
|
@ -19,6 +22,12 @@ class NamespaceSetting < ApplicationRecord
|
|||
|
||||
self.primary_key = :namespace_id
|
||||
|
||||
def prevent_sharing_groups_outside_hierarchy
|
||||
return super if namespace.root?
|
||||
|
||||
namespace.root_ancestor.prevent_sharing_groups_outside_hierarchy
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def normalize_default_branch_name
|
||||
|
@ -48,6 +57,18 @@ class NamespaceSetting < ApplicationRecord
|
|||
errors.add(:resource_access_token_creation_allowed, _('is not allowed since the group is not top-level group.'))
|
||||
end
|
||||
end
|
||||
|
||||
def set_prevent_sharing_groups_outside_hierarchy
|
||||
self.prevent_sharing_groups_outside_hierarchy = true
|
||||
end
|
||||
|
||||
def disable_project_sharing!
|
||||
namespace.update_attribute(:share_with_group_lock, true)
|
||||
end
|
||||
|
||||
def user_cap_enabled?
|
||||
new_user_signups_cap.present? && namespace.root?
|
||||
end
|
||||
end
|
||||
|
||||
NamespaceSetting.prepend_mod_with('NamespaceSetting')
|
||||
|
|
|
@ -19,6 +19,14 @@ class Timelog < ApplicationRecord
|
|||
joins(:project).where(projects: { namespace: group.self_and_descendants })
|
||||
end
|
||||
|
||||
scope :in_project, -> (project) do
|
||||
where(project: project)
|
||||
end
|
||||
|
||||
scope :for_user, -> (user) do
|
||||
where(user: user)
|
||||
end
|
||||
|
||||
scope :at_or_after, -> (start_time) do
|
||||
where('spent_at >= ?', start_time)
|
||||
end
|
||||
|
|
|
@ -26,6 +26,7 @@ class User < ApplicationRecord
|
|||
include UpdateHighestRole
|
||||
include HasUserType
|
||||
include Gitlab::Auth::Otp::Fortinet
|
||||
include RestrictedSignup
|
||||
|
||||
DEFAULT_NOTIFICATION_LEVEL = :participating
|
||||
|
||||
|
@ -211,6 +212,8 @@ class User < ApplicationRecord
|
|||
|
||||
has_many :in_product_marketing_emails, class_name: '::Users::InProductMarketingEmail'
|
||||
|
||||
has_many :timelogs
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
@ -235,8 +238,7 @@ class User < ApplicationRecord
|
|||
validate :owns_notification_email, if: :notification_email_changed?
|
||||
validate :owns_public_email, if: :public_email_changed?
|
||||
validate :owns_commit_email, if: :commit_email_changed?
|
||||
validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
|
||||
validate :check_email_restrictions, on: :create, if: ->(user) { !user.created_by_id }
|
||||
validate :signup_email_valid?, on: :create, if: ->(user) { !user.created_by_id }
|
||||
validate :check_username_format, if: :username_changed?
|
||||
|
||||
validates :theme_id, allow_nil: true, inclusion: { in: Gitlab::Themes.valid_ids,
|
||||
|
@ -2070,51 +2072,10 @@ class User < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def signup_domain_valid?
|
||||
valid = true
|
||||
error = nil
|
||||
def signup_email_valid?
|
||||
error = validate_admin_signup_restrictions(email)
|
||||
|
||||
if Gitlab::CurrentSettings.domain_denylist_enabled?
|
||||
blocked_domains = Gitlab::CurrentSettings.domain_denylist
|
||||
if domain_matches?(blocked_domains, email)
|
||||
error = 'is not from an allowed domain.'
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
|
||||
allowed_domains = Gitlab::CurrentSettings.domain_allowlist
|
||||
unless allowed_domains.blank?
|
||||
if domain_matches?(allowed_domains, email)
|
||||
valid = true
|
||||
else
|
||||
error = "domain is not authorized for sign-up"
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
|
||||
errors.add(:email, error) unless valid
|
||||
|
||||
valid
|
||||
end
|
||||
|
||||
def domain_matches?(email_domains, email)
|
||||
signup_domain = Mail::Address.new(email).domain
|
||||
email_domains.any? do |domain|
|
||||
escaped = Regexp.escape(domain).gsub('\*', '.*?')
|
||||
regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
|
||||
signup_domain =~ regexp
|
||||
end
|
||||
end
|
||||
|
||||
def check_email_restrictions
|
||||
return unless Gitlab::CurrentSettings.email_restrictions_enabled?
|
||||
|
||||
restrictions = Gitlab::CurrentSettings.email_restrictions
|
||||
return if restrictions.blank?
|
||||
|
||||
if Gitlab::UntrustedRegexp.new(restrictions).match?(email)
|
||||
errors.add(:email, _('is not allowed. Try again with a different email address, or contact your GitLab admin.'))
|
||||
end
|
||||
errors.add(:email, error) if error
|
||||
end
|
||||
|
||||
def check_username_format
|
||||
|
|
|
@ -38,7 +38,7 @@ module Ci
|
|||
end
|
||||
|
||||
def stage_dependent_jobs(processable)
|
||||
skipped_jobs(processable).scheduling_type_stage.after_stage(processable.stage_idx)
|
||||
skipped_jobs(processable).after_stage(processable.stage_idx)
|
||||
end
|
||||
|
||||
def needs_dependent_jobs(processable)
|
||||
|
|
|
@ -53,6 +53,10 @@ module Ci
|
|||
relation.pluck(:id)
|
||||
end
|
||||
|
||||
def use_denormalized_shared_runners_data?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def running_builds_for_shared_runners
|
||||
|
|
|
@ -11,25 +11,9 @@ module Ci
|
|||
|
||||
# rubocop:disable CodeReuse/ActiveRecord
|
||||
def builds_for_shared_runner
|
||||
relation = new_builds
|
||||
# don't run projects which have not enabled shared runners and builds
|
||||
.joins('INNER JOIN projects ON ci_pending_builds.project_id = projects.id')
|
||||
.where(projects: { shared_runners_enabled: true, pending_delete: false })
|
||||
.joins('LEFT JOIN project_features ON ci_pending_builds.project_id = project_features.project_id')
|
||||
.where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0')
|
||||
shared_builds = builds_available_for_shared_runners
|
||||
|
||||
if Feature.enabled?(:ci_queueing_disaster_recovery_disable_fair_scheduling, runner, type: :ops, default_enabled: :yaml)
|
||||
# if disaster recovery is enabled, we fallback to FIFO scheduling
|
||||
relation.order('ci_pending_builds.build_id ASC')
|
||||
else
|
||||
# Implement fair scheduling
|
||||
# this returns builds that are ordered by number of running builds
|
||||
# we prefer projects that don't use shared runners at all
|
||||
relation
|
||||
.with(running_builds_for_shared_runners_cte.to_arel)
|
||||
.joins("LEFT JOIN project_builds ON ci_pending_builds.project_id = project_builds.project_id")
|
||||
.order(Arel.sql('COALESCE(project_builds.running_builds, 0) ASC'), 'ci_pending_builds.build_id ASC')
|
||||
end
|
||||
builds_ordered_for_shared_runners(shared_builds)
|
||||
end
|
||||
|
||||
def builds_matching_tag_ids(relation, ids)
|
||||
|
@ -52,8 +36,40 @@ module Ci
|
|||
relation.pluck(:build_id)
|
||||
end
|
||||
|
||||
def use_denormalized_shared_runners_data?
|
||||
::Feature.enabled?(:ci_queueing_denormalize_shared_runners_information, runner, type: :development, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def builds_available_for_shared_runners
|
||||
if use_denormalized_shared_runners_data?
|
||||
new_builds.with_instance_runners
|
||||
else
|
||||
new_builds
|
||||
# don't run projects which have not enabled shared runners and builds
|
||||
.joins('INNER JOIN projects ON ci_pending_builds.project_id = projects.id')
|
||||
.where(projects: { shared_runners_enabled: true, pending_delete: false })
|
||||
.joins('LEFT JOIN project_features ON ci_pending_builds.project_id = project_features.project_id')
|
||||
.where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0')
|
||||
end
|
||||
end
|
||||
|
||||
def builds_ordered_for_shared_runners(relation)
|
||||
if Feature.enabled?(:ci_queueing_disaster_recovery_disable_fair_scheduling, runner, type: :ops, default_enabled: :yaml)
|
||||
# if disaster recovery is enabled, we fallback to FIFO scheduling
|
||||
relation.order('ci_pending_builds.build_id ASC')
|
||||
else
|
||||
# Implement fair scheduling
|
||||
# this returns builds that are ordered by number of running builds
|
||||
# we prefer projects that don't use shared runners at all
|
||||
relation
|
||||
.with(running_builds_for_shared_runners_cte.to_arel)
|
||||
.joins("LEFT JOIN project_builds ON ci_pending_builds.project_id = project_builds.project_id")
|
||||
.order(Arel.sql('COALESCE(project_builds.running_builds, 0) ASC'), 'ci_pending_builds.build_id ASC')
|
||||
end
|
||||
end
|
||||
|
||||
def running_builds_for_shared_runners_cte
|
||||
running_builds = ::Ci::RunningBuild
|
||||
.instance_type
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
- if project.has_extra_topics?
|
||||
- title = _('More topics')
|
||||
- content = capture do
|
||||
%span.gl-display-inline-flex
|
||||
%span.gl-display-inline-flex.gl-flex-wrap
|
||||
- project.topics_not_shown.each do |topic|
|
||||
- explore_project_topic_path = explore_projects_path(topic: topic)
|
||||
- if topic.length > max_project_topic_length
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: ci_queueing_denormalize_shared_runners_information
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
milestone: '14.2'
|
||||
type: development
|
||||
group: group::pipeline execution
|
||||
default_enabled: false
|
|
@ -380,6 +380,28 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="querysnippetstype"></a>`type` | [`TypeEnum`](#typeenum) | The type of snippet. |
|
||||
| <a id="querysnippetsvisibility"></a>`visibility` | [`VisibilityScopesEnum`](#visibilityscopesenum) | The visibility of the snippet. |
|
||||
|
||||
### `Query.timelogs`
|
||||
|
||||
Find timelogs visible to the current user.
|
||||
|
||||
Returns [`TimelogConnection`](#timelogconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="querytimelogsenddate"></a>`endDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or before endDate. |
|
||||
| <a id="querytimelogsendtime"></a>`endTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or before endTime. |
|
||||
| <a id="querytimelogsgroupid"></a>`groupId` | [`GroupID`](#groupid) | List timelogs for a group. |
|
||||
| <a id="querytimelogsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | List timelogs for a project. |
|
||||
| <a id="querytimelogsstartdate"></a>`startDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or after startDate. |
|
||||
| <a id="querytimelogsstarttime"></a>`startTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or after startTime. |
|
||||
| <a id="querytimelogsusername"></a>`username` | [`String`](#string) | List timelogs for a user. |
|
||||
|
||||
### `Query.usageTrendsMeasurements`
|
||||
|
||||
Get statistics on the instance.
|
||||
|
@ -535,7 +557,7 @@ Input type: `AdminSidekiqQueuesDeleteJobsInput`
|
|||
| <a id="mutationadminsidekiqqueuesdeletejobsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationadminsidekiqqueuesdeletejobsfeaturecategory"></a>`featureCategory` | [`String`](#string) | Delete jobs matching feature_category in the context metadata. |
|
||||
| <a id="mutationadminsidekiqqueuesdeletejobsproject"></a>`project` | [`String`](#string) | Delete jobs matching project in the context metadata. |
|
||||
| <a id="mutationadminsidekiqqueuesdeletejobsqueuename"></a>`queueName` | [`String!`](#string) | The name of the queue to delete jobs from. |
|
||||
| <a id="mutationadminsidekiqqueuesdeletejobsqueuename"></a>`queueName` | [`String!`](#string) | Name of the queue to delete jobs from. |
|
||||
| <a id="mutationadminsidekiqqueuesdeletejobsrelatedclass"></a>`relatedClass` | [`String`](#string) | Delete jobs matching related_class in the context metadata. |
|
||||
| <a id="mutationadminsidekiqqueuesdeletejobsremoteip"></a>`remoteIp` | [`String`](#string) | Delete jobs matching remote_ip in the context metadata. |
|
||||
| <a id="mutationadminsidekiqqueuesdeletejobsrootnamespace"></a>`rootNamespace` | [`String`](#string) | Delete jobs matching root_namespace in the context metadata. |
|
||||
|
@ -558,21 +580,21 @@ Input type: `AlertSetAssigneesInput`
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationalertsetassigneesassigneeusernames"></a>`assigneeUsernames` | [`[String!]!`](#string) | The usernames to assign to the alert. Replaces existing assignees by default. |
|
||||
| <a id="mutationalertsetassigneesassigneeusernames"></a>`assigneeUsernames` | [`[String!]!`](#string) | Usernames to assign to the alert. Replaces existing assignees by default. |
|
||||
| <a id="mutationalertsetassigneesclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationalertsetassigneesiid"></a>`iid` | [`String!`](#string) | The IID of the alert to mutate. |
|
||||
| <a id="mutationalertsetassigneesoperationmode"></a>`operationMode` | [`MutationOperationMode`](#mutationoperationmode) | The operation to perform. Defaults to REPLACE. |
|
||||
| <a id="mutationalertsetassigneesprojectpath"></a>`projectPath` | [`ID!`](#id) | The project the alert to mutate is in. |
|
||||
| <a id="mutationalertsetassigneesiid"></a>`iid` | [`String!`](#string) | IID of the alert to mutate. |
|
||||
| <a id="mutationalertsetassigneesoperationmode"></a>`operationMode` | [`MutationOperationMode`](#mutationoperationmode) | Operation to perform. Defaults to REPLACE. |
|
||||
| <a id="mutationalertsetassigneesprojectpath"></a>`projectPath` | [`ID!`](#id) | Project the alert to mutate is in. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationalertsetassigneesalert"></a>`alert` | [`AlertManagementAlert`](#alertmanagementalert) | The alert after mutation. |
|
||||
| <a id="mutationalertsetassigneesalert"></a>`alert` | [`AlertManagementAlert`](#alertmanagementalert) | Alert after mutation. |
|
||||
| <a id="mutationalertsetassigneesclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationalertsetassigneeserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationalertsetassigneesissue"></a>`issue` | [`Issue`](#issue) | The issue created after mutation. |
|
||||
| <a id="mutationalertsetassigneestodo"></a>`todo` | [`Todo`](#todo) | The to-do item after mutation. |
|
||||
| <a id="mutationalertsetassigneesissue"></a>`issue` | [`Issue`](#issue) | Issue created after mutation. |
|
||||
| <a id="mutationalertsetassigneestodo"></a>`todo` | [`Todo`](#todo) | To-do item after mutation. |
|
||||
|
||||
### `Mutation.alertTodoCreate`
|
||||
|
||||
|
@ -583,18 +605,18 @@ Input type: `AlertTodoCreateInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationalerttodocreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationalerttodocreateiid"></a>`iid` | [`String!`](#string) | The IID of the alert to mutate. |
|
||||
| <a id="mutationalerttodocreateprojectpath"></a>`projectPath` | [`ID!`](#id) | The project the alert to mutate is in. |
|
||||
| <a id="mutationalerttodocreateiid"></a>`iid` | [`String!`](#string) | IID of the alert to mutate. |
|
||||
| <a id="mutationalerttodocreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Project the alert to mutate is in. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationalerttodocreatealert"></a>`alert` | [`AlertManagementAlert`](#alertmanagementalert) | The alert after mutation. |
|
||||
| <a id="mutationalerttodocreatealert"></a>`alert` | [`AlertManagementAlert`](#alertmanagementalert) | Alert after mutation. |
|
||||
| <a id="mutationalerttodocreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationalerttodocreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationalerttodocreateissue"></a>`issue` | [`Issue`](#issue) | The issue created after mutation. |
|
||||
| <a id="mutationalerttodocreatetodo"></a>`todo` | [`Todo`](#todo) | The to-do item after mutation. |
|
||||
| <a id="mutationalerttodocreateissue"></a>`issue` | [`Issue`](#issue) | Issue created after mutation. |
|
||||
| <a id="mutationalerttodocreatetodo"></a>`todo` | [`Todo`](#todo) | To-do item after mutation. |
|
||||
|
||||
### `Mutation.apiFuzzingCiConfigurationCreate`
|
||||
|
||||
|
@ -630,7 +652,7 @@ Input type: `AwardEmojiAddInput`
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationawardemojiaddawardableid"></a>`awardableId` | [`AwardableID!`](#awardableid) | The global ID of the awardable resource. |
|
||||
| <a id="mutationawardemojiaddawardableid"></a>`awardableId` | [`AwardableID!`](#awardableid) | Global ID of the awardable resource. |
|
||||
| <a id="mutationawardemojiaddclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationawardemojiaddname"></a>`name` | [`String!`](#string) | The emoji name. |
|
||||
|
||||
|
@ -638,7 +660,7 @@ Input type: `AwardEmojiAddInput`
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationawardemojiaddawardemoji"></a>`awardEmoji` | [`AwardEmoji`](#awardemoji) | The award emoji after mutation. |
|
||||
| <a id="mutationawardemojiaddawardemoji"></a>`awardEmoji` | [`AwardEmoji`](#awardemoji) | Award emoji after mutation. |
|
||||
| <a id="mutationawardemojiaddclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationawardemojiadderrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
|
@ -650,7 +672,7 @@ Input type: `AwardEmojiRemoveInput`
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationawardemojiremoveawardableid"></a>`awardableId` | [`AwardableID!`](#awardableid) | The global ID of the awardable resource. |
|
||||
| <a id="mutationawardemojiremoveawardableid"></a>`awardableId` | [`AwardableID!`](#awardableid) | Global ID of the awardable resource. |
|
||||
| <a id="mutationawardemojiremoveclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationawardemojiremovename"></a>`name` | [`String!`](#string) | The emoji name. |
|
||||
|
||||
|
@ -658,7 +680,7 @@ Input type: `AwardEmojiRemoveInput`
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationawardemojiremoveawardemoji"></a>`awardEmoji` | [`AwardEmoji`](#awardemoji) | The award emoji after mutation. |
|
||||
| <a id="mutationawardemojiremoveawardemoji"></a>`awardEmoji` | [`AwardEmoji`](#awardemoji) | Award emoji after mutation. |
|
||||
| <a id="mutationawardemojiremoveclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationawardemojiremoveerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
|
@ -670,7 +692,7 @@ Input type: `AwardEmojiToggleInput`
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationawardemojitoggleawardableid"></a>`awardableId` | [`AwardableID!`](#awardableid) | The global ID of the awardable resource. |
|
||||
| <a id="mutationawardemojitoggleawardableid"></a>`awardableId` | [`AwardableID!`](#awardableid) | Global ID of the awardable resource. |
|
||||
| <a id="mutationawardemojitoggleclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationawardemojitogglename"></a>`name` | [`String!`](#string) | The emoji name. |
|
||||
|
||||
|
@ -678,7 +700,7 @@ Input type: `AwardEmojiToggleInput`
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationawardemojitoggleawardemoji"></a>`awardEmoji` | [`AwardEmoji`](#awardemoji) | The award emoji after mutation. |
|
||||
| <a id="mutationawardemojitoggleawardemoji"></a>`awardEmoji` | [`AwardEmoji`](#awardemoji) | Award emoji after mutation. |
|
||||
| <a id="mutationawardemojitoggleclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationawardemojitoggleerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationawardemojitoggletoggledon"></a>`toggledOn` | [`Boolean!`](#boolean) | Indicates the status of the emoji. True if the toggle awarded the emoji, and false if the toggle removed the emoji. |
|
||||
|
@ -792,7 +814,7 @@ Input type: `CiCdSettingsUpdateInput`
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcicdsettingsupdatecicdsettings"></a>`ciCdSettings` | [`ProjectCiCdSetting!`](#projectcicdsetting) | The CI/CD settings after mutation. |
|
||||
| <a id="mutationcicdsettingsupdatecicdsettings"></a>`ciCdSettings` | [`ProjectCiCdSetting!`](#projectcicdsetting) | CI/CD settings after mutation. |
|
||||
| <a id="mutationcicdsettingsupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcicdsettingsupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
|
@ -805,14 +827,14 @@ Input type: `CiJobTokenScopeAddProjectInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcijobtokenscopeaddprojectclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcijobtokenscopeaddprojectprojectpath"></a>`projectPath` | [`ID!`](#id) | The project that the CI job token scope belongs to. |
|
||||
| <a id="mutationcijobtokenscopeaddprojecttargetprojectpath"></a>`targetProjectPath` | [`ID!`](#id) | The project to be added to the CI job token scope. |
|
||||
| <a id="mutationcijobtokenscopeaddprojectprojectpath"></a>`projectPath` | [`ID!`](#id) | Project that the CI job token scope belongs to. |
|
||||
| <a id="mutationcijobtokenscopeaddprojecttargetprojectpath"></a>`targetProjectPath` | [`ID!`](#id) | Project to be added to the CI job token scope. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcijobtokenscopeaddprojectcijobtokenscope"></a>`ciJobTokenScope` | [`CiJobTokenScopeType`](#cijobtokenscopetype) | The CI job token's scope of access. |
|
||||
| <a id="mutationcijobtokenscopeaddprojectcijobtokenscope"></a>`ciJobTokenScope` | [`CiJobTokenScopeType`](#cijobtokenscopetype) | CI job token's scope of access. |
|
||||
| <a id="mutationcijobtokenscopeaddprojectclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcijobtokenscopeaddprojecterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
|
@ -825,14 +847,14 @@ Input type: `CiJobTokenScopeRemoveProjectInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcijobtokenscoperemoveprojectclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcijobtokenscoperemoveprojectprojectpath"></a>`projectPath` | [`ID!`](#id) | The project that the CI job token scope belongs to. |
|
||||
| <a id="mutationcijobtokenscoperemoveprojecttargetprojectpath"></a>`targetProjectPath` | [`ID!`](#id) | The project to be removed from the CI job token scope. |
|
||||
| <a id="mutationcijobtokenscoperemoveprojectprojectpath"></a>`projectPath` | [`ID!`](#id) | Project that the CI job token scope belongs to. |
|
||||
| <a id="mutationcijobtokenscoperemoveprojecttargetprojectpath"></a>`targetProjectPath` | [`ID!`](#id) | Project to be removed from the CI job token scope. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcijobtokenscoperemoveprojectcijobtokenscope"></a>`ciJobTokenScope` | [`CiJobTokenScopeType`](#cijobtokenscopetype) | The CI job token's scope of access. |
|
||||
| <a id="mutationcijobtokenscoperemoveprojectcijobtokenscope"></a>`ciJobTokenScope` | [`CiJobTokenScopeType`](#cijobtokenscopetype) | CI job token's scope of access. |
|
||||
| <a id="mutationcijobtokenscoperemoveprojectclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcijobtokenscoperemoveprojecterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
|
@ -1002,18 +1024,18 @@ Input type: `CreateAlertIssueInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcreatealertissueclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcreatealertissueiid"></a>`iid` | [`String!`](#string) | The IID of the alert to mutate. |
|
||||
| <a id="mutationcreatealertissueprojectpath"></a>`projectPath` | [`ID!`](#id) | The project the alert to mutate is in. |
|
||||
| <a id="mutationcreatealertissueiid"></a>`iid` | [`String!`](#string) | IID of the alert to mutate. |
|
||||
| <a id="mutationcreatealertissueprojectpath"></a>`projectPath` | [`ID!`](#id) | Project the alert to mutate is in. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcreatealertissuealert"></a>`alert` | [`AlertManagementAlert`](#alertmanagementalert) | The alert after mutation. |
|
||||
| <a id="mutationcreatealertissuealert"></a>`alert` | [`AlertManagementAlert`](#alertmanagementalert) | Alert after mutation. |
|
||||
| <a id="mutationcreatealertissueclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcreatealertissueerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationcreatealertissueissue"></a>`issue` | [`Issue`](#issue) | The issue created after mutation. |
|
||||
| <a id="mutationcreatealertissuetodo"></a>`todo` | [`Todo`](#todo) | The to-do item after mutation. |
|
||||
| <a id="mutationcreatealertissueissue"></a>`issue` | [`Issue`](#issue) | Issue created after mutation. |
|
||||
| <a id="mutationcreatealertissuetodo"></a>`todo` | [`Todo`](#todo) | To-do item after mutation. |
|
||||
|
||||
### `Mutation.createAnnotation`
|
||||
|
||||
|
@ -1056,7 +1078,7 @@ Input type: `CreateBoardInput`
|
|||
| <a id="mutationcreateboardlabelids"></a>`labelIds` | [`[LabelID!]`](#labelid) | IDs of labels to be added to the board. |
|
||||
| <a id="mutationcreateboardlabels"></a>`labels` | [`[String!]`](#string) | Labels of the issue. |
|
||||
| <a id="mutationcreateboardmilestoneid"></a>`milestoneId` | [`MilestoneID`](#milestoneid) | ID of milestone to be assigned to the board. |
|
||||
| <a id="mutationcreateboardname"></a>`name` | [`String`](#string) | The board name. |
|
||||
| <a id="mutationcreateboardname"></a>`name` | [`String`](#string) | Board name. |
|
||||
| <a id="mutationcreateboardprojectpath"></a>`projectPath` | [`ID`](#id) | Full path of the project with which the resource is associated. |
|
||||
| <a id="mutationcreateboardweight"></a>`weight` | [`Int`](#int) | Weight value to be assigned to the board. |
|
||||
|
||||
|
@ -1064,7 +1086,7 @@ Input type: `CreateBoardInput`
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcreateboardboard"></a>`board` | [`Board`](#board) | The board after mutation. |
|
||||
| <a id="mutationcreateboardboard"></a>`board` | [`Board`](#board) | Board after mutation. |
|
||||
| <a id="mutationcreateboardclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcreateboarderrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
|
@ -1791,13 +1813,13 @@ Input type: `DestroyBoardInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationdestroyboardclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationdestroyboardid"></a>`id` | [`BoardID!`](#boardid) | The global ID of the board to destroy. |
|
||||
| <a id="mutationdestroyboardid"></a>`id` | [`BoardID!`](#boardid) | Global ID of the board to destroy. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationdestroyboardboard"></a>`board` | [`Board`](#board) | The board after mutation. |
|
||||
| <a id="mutationdestroyboardboard"></a>`board` | [`Board`](#board) | Board after mutation. |
|
||||
| <a id="mutationdestroyboardclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationdestroyboarderrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
|
@ -1818,7 +1840,7 @@ Input type: `DestroyBoardListInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationdestroyboardlistclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationdestroyboardlisterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationdestroyboardlistlist"></a>`list` | [`BoardList`](#boardlist) | The list after mutation. |
|
||||
| <a id="mutationdestroyboardlistlist"></a>`list` | [`BoardList`](#boardlist) | List after mutation. |
|
||||
|
||||
### `Mutation.destroyComplianceFramework`
|
||||
|
||||
|
@ -2115,7 +2137,7 @@ Input type: `EpicBoardCreateInput`
|
|||
| <a id="mutationepicboardcreatehideclosedlist"></a>`hideClosedList` | [`Boolean`](#boolean) | Whether or not closed list is hidden. |
|
||||
| <a id="mutationepicboardcreatelabelids"></a>`labelIds` | [`[LabelID!]`](#labelid) | IDs of labels to be added to the board. |
|
||||
| <a id="mutationepicboardcreatelabels"></a>`labels` | [`[String!]`](#string) | Labels of the issue. |
|
||||
| <a id="mutationepicboardcreatename"></a>`name` | [`String`](#string) | The board name. |
|
||||
| <a id="mutationepicboardcreatename"></a>`name` | [`String`](#string) | Board name. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -2181,7 +2203,7 @@ Input type: `EpicBoardUpdateInput`
|
|||
| <a id="mutationepicboardupdateid"></a>`id` | [`BoardsEpicBoardID!`](#boardsepicboardid) | The epic board global ID. |
|
||||
| <a id="mutationepicboardupdatelabelids"></a>`labelIds` | [`[LabelID!]`](#labelid) | IDs of labels to be added to the board. |
|
||||
| <a id="mutationepicboardupdatelabels"></a>`labels` | [`[String!]`](#string) | Labels of the issue. |
|
||||
| <a id="mutationepicboardupdatename"></a>`name` | [`String`](#string) | The board name. |
|
||||
| <a id="mutationepicboardupdatename"></a>`name` | [`String`](#string) | Board name. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -2390,10 +2412,10 @@ Input type: `HttpIntegrationCreateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationhttpintegrationcreateactive"></a>`active` | [`Boolean!`](#boolean) | Whether the integration is receiving alerts. |
|
||||
| <a id="mutationhttpintegrationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationhttpintegrationcreatename"></a>`name` | [`String!`](#string) | The name of the integration. |
|
||||
| <a id="mutationhttpintegrationcreatename"></a>`name` | [`String!`](#string) | Name of the integration. |
|
||||
| <a id="mutationhttpintegrationcreatepayloadattributemappings"></a>`payloadAttributeMappings` | [`[AlertManagementPayloadAlertFieldInput!]`](#alertmanagementpayloadalertfieldinput) | The custom mapping of GitLab alert attributes to fields from the payload_example. |
|
||||
| <a id="mutationhttpintegrationcreatepayloadexample"></a>`payloadExample` | [`JsonString`](#jsonstring) | The example of an alert payload. |
|
||||
| <a id="mutationhttpintegrationcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | The project to create the integration in. |
|
||||
| <a id="mutationhttpintegrationcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Project to create the integration in. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -2401,7 +2423,7 @@ Input type: `HttpIntegrationCreateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationhttpintegrationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationhttpintegrationcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationhttpintegrationcreateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | The HTTP integration. |
|
||||
| <a id="mutationhttpintegrationcreateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
|
||||
|
||||
### `Mutation.httpIntegrationDestroy`
|
||||
|
||||
|
@ -2412,7 +2434,7 @@ Input type: `HttpIntegrationDestroyInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationhttpintegrationdestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationhttpintegrationdestroyid"></a>`id` | [`AlertManagementHttpIntegrationID!`](#alertmanagementhttpintegrationid) | The ID of the integration to remove. |
|
||||
| <a id="mutationhttpintegrationdestroyid"></a>`id` | [`AlertManagementHttpIntegrationID!`](#alertmanagementhttpintegrationid) | ID of the integration to remove. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -2420,7 +2442,7 @@ Input type: `HttpIntegrationDestroyInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationhttpintegrationdestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationhttpintegrationdestroyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationhttpintegrationdestroyintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | The HTTP integration. |
|
||||
| <a id="mutationhttpintegrationdestroyintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
|
||||
|
||||
### `Mutation.httpIntegrationResetToken`
|
||||
|
||||
|
@ -2431,7 +2453,7 @@ Input type: `HttpIntegrationResetTokenInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationhttpintegrationresettokenclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationhttpintegrationresettokenid"></a>`id` | [`AlertManagementHttpIntegrationID!`](#alertmanagementhttpintegrationid) | The ID of the integration to mutate. |
|
||||
| <a id="mutationhttpintegrationresettokenid"></a>`id` | [`AlertManagementHttpIntegrationID!`](#alertmanagementhttpintegrationid) | ID of the integration to mutate. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -2439,7 +2461,7 @@ Input type: `HttpIntegrationResetTokenInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationhttpintegrationresettokenclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationhttpintegrationresettokenerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationhttpintegrationresettokenintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | The HTTP integration. |
|
||||
| <a id="mutationhttpintegrationresettokenintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
|
||||
|
||||
### `Mutation.httpIntegrationUpdate`
|
||||
|
||||
|
@ -2451,8 +2473,8 @@ Input type: `HttpIntegrationUpdateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationhttpintegrationupdateactive"></a>`active` | [`Boolean`](#boolean) | Whether the integration is receiving alerts. |
|
||||
| <a id="mutationhttpintegrationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationhttpintegrationupdateid"></a>`id` | [`AlertManagementHttpIntegrationID!`](#alertmanagementhttpintegrationid) | The ID of the integration to mutate. |
|
||||
| <a id="mutationhttpintegrationupdatename"></a>`name` | [`String`](#string) | The name of the integration. |
|
||||
| <a id="mutationhttpintegrationupdateid"></a>`id` | [`AlertManagementHttpIntegrationID!`](#alertmanagementhttpintegrationid) | ID of the integration to mutate. |
|
||||
| <a id="mutationhttpintegrationupdatename"></a>`name` | [`String`](#string) | Name of the integration. |
|
||||
| <a id="mutationhttpintegrationupdatepayloadattributemappings"></a>`payloadAttributeMappings` | [`[AlertManagementPayloadAlertFieldInput!]`](#alertmanagementpayloadalertfieldinput) | The custom mapping of GitLab alert attributes to fields from the payload_example. |
|
||||
| <a id="mutationhttpintegrationupdatepayloadexample"></a>`payloadExample` | [`JsonString`](#jsonstring) | The example of an alert payload. |
|
||||
|
||||
|
@ -2462,7 +2484,7 @@ Input type: `HttpIntegrationUpdateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationhttpintegrationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationhttpintegrationupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationhttpintegrationupdateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | The HTTP integration. |
|
||||
| <a id="mutationhttpintegrationupdateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
|
||||
|
||||
### `Mutation.issueMove`
|
||||
|
||||
|
@ -2869,7 +2891,7 @@ Input type: `JobPlayInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationjobplayclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationjobplayid"></a>`id` | [`CiBuildID!`](#cibuildid) | The ID of the job to mutate. |
|
||||
| <a id="mutationjobplayid"></a>`id` | [`CiBuildID!`](#cibuildid) | ID of the job to mutate. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -2877,7 +2899,7 @@ Input type: `JobPlayInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationjobplayclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationjobplayerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationjobplayjob"></a>`job` | [`CiJob`](#cijob) | The job after the mutation. |
|
||||
| <a id="mutationjobplayjob"></a>`job` | [`CiJob`](#cijob) | Job after the mutation. |
|
||||
|
||||
### `Mutation.jobRetry`
|
||||
|
||||
|
@ -2888,7 +2910,7 @@ Input type: `JobRetryInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationjobretryclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationjobretryid"></a>`id` | [`CiBuildID!`](#cibuildid) | The ID of the job to mutate. |
|
||||
| <a id="mutationjobretryid"></a>`id` | [`CiBuildID!`](#cibuildid) | ID of the job to mutate. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -2896,7 +2918,7 @@ Input type: `JobRetryInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationjobretryclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationjobretryerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationjobretryjob"></a>`job` | [`CiJob`](#cijob) | The job after the mutation. |
|
||||
| <a id="mutationjobretryjob"></a>`job` | [`CiJob`](#cijob) | Job after the mutation. |
|
||||
|
||||
### `Mutation.labelCreate`
|
||||
|
||||
|
@ -3359,7 +3381,7 @@ Input type: `PipelineCancelInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationpipelinecancelclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationpipelinecancelid"></a>`id` | [`CiPipelineID!`](#cipipelineid) | The ID of the pipeline to mutate. |
|
||||
| <a id="mutationpipelinecancelid"></a>`id` | [`CiPipelineID!`](#cipipelineid) | ID of the pipeline to mutate. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -3377,7 +3399,7 @@ Input type: `PipelineDestroyInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationpipelinedestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationpipelinedestroyid"></a>`id` | [`CiPipelineID!`](#cipipelineid) | The ID of the pipeline to mutate. |
|
||||
| <a id="mutationpipelinedestroyid"></a>`id` | [`CiPipelineID!`](#cipipelineid) | ID of the pipeline to mutate. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -3395,7 +3417,7 @@ Input type: `PipelineRetryInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationpipelineretryclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationpipelineretryid"></a>`id` | [`CiPipelineID!`](#cipipelineid) | The ID of the pipeline to mutate. |
|
||||
| <a id="mutationpipelineretryid"></a>`id` | [`CiPipelineID!`](#cipipelineid) | ID of the pipeline to mutate. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -3403,7 +3425,7 @@ Input type: `PipelineRetryInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationpipelineretryclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationpipelineretryerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationpipelineretrypipeline"></a>`pipeline` | [`Pipeline`](#pipeline) | The pipeline after mutation. |
|
||||
| <a id="mutationpipelineretrypipeline"></a>`pipeline` | [`Pipeline`](#pipeline) | Pipeline after mutation. |
|
||||
|
||||
### `Mutation.projectSetComplianceFramework`
|
||||
|
||||
|
@ -3459,7 +3481,7 @@ Input type: `PrometheusIntegrationCreateInput`
|
|||
| <a id="mutationprometheusintegrationcreateactive"></a>`active` | [`Boolean!`](#boolean) | Whether the integration is receiving alerts. |
|
||||
| <a id="mutationprometheusintegrationcreateapiurl"></a>`apiUrl` | [`String!`](#string) | Endpoint at which Prometheus can be queried. |
|
||||
| <a id="mutationprometheusintegrationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationprometheusintegrationcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | The project to create the integration in. |
|
||||
| <a id="mutationprometheusintegrationcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Project to create the integration in. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -3467,7 +3489,7 @@ Input type: `PrometheusIntegrationCreateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationprometheusintegrationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationprometheusintegrationcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationprometheusintegrationcreateintegration"></a>`integration` | [`AlertManagementPrometheusIntegration`](#alertmanagementprometheusintegration) | The newly created integration. |
|
||||
| <a id="mutationprometheusintegrationcreateintegration"></a>`integration` | [`AlertManagementPrometheusIntegration`](#alertmanagementprometheusintegration) | Newly created integration. |
|
||||
|
||||
### `Mutation.prometheusIntegrationResetToken`
|
||||
|
||||
|
@ -3478,7 +3500,7 @@ Input type: `PrometheusIntegrationResetTokenInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationprometheusintegrationresettokenclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationprometheusintegrationresettokenid"></a>`id` | [`IntegrationsPrometheusID!`](#integrationsprometheusid) | The ID of the integration to mutate. |
|
||||
| <a id="mutationprometheusintegrationresettokenid"></a>`id` | [`IntegrationsPrometheusID!`](#integrationsprometheusid) | ID of the integration to mutate. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -3486,7 +3508,7 @@ Input type: `PrometheusIntegrationResetTokenInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationprometheusintegrationresettokenclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationprometheusintegrationresettokenerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationprometheusintegrationresettokenintegration"></a>`integration` | [`AlertManagementPrometheusIntegration`](#alertmanagementprometheusintegration) | The newly created integration. |
|
||||
| <a id="mutationprometheusintegrationresettokenintegration"></a>`integration` | [`AlertManagementPrometheusIntegration`](#alertmanagementprometheusintegration) | Newly created integration. |
|
||||
|
||||
### `Mutation.prometheusIntegrationUpdate`
|
||||
|
||||
|
@ -3499,7 +3521,7 @@ Input type: `PrometheusIntegrationUpdateInput`
|
|||
| <a id="mutationprometheusintegrationupdateactive"></a>`active` | [`Boolean`](#boolean) | Whether the integration is receiving alerts. |
|
||||
| <a id="mutationprometheusintegrationupdateapiurl"></a>`apiUrl` | [`String`](#string) | Endpoint at which Prometheus can be queried. |
|
||||
| <a id="mutationprometheusintegrationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationprometheusintegrationupdateid"></a>`id` | [`IntegrationsPrometheusID!`](#integrationsprometheusid) | The ID of the integration to mutate. |
|
||||
| <a id="mutationprometheusintegrationupdateid"></a>`id` | [`IntegrationsPrometheusID!`](#integrationsprometheusid) | ID of the integration to mutate. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -3507,7 +3529,7 @@ Input type: `PrometheusIntegrationUpdateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationprometheusintegrationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationprometheusintegrationupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationprometheusintegrationupdateintegration"></a>`integration` | [`AlertManagementPrometheusIntegration`](#alertmanagementprometheusintegration) | The newly created integration. |
|
||||
| <a id="mutationprometheusintegrationupdateintegration"></a>`integration` | [`AlertManagementPrometheusIntegration`](#alertmanagementprometheusintegration) | Newly created integration. |
|
||||
|
||||
### `Mutation.promoteToEpic`
|
||||
|
||||
|
@ -3755,7 +3777,7 @@ Input type: `RunnerUpdateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationrunnerupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationrunnerupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationrunnerupdaterunner"></a>`runner` | [`CiRunner`](#cirunner) | The runner after mutation. |
|
||||
| <a id="mutationrunnerupdaterunner"></a>`runner` | [`CiRunner`](#cirunner) | Runner after mutation. |
|
||||
|
||||
### `Mutation.runnersRegistrationTokenReset`
|
||||
|
||||
|
@ -3777,7 +3799,7 @@ Input type: `RunnersRegistrationTokenResetInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationrunnersregistrationtokenresetclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationrunnersregistrationtokenreseterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationrunnersregistrationtokenresettoken"></a>`token` | [`String`](#string) | The runner token after mutation. |
|
||||
| <a id="mutationrunnersregistrationtokenresettoken"></a>`token` | [`String`](#string) | Runner token after mutation. |
|
||||
|
||||
### `Mutation.scanExecutionPolicyCommit`
|
||||
|
||||
|
@ -3995,19 +4017,19 @@ Input type: `UpdateAlertStatusInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationupdatealertstatusclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationupdatealertstatusiid"></a>`iid` | [`String!`](#string) | The IID of the alert to mutate. |
|
||||
| <a id="mutationupdatealertstatusprojectpath"></a>`projectPath` | [`ID!`](#id) | The project the alert to mutate is in. |
|
||||
| <a id="mutationupdatealertstatusstatus"></a>`status` | [`AlertManagementStatus!`](#alertmanagementstatus) | The status to set the alert. |
|
||||
| <a id="mutationupdatealertstatusiid"></a>`iid` | [`String!`](#string) | IID of the alert to mutate. |
|
||||
| <a id="mutationupdatealertstatusprojectpath"></a>`projectPath` | [`ID!`](#id) | Project the alert to mutate is in. |
|
||||
| <a id="mutationupdatealertstatusstatus"></a>`status` | [`AlertManagementStatus!`](#alertmanagementstatus) | Status to set the alert. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationupdatealertstatusalert"></a>`alert` | [`AlertManagementAlert`](#alertmanagementalert) | The alert after mutation. |
|
||||
| <a id="mutationupdatealertstatusalert"></a>`alert` | [`AlertManagementAlert`](#alertmanagementalert) | Alert after mutation. |
|
||||
| <a id="mutationupdatealertstatusclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationupdatealertstatuserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationupdatealertstatusissue"></a>`issue` | [`Issue`](#issue) | The issue created after mutation. |
|
||||
| <a id="mutationupdatealertstatustodo"></a>`todo` | [`Todo`](#todo) | The to-do item after mutation. |
|
||||
| <a id="mutationupdatealertstatusissue"></a>`issue` | [`Issue`](#issue) | Issue created after mutation. |
|
||||
| <a id="mutationupdatealertstatustodo"></a>`todo` | [`Todo`](#todo) | To-do item after mutation. |
|
||||
|
||||
### `Mutation.updateBoard`
|
||||
|
||||
|
@ -4021,19 +4043,19 @@ Input type: `UpdateBoardInput`
|
|||
| <a id="mutationupdateboardclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationupdateboardhidebackloglist"></a>`hideBacklogList` | [`Boolean`](#boolean) | Whether or not backlog list is hidden. |
|
||||
| <a id="mutationupdateboardhideclosedlist"></a>`hideClosedList` | [`Boolean`](#boolean) | Whether or not closed list is hidden. |
|
||||
| <a id="mutationupdateboardid"></a>`id` | [`BoardID!`](#boardid) | The board global ID. |
|
||||
| <a id="mutationupdateboardid"></a>`id` | [`BoardID!`](#boardid) | Board global ID. |
|
||||
| <a id="mutationupdateboarditerationid"></a>`iterationId` | [`IterationID`](#iterationid) | ID of iteration to be assigned to the board. |
|
||||
| <a id="mutationupdateboardlabelids"></a>`labelIds` | [`[LabelID!]`](#labelid) | IDs of labels to be added to the board. |
|
||||
| <a id="mutationupdateboardlabels"></a>`labels` | [`[String!]`](#string) | Labels of the issue. |
|
||||
| <a id="mutationupdateboardmilestoneid"></a>`milestoneId` | [`MilestoneID`](#milestoneid) | ID of milestone to be assigned to the board. |
|
||||
| <a id="mutationupdateboardname"></a>`name` | [`String`](#string) | The board name. |
|
||||
| <a id="mutationupdateboardname"></a>`name` | [`String`](#string) | Board name. |
|
||||
| <a id="mutationupdateboardweight"></a>`weight` | [`Int`](#int) | Weight value to be assigned to the board. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationupdateboardboard"></a>`board` | [`Board`](#board) | The board after mutation. |
|
||||
| <a id="mutationupdateboardboard"></a>`board` | [`Board`](#board) | Board after mutation. |
|
||||
| <a id="mutationupdateboardclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationupdateboarderrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
|
@ -9925,10 +9947,13 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="grouptimelogsenddate"></a>`endDate` | [`Time`](#time) | List time logs within a date range where the logged date is equal to or before endDate. |
|
||||
| <a id="grouptimelogsendtime"></a>`endTime` | [`Time`](#time) | List time-logs within a time range where the logged time is equal to or before endTime. |
|
||||
| <a id="grouptimelogsstartdate"></a>`startDate` | [`Time`](#time) | List time logs within a date range where the logged date is equal to or after startDate. |
|
||||
| <a id="grouptimelogsstarttime"></a>`startTime` | [`Time`](#time) | List time-logs within a time range where the logged time is equal to or after startTime. |
|
||||
| <a id="grouptimelogsenddate"></a>`endDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or before endDate. |
|
||||
| <a id="grouptimelogsendtime"></a>`endTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or before endTime. |
|
||||
| <a id="grouptimelogsgroupid"></a>`groupId` | [`GroupID`](#groupid) | List timelogs for a group. |
|
||||
| <a id="grouptimelogsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | List timelogs for a project. |
|
||||
| <a id="grouptimelogsstartdate"></a>`startDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or after startDate. |
|
||||
| <a id="grouptimelogsstarttime"></a>`startTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or after startTime. |
|
||||
| <a id="grouptimelogsusername"></a>`username` | [`String`](#string) | List timelogs for a user. |
|
||||
|
||||
##### `Group.vulnerabilities`
|
||||
|
||||
|
@ -10810,6 +10835,28 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestassigneestarredprojectssearch"></a>`search` | [`String`](#string) | Search query. |
|
||||
|
||||
##### `MergeRequestAssignee.timelogs`
|
||||
|
||||
Time logged by the user.
|
||||
|
||||
Returns [`TimelogConnection`](#timelogconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestassigneetimelogsenddate"></a>`endDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or before endDate. |
|
||||
| <a id="mergerequestassigneetimelogsendtime"></a>`endTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or before endTime. |
|
||||
| <a id="mergerequestassigneetimelogsgroupid"></a>`groupId` | [`GroupID`](#groupid) | List timelogs for a group. |
|
||||
| <a id="mergerequestassigneetimelogsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | List timelogs for a project. |
|
||||
| <a id="mergerequestassigneetimelogsstartdate"></a>`startDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or after startDate. |
|
||||
| <a id="mergerequestassigneetimelogsstarttime"></a>`startTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or after startTime. |
|
||||
| <a id="mergerequestassigneetimelogsusername"></a>`username` | [`String`](#string) | List timelogs for a user. |
|
||||
|
||||
##### `MergeRequestAssignee.todos`
|
||||
|
||||
To-do items of the user.
|
||||
|
@ -11017,6 +11064,28 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestreviewerstarredprojectssearch"></a>`search` | [`String`](#string) | Search query. |
|
||||
|
||||
##### `MergeRequestReviewer.timelogs`
|
||||
|
||||
Time logged by the user.
|
||||
|
||||
Returns [`TimelogConnection`](#timelogconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestreviewertimelogsenddate"></a>`endDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or before endDate. |
|
||||
| <a id="mergerequestreviewertimelogsendtime"></a>`endTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or before endTime. |
|
||||
| <a id="mergerequestreviewertimelogsgroupid"></a>`groupId` | [`GroupID`](#groupid) | List timelogs for a group. |
|
||||
| <a id="mergerequestreviewertimelogsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | List timelogs for a project. |
|
||||
| <a id="mergerequestreviewertimelogsstartdate"></a>`startDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or after startDate. |
|
||||
| <a id="mergerequestreviewertimelogsstarttime"></a>`startTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or after startTime. |
|
||||
| <a id="mergerequestreviewertimelogsusername"></a>`username` | [`String`](#string) | List timelogs for a user. |
|
||||
|
||||
##### `MergeRequestReviewer.todos`
|
||||
|
||||
To-do items of the user.
|
||||
|
@ -12418,6 +12487,28 @@ Returns [`TerraformState`](#terraformstate).
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="projectterraformstatename"></a>`name` | [`String!`](#string) | Name of the Terraform state. |
|
||||
|
||||
##### `Project.timelogs`
|
||||
|
||||
Time logged on issues and merge requests in the project.
|
||||
|
||||
Returns [`TimelogConnection`](#timelogconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projecttimelogsenddate"></a>`endDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or before endDate. |
|
||||
| <a id="projecttimelogsendtime"></a>`endTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or before endTime. |
|
||||
| <a id="projecttimelogsgroupid"></a>`groupId` | [`GroupID`](#groupid) | List timelogs for a group. |
|
||||
| <a id="projecttimelogsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | List timelogs for a project. |
|
||||
| <a id="projecttimelogsstartdate"></a>`startDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or after startDate. |
|
||||
| <a id="projecttimelogsstarttime"></a>`startTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or after startTime. |
|
||||
| <a id="projecttimelogsusername"></a>`username` | [`String`](#string) | List timelogs for a user. |
|
||||
|
||||
##### `Project.vulnerabilities`
|
||||
|
||||
Vulnerabilities reported on the project.
|
||||
|
@ -13793,6 +13884,28 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="usercorestarredprojectssearch"></a>`search` | [`String`](#string) | Search query. |
|
||||
|
||||
##### `UserCore.timelogs`
|
||||
|
||||
Time logged by the user.
|
||||
|
||||
Returns [`TimelogConnection`](#timelogconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="usercoretimelogsenddate"></a>`endDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or before endDate. |
|
||||
| <a id="usercoretimelogsendtime"></a>`endTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or before endTime. |
|
||||
| <a id="usercoretimelogsgroupid"></a>`groupId` | [`GroupID`](#groupid) | List timelogs for a group. |
|
||||
| <a id="usercoretimelogsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | List timelogs for a project. |
|
||||
| <a id="usercoretimelogsstartdate"></a>`startDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or after startDate. |
|
||||
| <a id="usercoretimelogsstarttime"></a>`startTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or after startTime. |
|
||||
| <a id="usercoretimelogsusername"></a>`username` | [`String`](#string) | List timelogs for a user. |
|
||||
|
||||
##### `UserCore.todos`
|
||||
|
||||
To-do items of the user.
|
||||
|
@ -16647,6 +16760,28 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="userstarredprojectssearch"></a>`search` | [`String`](#string) | Search query. |
|
||||
|
||||
###### `User.timelogs`
|
||||
|
||||
Time logged by the user.
|
||||
|
||||
Returns [`TimelogConnection`](#timelogconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
####### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="usertimelogsenddate"></a>`endDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or before endDate. |
|
||||
| <a id="usertimelogsendtime"></a>`endTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or before endTime. |
|
||||
| <a id="usertimelogsgroupid"></a>`groupId` | [`GroupID`](#groupid) | List timelogs for a group. |
|
||||
| <a id="usertimelogsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | List timelogs for a project. |
|
||||
| <a id="usertimelogsstartdate"></a>`startDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or after startDate. |
|
||||
| <a id="usertimelogsstarttime"></a>`startTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or after startTime. |
|
||||
| <a id="usertimelogsusername"></a>`username` | [`String`](#string) | List timelogs for a user. |
|
||||
|
||||
###### `User.todos`
|
||||
|
||||
To-do items of the user.
|
||||
|
|
|
@ -45,6 +45,7 @@ is **not** `19.03.0`. See [troubleshooting information](#error-response-from-dae
|
|||
|
||||
WARNING:
|
||||
Dependency Scanning does not support run-time installation of compilers and interpreters.
|
||||
If you have need of this, please explain why by filling out the survey [here](https://docs.google.com/forms/d/e/1FAIpQLScKo7xEYA65rOjPTGIufAyfjPGnCALSJZoTxBlvskfFMEOZMw/viewform).
|
||||
|
||||
## Supported languages and package managers
|
||||
|
||||
|
|
|
@ -130,4 +130,7 @@ With this option enabled, `75h` is displayed instead of `1w 4d 3h`.
|
|||
- [Connection](../../api/graphql/reference/index.md#timelogconnection)
|
||||
- [Edge](../../api/graphql/reference/index.md#timelogedge)
|
||||
- [Fields](../../api/graphql/reference/index.md#timelog)
|
||||
- [Timelogs](../../api/graphql/reference/index.md#querytimelogs)
|
||||
- [Group timelogs](../../api/graphql/reference/index.md#grouptimelogs)
|
||||
- [Project Timelogs](../../api/graphql/reference/index.md#projecttimelogs)
|
||||
- [User Timelogs](../../api/graphql/reference/index.md#usertimelogs)
|
||||
|
|
|
@ -108,6 +108,7 @@ module Gitlab
|
|||
deployments: deployment_count(Deployment),
|
||||
successful_deployments: deployment_count(Deployment.success),
|
||||
failed_deployments: deployment_count(Deployment.failed),
|
||||
feature_flags: count(Operations::FeatureFlag),
|
||||
# rubocop: enable UsageData/LargeTable:
|
||||
environments: count(::Environment),
|
||||
clusters: count(::Clusters::Cluster),
|
||||
|
|
|
@ -4514,6 +4514,9 @@ msgstr ""
|
|||
msgid "Ask again later"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ask someone with write access to resolve it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Ask your group maintainer to set up a group runner."
|
||||
msgstr ""
|
||||
|
||||
|
@ -8503,6 +8506,27 @@ msgstr ""
|
|||
msgid "Confirmed:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Conflict: This file was added both in the source and target branches, but with different contents."
|
||||
msgstr ""
|
||||
|
||||
msgid "Conflict: This file was modified in both the source and target branches."
|
||||
msgstr ""
|
||||
|
||||
msgid "Conflict: This file was modified in the source branch, but removed in the target branch."
|
||||
msgstr ""
|
||||
|
||||
msgid "Conflict: This file was removed in the source branch, but modified in the target branch."
|
||||
msgstr ""
|
||||
|
||||
msgid "Conflict: This file was removed in the source branch, but renamed in the target branch."
|
||||
msgstr ""
|
||||
|
||||
msgid "Conflict: This file was renamed differently in the source and target branches."
|
||||
msgstr ""
|
||||
|
||||
msgid "Conflict: This file was renamed in the source branch, but removed in the target branch."
|
||||
msgstr ""
|
||||
|
||||
msgid "Confluence"
|
||||
msgstr ""
|
||||
|
||||
|
@ -37762,6 +37786,12 @@ msgstr ""
|
|||
msgid "You are using PostgreSQL %{pg_version_current}, but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. Please upgrade your environment to a supported PostgreSQL version, see %{pg_requirements_url} for details."
|
||||
msgstr ""
|
||||
|
||||
msgid "You can %{gitlabLinkStart}resolve conflicts on GitLab%{gitlabLinkEnd} or %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "You can %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "You can also create a project from the command line."
|
||||
msgstr ""
|
||||
|
||||
|
@ -39064,6 +39094,9 @@ msgstr ""
|
|||
msgid "does not have a supported extension. Only %{extension_list} are supported"
|
||||
msgstr ""
|
||||
|
||||
msgid "domain is not authorized for sign-up."
|
||||
msgstr ""
|
||||
|
||||
msgid "download it"
|
||||
msgstr ""
|
||||
|
||||
|
@ -39290,6 +39323,9 @@ msgstr ""
|
|||
msgid "is not an email you own"
|
||||
msgstr ""
|
||||
|
||||
msgid "is not from an allowed domain."
|
||||
msgstr ""
|
||||
|
||||
msgid "is not in the group enforcing Group Managed Account"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -57,9 +57,9 @@
|
|||
"@babel/preset-env": "^7.10.1",
|
||||
"@gitlab/at.js": "1.5.7",
|
||||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/svgs": "1.209.0",
|
||||
"@gitlab/svgs": "1.210.0",
|
||||
"@gitlab/tributejs": "1.0.0",
|
||||
"@gitlab/ui": "32.1.0",
|
||||
"@gitlab/ui": "32.2.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "6.1.3-2",
|
||||
"@rails/ujs": "6.1.3-2",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module QA
|
||||
RSpec.describe 'Package' do
|
||||
describe 'Container Registry Online Garbage Collection', :registry_gc, only: { subdomain: %i[pre] } do
|
||||
describe 'Container Registry Online Garbage Collection', :registry_gc, only: { subdomain: %i[pre] }, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/337791', type: :waiting_on } do
|
||||
let(:group) { Resource::Group.fabricate_via_api! }
|
||||
|
||||
let(:imported_project) do
|
||||
|
|
|
@ -153,7 +153,8 @@ module Trigger
|
|||
'ALTERNATIVE_SOURCES' => 'true',
|
||||
'SECURITY_SOURCES' => Trigger.security? ? 'true' : 'false',
|
||||
'ee' => Trigger.ee? ? 'true' : 'false',
|
||||
'QA_BRANCH' => ENV['QA_BRANCH'] || 'master'
|
||||
'QA_BRANCH' => ENV['QA_BRANCH'] || 'master',
|
||||
'CACHE_UPDATE' => ENV['OMNIBUS_GITLAB_CACHE_UPDATE']
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -123,6 +123,8 @@ FactoryBot.define do
|
|||
create_list(:project_snippet, 2, project: projects[0], created_at: n.days.ago)
|
||||
create(:personal_snippet, created_at: n.days.ago)
|
||||
end
|
||||
|
||||
create(:operations_feature_flag, project: projects[0])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,7 +17,6 @@ import TreeList from '~/diffs/components/tree_list.vue';
|
|||
/* You know what: sometimes alphabetical isn't the best order */
|
||||
import CollapsedFilesWarning from '~/diffs/components/collapsed_files_warning.vue';
|
||||
import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue';
|
||||
import MergeConflictWarning from '~/diffs/components/merge_conflict_warning.vue';
|
||||
/* eslint-enable import/order */
|
||||
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
@ -542,43 +541,6 @@ describe('diffs/components/app', () => {
|
|||
expect(getCollapsedFilesWarning(wrapper).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('merge conflicts', () => {
|
||||
it('should render the merge conflicts banner if viewing the whole changeset and there are conflicts', () => {
|
||||
createComponent({}, ({ state }) => {
|
||||
Object.assign(state.diffs, {
|
||||
latestDiff: true,
|
||||
startVersion: null,
|
||||
hasConflicts: true,
|
||||
canMerge: false,
|
||||
conflictResolutionPath: 'path',
|
||||
});
|
||||
});
|
||||
|
||||
expect(wrapper.find(MergeConflictWarning).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it.each`
|
||||
prop | value
|
||||
${'latestDiff'} | ${false}
|
||||
${'startVersion'} | ${'notnull'}
|
||||
${'hasConflicts'} | ${false}
|
||||
`(
|
||||
"should not render if any of the MR properties aren't correct - like $prop: $value",
|
||||
({ prop, value }) => {
|
||||
createComponent({}, ({ state }) => {
|
||||
Object.assign(state.diffs, {
|
||||
latestDiff: true,
|
||||
startVersion: null,
|
||||
hasConflicts: true,
|
||||
[prop]: value,
|
||||
});
|
||||
});
|
||||
|
||||
expect(wrapper.find(MergeConflictWarning).exists()).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should display commit widget if store has a commit', () => {
|
||||
|
|
|
@ -541,4 +541,34 @@ describe('DiffFile', () => {
|
|||
|
||||
expect(findLoader(wrapper).exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('merge conflicts', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('does not render conflict alert', () => {
|
||||
const file = {
|
||||
...getReadableFile(),
|
||||
conflict_type: null,
|
||||
renderIt: true,
|
||||
};
|
||||
|
||||
({ wrapper, store } = createComponent({ file }));
|
||||
|
||||
expect(wrapper.find('[data-testid="conflictsAlert"]').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders conflict alert when conflict_type is present', () => {
|
||||
const file = {
|
||||
...getReadableFile(),
|
||||
conflict_type: 'both_modified',
|
||||
renderIt: true,
|
||||
};
|
||||
|
||||
({ wrapper, store } = createComponent({ file }));
|
||||
|
||||
expect(wrapper.find('[data-testid="conflictsAlert"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ResolvesIds do
|
||||
# gid://gitlab/Project/6
|
||||
# gid://gitlab/Issue/6
|
||||
# gid://gitlab/Project/6 gid://gitlab/Issue/6
|
||||
context 'with a single project' do
|
||||
let(:ids) { 'gid://gitlab/Project/6' }
|
||||
let(:type) { ::Types::GlobalIDType[::Project] }
|
||||
|
||||
it 'returns the correct array' do
|
||||
expect(resolve_ids).to match_array(['6'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a single issue' do
|
||||
let(:ids) { 'gid://gitlab/Issue/9' }
|
||||
let(:type) { ::Types::GlobalIDType[::Issue] }
|
||||
|
||||
it 'returns the correct array' do
|
||||
expect(resolve_ids).to match_array(['9'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple users' do
|
||||
let(:ids) { ['gid://gitlab/User/7', 'gid://gitlab/User/13', 'gid://gitlab/User/21'] }
|
||||
let(:type) { ::Types::GlobalIDType[::User] }
|
||||
|
||||
it 'returns the correct array' do
|
||||
expect(resolve_ids).to match_array(%w[7 13 21])
|
||||
end
|
||||
end
|
||||
|
||||
def mock_resolver
|
||||
Class.new(GraphQL::Schema::Resolver) { extend ResolvesIds }
|
||||
end
|
||||
|
||||
def resolve_ids
|
||||
mock_resolver.resolve_ids(ids, type)
|
||||
end
|
||||
end
|
|
@ -5,115 +5,306 @@ require 'spec_helper'
|
|||
RSpec.describe Resolvers::TimelogResolver do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, :empty_repo, :public, group: group) }
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:error_class) { Gitlab::Graphql::Errors::ArgumentError }
|
||||
|
||||
specify do
|
||||
expect(described_class).to have_non_null_graphql_type(::Types::TimelogType.connection_type)
|
||||
end
|
||||
|
||||
context "with a group" do
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, :empty_repo, :public, group: group) }
|
||||
shared_examples_for 'with a project' do
|
||||
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
|
||||
let_it_be(:timelog1) { create(:issue_timelog, issue: issue, spent_at: 2.days.ago.beginning_of_day) }
|
||||
let_it_be(:timelog2) { create(:issue_timelog, issue: issue, spent_at: 2.days.ago.end_of_day) }
|
||||
let_it_be(:timelog3) { create(:merge_request_timelog, merge_request: merge_request, spent_at: 10.days.ago) }
|
||||
|
||||
describe '#resolve' do
|
||||
let_it_be(:short_time_ago) { 5.days.ago.beginning_of_day }
|
||||
let_it_be(:medium_time_ago) { 15.days.ago.beginning_of_day }
|
||||
let(:args) { { start_time: 6.days.ago, end_time: 2.days.ago.noon } }
|
||||
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
|
||||
it 'finds all timelogs within given dates' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
let_it_be(:timelog1) { create(:issue_timelog, issue: issue, spent_at: short_time_ago.beginning_of_day) }
|
||||
let_it_be(:timelog2) { create(:issue_timelog, issue: issue, spent_at: short_time_ago.end_of_day) }
|
||||
let_it_be(:timelog3) { create(:merge_request_timelog, merge_request: merge_request, spent_at: medium_time_ago) }
|
||||
expect(timelogs).to contain_exactly(timelog1)
|
||||
end
|
||||
|
||||
let(:args) { { start_time: short_time_ago, end_time: short_time_ago.noon } }
|
||||
context 'when no dates specified' do
|
||||
let(:args) { {} }
|
||||
|
||||
it 'finds all timelogs' do
|
||||
timelogs = resolve_timelogs
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1, timelog2, timelog3)
|
||||
end
|
||||
end
|
||||
|
||||
it 'finds all timelogs within given dates' do
|
||||
context 'when only start_time present' do
|
||||
let(:args) { { start_time: 2.days.ago.noon } }
|
||||
|
||||
it 'finds timelogs after the start_time' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when only end_time present' do
|
||||
let(:args) { { end_time: 2.days.ago.noon } }
|
||||
|
||||
it 'finds timelogs before the end_time' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1, timelog3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when start_time and end_date are present' do
|
||||
let(:args) { { start_time: 6.days.ago, end_date: 2.days.ago } }
|
||||
|
||||
it 'finds timelogs until the end of day of end_date' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1, timelog2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when start_date and end_time are present' do
|
||||
let(:args) { { start_date: 6.days.ago, end_time: 2.days.ago.noon } }
|
||||
|
||||
it 'finds all timelogs within start_date and end_time' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when only start_date is present' do
|
||||
let(:args) { { start_date: short_time_ago } }
|
||||
it 'return nothing when user has insufficient permissions' do
|
||||
project2 = create(:project, :empty_repo, :private)
|
||||
issue2 = create(:issue, project: project2)
|
||||
create(:issue_timelog, issue: issue2, spent_at: 2.days.ago.beginning_of_day)
|
||||
|
||||
it 'finds timelogs until the end of day of end_date' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
user = create(:user)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1, timelog2)
|
||||
expect(resolve_timelogs(user: user, obj: project2, **args)).to be_empty
|
||||
end
|
||||
|
||||
context 'when arguments are invalid' do
|
||||
let_it_be(:error_class) { Gitlab::Graphql::Errors::ArgumentError }
|
||||
|
||||
context 'when start_time and start_date are present' do
|
||||
let(:args) { { start_time: 6.days.ago, start_date: 6.days.ago } }
|
||||
|
||||
it 'returns correct error' do
|
||||
expect { resolve_timelogs(**args) }
|
||||
.to raise_error(error_class, /Provide either a start date or time, but not both/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when only end_date is present' do
|
||||
let(:args) { { end_date: medium_time_ago } }
|
||||
context 'when end_time and end_date are present' do
|
||||
let(:args) { { end_time: 2.days.ago, end_date: 2.days.ago } }
|
||||
|
||||
it 'finds timelogs until the end of day of end_date' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog3)
|
||||
it 'returns correct error' do
|
||||
expect { resolve_timelogs(**args) }
|
||||
.to raise_error(error_class, /Provide either an end date or time, but not both/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when start_time and end_date are present' do
|
||||
let(:args) { { start_time: short_time_ago, end_date: short_time_ago } }
|
||||
context 'when start argument is after end argument' do
|
||||
let(:args) { { start_time: 2.days.ago, end_time: 6.days.ago } }
|
||||
|
||||
it 'finds timelogs until the end of day of end_date' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1, timelog2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when start_date and end_time are present' do
|
||||
let(:args) { { start_date: short_time_ago, end_time: short_time_ago.noon } }
|
||||
|
||||
it 'finds all timelogs within start_date and end_time' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when arguments are invalid' do
|
||||
let_it_be(:error_class) { Gitlab::Graphql::Errors::ArgumentError }
|
||||
|
||||
context 'when start_time and start_date are present' do
|
||||
let(:args) { { start_time: short_time_ago, start_date: short_time_ago } }
|
||||
|
||||
it 'returns correct error' do
|
||||
expect { resolve_timelogs(**args) }
|
||||
.to raise_error(error_class, /Provide either a start date or time, but not both/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when end_time and end_date are present' do
|
||||
let(:args) { { end_time: short_time_ago, end_date: short_time_ago } }
|
||||
|
||||
it 'returns correct error' do
|
||||
expect { resolve_timelogs(**args) }
|
||||
.to raise_error(error_class, /Provide either an end date or time, but not both/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when start argument is after end argument' do
|
||||
let(:args) { { start_time: short_time_ago, end_time: medium_time_ago } }
|
||||
|
||||
it 'returns correct error' do
|
||||
expect { resolve_timelogs(**args) }
|
||||
.to raise_error(error_class, /Start argument must be before End argument/)
|
||||
end
|
||||
it 'returns correct error' do
|
||||
expect { resolve_timelogs(**args) }
|
||||
.to raise_error(error_class, /Start argument must be before End argument/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_timelogs(user: current_user, **args)
|
||||
shared_examples "with a group" do
|
||||
let_it_be(:short_time_ago) { 5.days.ago.beginning_of_day }
|
||||
let_it_be(:medium_time_ago) { 15.days.ago.beginning_of_day }
|
||||
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
|
||||
|
||||
let_it_be(:timelog1) { create(:issue_timelog, issue: issue, spent_at: short_time_ago.beginning_of_day) }
|
||||
let_it_be(:timelog2) { create(:issue_timelog, issue: issue, spent_at: short_time_ago.end_of_day) }
|
||||
let_it_be(:timelog3) { create(:merge_request_timelog, merge_request: merge_request, spent_at: medium_time_ago) }
|
||||
|
||||
let(:args) { { start_time: short_time_ago, end_time: short_time_ago.noon } }
|
||||
|
||||
it 'finds all timelogs' do
|
||||
timelogs = resolve_timelogs
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1, timelog2, timelog3)
|
||||
end
|
||||
|
||||
it 'finds all timelogs within given dates' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1)
|
||||
end
|
||||
|
||||
context 'when only start_date is present' do
|
||||
let(:args) { { start_date: short_time_ago } }
|
||||
|
||||
it 'finds timelogs until the end of day of end_date' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1, timelog2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when only end_date is present' do
|
||||
let(:args) { { end_date: medium_time_ago } }
|
||||
|
||||
it 'finds timelogs until the end of day of end_date' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when start_time and end_date are present' do
|
||||
let(:args) { { start_time: short_time_ago, end_date: short_time_ago } }
|
||||
|
||||
it 'finds timelogs until the end of day of end_date' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1, timelog2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when start_date and end_time are present' do
|
||||
let(:args) { { start_date: short_time_ago, end_time: short_time_ago.noon } }
|
||||
|
||||
it 'finds all timelogs within start_date and end_time' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when arguments are invalid' do
|
||||
context 'when start_time and start_date are present' do
|
||||
let(:args) { { start_time: short_time_ago, start_date: short_time_ago } }
|
||||
|
||||
it 'returns correct error' do
|
||||
expect { resolve_timelogs(**args) }
|
||||
.to raise_error(error_class, /Provide either a start date or time, but not both/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when end_time and end_date are present' do
|
||||
let(:args) { { end_time: short_time_ago, end_date: short_time_ago } }
|
||||
|
||||
it 'returns correct error' do
|
||||
expect { resolve_timelogs(**args) }
|
||||
.to raise_error(error_class, /Provide either an end date or time, but not both/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when start argument is after end argument' do
|
||||
let(:args) { { start_time: short_time_ago, end_time: medium_time_ago } }
|
||||
|
||||
it 'returns correct error' do
|
||||
expect { resolve_timelogs(**args) }
|
||||
.to raise_error(error_class, /Start argument must be before End argument/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "with a user" do
|
||||
let_it_be(:short_time_ago) { 5.days.ago.beginning_of_day }
|
||||
let_it_be(:medium_time_ago) { 15.days.ago.beginning_of_day }
|
||||
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
|
||||
|
||||
let_it_be(:timelog1) { create(:issue_timelog, issue: issue, user: current_user) }
|
||||
let_it_be(:timelog2) { create(:issue_timelog, issue: issue, user: create(:user)) }
|
||||
let_it_be(:timelog3) { create(:merge_request_timelog, merge_request: merge_request, user: current_user) }
|
||||
|
||||
it 'blah' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs).to contain_exactly(timelog1, timelog3)
|
||||
end
|
||||
end
|
||||
|
||||
context "on a project" do
|
||||
let(:object) { project }
|
||||
let(:extra_args) { {} }
|
||||
|
||||
it_behaves_like 'with a project'
|
||||
end
|
||||
|
||||
context "with a project filter" do
|
||||
let(:object) { nil }
|
||||
let(:extra_args) { { project_id: project.to_global_id } }
|
||||
|
||||
it_behaves_like 'with a project'
|
||||
end
|
||||
|
||||
context 'on a group' do
|
||||
let(:object) { group }
|
||||
let(:extra_args) { {} }
|
||||
|
||||
it_behaves_like 'with a group'
|
||||
end
|
||||
|
||||
context 'with a group filter' do
|
||||
let(:object) { nil }
|
||||
let(:extra_args) { { group_id: group.to_global_id } }
|
||||
|
||||
it_behaves_like 'with a group'
|
||||
end
|
||||
|
||||
context 'on a user' do
|
||||
let(:object) { current_user }
|
||||
let(:extra_args) { {} }
|
||||
let(:args) { {} }
|
||||
|
||||
it_behaves_like 'with a user'
|
||||
end
|
||||
|
||||
context 'with a user filter' do
|
||||
let(:object) { nil }
|
||||
let(:extra_args) { { username: current_user.username } }
|
||||
let(:args) { {} }
|
||||
|
||||
it_behaves_like 'with a user'
|
||||
end
|
||||
|
||||
context 'when > `default_max_page_size` records' do
|
||||
let(:object) { nil }
|
||||
let!(:timelog_list) { create_list(:timelog, 101, issue: issue) }
|
||||
let(:args) { { project_id: "gid://gitlab/Project/#{project.id}" } }
|
||||
let(:extra_args) { {} }
|
||||
|
||||
it 'pagination returns `default_max_page_size` and sets `has_next_page` true' do
|
||||
timelogs = resolve_timelogs(**args)
|
||||
|
||||
expect(timelogs.items.count).to be(100)
|
||||
expect(timelogs.has_next_page).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no object or arguments provided' do
|
||||
let(:object) { nil }
|
||||
let(:args) { {} }
|
||||
let(:extra_args) { {} }
|
||||
|
||||
it 'returns correct error' do
|
||||
expect { resolve_timelogs(**args) }
|
||||
.to raise_error(error_class, /Provide at least one argument/)
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_timelogs(user: current_user, obj: object, **args)
|
||||
context = { current_user: user }
|
||||
resolve(described_class, obj: group, args: args, ctx: context)
|
||||
resolve(described_class, obj: obj, args: args.merge(extra_args), ctx: context)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,7 +18,7 @@ RSpec.describe GitlabSchema.types['Group'] do
|
|||
two_factor_grace_period auto_devops_enabled emails_disabled
|
||||
mentions_disabled parent boards milestones group_members
|
||||
merge_requests container_repositories container_repositories_count
|
||||
packages shared_runners_setting
|
||||
packages shared_runners_setting timelogs
|
||||
]
|
||||
|
||||
expect(described_class).to include_graphql_fields(*expected_fields)
|
||||
|
@ -39,6 +39,15 @@ RSpec.describe GitlabSchema.types['Group'] do
|
|||
it { is_expected.to have_graphql_resolver(Resolvers::GroupMembersResolver) }
|
||||
end
|
||||
|
||||
describe 'timelogs field' do
|
||||
subject { described_class.fields['timelogs'] }
|
||||
|
||||
it 'finds timelogs between start time and end time' do
|
||||
is_expected.to have_graphql_resolver(Resolvers::TimelogResolver)
|
||||
is_expected.to have_non_null_graphql_type(Types::TimelogType.connection_type)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'a GraphQL type with labels' do
|
||||
let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups, :includeDescendantGroups, :onlyGroupLabels] }
|
||||
end
|
||||
|
|
|
@ -32,6 +32,7 @@ RSpec.describe GitlabSchema.types['MergeRequestReviewer'] do
|
|||
callouts
|
||||
merge_request_interaction
|
||||
namespace
|
||||
timelogs
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
|
|
|
@ -33,7 +33,7 @@ RSpec.describe GitlabSchema.types['Project'] do
|
|||
issue_status_counts terraform_states alert_management_integrations
|
||||
container_repositories container_repositories_count
|
||||
pipeline_analytics squash_read_only sast_ci_configuration
|
||||
ci_template
|
||||
ci_template timelogs
|
||||
]
|
||||
|
||||
expect(described_class).to include_graphql_fields(*expected_fields)
|
||||
|
@ -392,6 +392,15 @@ RSpec.describe GitlabSchema.types['Project'] do
|
|||
it { is_expected.to have_graphql_resolver(Resolvers::Terraform::StatesResolver) }
|
||||
end
|
||||
|
||||
describe 'timelogs field' do
|
||||
subject { described_class.fields['timelogs'] }
|
||||
|
||||
it 'finds timelogs for project' do
|
||||
is_expected.to have_graphql_resolver(Resolvers::TimelogResolver)
|
||||
is_expected.to have_graphql_type(Types::TimelogType.connection_type)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'a GraphQL type with labels' do
|
||||
let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups] }
|
||||
end
|
||||
|
|
|
@ -26,6 +26,7 @@ RSpec.describe GitlabSchema.types['Query'] do
|
|||
runner_platforms
|
||||
runner
|
||||
runners
|
||||
timelogs
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields).at_least
|
||||
|
@ -125,4 +126,14 @@ RSpec.describe GitlabSchema.types['Query'] do
|
|||
|
||||
it { is_expected.to have_graphql_type(Types::Packages::PackageDetailsType) }
|
||||
end
|
||||
|
||||
describe 'timelogs field' do
|
||||
subject { described_class.fields['timelogs'] }
|
||||
|
||||
it 'returns timelogs' do
|
||||
is_expected.to have_graphql_arguments(:startDate, :endDate, :startTime, :endTime, :username, :projectId, :groupId, :after, :before, :first, :last)
|
||||
is_expected.to have_graphql_type(Types::TimelogType.connection_type)
|
||||
is_expected.to have_graphql_resolver(Resolvers::TimelogResolver)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,6 +37,7 @@ RSpec.describe GitlabSchema.types['User'] do
|
|||
starredProjects
|
||||
callouts
|
||||
namespace
|
||||
timelogs
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
|
@ -58,4 +59,13 @@ RSpec.describe GitlabSchema.types['User'] do
|
|||
is_expected.to have_graphql_type(Types::UserCalloutType.connection_type)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'timelogs field' do
|
||||
subject { described_class.fields['timelogs'] }
|
||||
|
||||
it 'returns user timelogs' do
|
||||
is_expected.to have_graphql_resolver(Resolvers::TimelogResolver)
|
||||
is_expected.to have_graphql_type(Types::TimelogType.connection_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -622,6 +622,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
|
|||
expect(count_data[:deployments]).to eq(4)
|
||||
expect(count_data[:successful_deployments]).to eq(2)
|
||||
expect(count_data[:failed_deployments]).to eq(2)
|
||||
expect(count_data[:feature_flags]).to eq(1)
|
||||
expect(count_data[:snippets]).to eq(6)
|
||||
expect(count_data[:personal_snippets]).to eq(2)
|
||||
expect(count_data[:project_snippets]).to eq(4)
|
||||
|
|
|
@ -14,6 +14,28 @@ RSpec.describe Ci::PendingBuild do
|
|||
it { is_expected.to belong_to :namespace }
|
||||
end
|
||||
|
||||
describe 'scopes' do
|
||||
describe '.with_instance_runners' do
|
||||
subject(:pending_builds) { described_class.with_instance_runners }
|
||||
|
||||
let!(:pending_build_1) { create(:ci_pending_build, instance_runners_enabled: false) }
|
||||
|
||||
context 'when pending builds cannot be picked up by runner' do
|
||||
it 'returns an empty collection of pending builds' do
|
||||
expect(pending_builds).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pending builds can be picked up by runner' do
|
||||
let!(:pending_build_2) { create(:ci_pending_build) }
|
||||
|
||||
it 'returns matching pending builds' do
|
||||
expect(pending_builds).to contain_exactly(pending_build_2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.upsert_from_build!' do
|
||||
context 'another pending entry does not exist' do
|
||||
it 'creates a new pending entry' do
|
||||
|
|
|
@ -2598,6 +2598,21 @@ RSpec.describe Group do
|
|||
it { is_expected.to eq(Set.new([child_1.id])) }
|
||||
end
|
||||
|
||||
describe '.timelogs' do
|
||||
let(:project) { create(:project, namespace: group) }
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
let(:other_project) { create(:project, namespace: create(:group)) }
|
||||
let(:other_issue) { create(:issue, project: other_project) }
|
||||
|
||||
let!(:timelog1) { create(:timelog, issue: issue) }
|
||||
let!(:timelog2) { create(:timelog, issue: other_issue) }
|
||||
let!(:timelog3) { create(:timelog, issue: issue) }
|
||||
|
||||
it 'returns timelogs belonging to the group' do
|
||||
expect(group.timelogs).to contain_exactly(timelog1, timelog3)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_ability_name' do
|
||||
it 'returns group' do
|
||||
group = build(:group)
|
||||
|
|
|
@ -64,6 +64,49 @@ RSpec.describe Member do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with admin signup restrictions' do
|
||||
context 'when allowed domains for signup is enabled' do
|
||||
before do
|
||||
stub_application_setting(domain_allowlist: ['example.com'])
|
||||
end
|
||||
|
||||
it 'adds an error message when email is not accepted' do
|
||||
member = build(:group_member, :invited, invite_email: 'info@gitlab.com')
|
||||
|
||||
expect(member).not_to be_valid
|
||||
expect(member.errors.messages[:user].first).to eq(_('domain is not authorized for sign-up.'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when denylist is enabled' do
|
||||
before do
|
||||
stub_application_setting(domain_denylist_enabled: true)
|
||||
stub_application_setting(domain_denylist: ['example.org'])
|
||||
end
|
||||
|
||||
it 'adds an error message when email is denied' do
|
||||
member = build(:group_member, :invited, invite_email: 'denylist@example.org')
|
||||
|
||||
expect(member).not_to be_valid
|
||||
expect(member.errors.messages[:user].first).to eq(_('is not from an allowed domain.'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when email restrictions is enabled' do
|
||||
before do
|
||||
stub_application_setting(email_restrictions_enabled: true)
|
||||
stub_application_setting(email_restrictions: '([\+]|\b(\w*gitlab.com\w*)\b)')
|
||||
end
|
||||
|
||||
it 'adds an error message when email is not accepted' do
|
||||
member = build(:group_member, :invited, invite_email: 'info@gitlab.com')
|
||||
|
||||
expect(member).not_to be_valid
|
||||
expect(member.errors.messages[:user].first).to eq(_('is not allowed. Try again with a different email address, or contact your GitLab admin.'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when a child member inherits its access level" do
|
||||
let(:user) { create(:user) }
|
||||
let(:member) { create(:group_member, :developer, user: user) }
|
||||
|
|
|
@ -106,4 +106,81 @@ RSpec.describe NamespaceSetting, type: :model do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#prevent_sharing_groups_outside_hierarchy' do
|
||||
let(:settings) { create(:namespace_settings, prevent_sharing_groups_outside_hierarchy: true) }
|
||||
let!(:group) { create(:group, parent: parent, namespace_settings: settings ) }
|
||||
|
||||
subject(:group_sharing_setting) { settings.prevent_sharing_groups_outside_hierarchy }
|
||||
|
||||
context 'when this namespace is a root ancestor' do
|
||||
let(:parent) { nil }
|
||||
|
||||
it 'returns the actual stored value' do
|
||||
expect(group_sharing_setting).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when this namespace is a descendant' do
|
||||
let(:parent) { create(:group) }
|
||||
|
||||
it 'returns the value stored for the parent settings' do
|
||||
expect(group_sharing_setting).to eq(parent.namespace_settings.prevent_sharing_groups_outside_hierarchy)
|
||||
expect(group_sharing_setting).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'hooks related to group user cap update' do
|
||||
let(:settings) { create(:namespace_settings, new_user_signups_cap: user_cap) }
|
||||
let(:group) { create(:group, namespace_settings: settings) }
|
||||
|
||||
before do
|
||||
allow(group).to receive(:root?).and_return(true)
|
||||
end
|
||||
|
||||
context 'when updating a group with a user cap' do
|
||||
let(:user_cap) { nil }
|
||||
|
||||
it 'also sets share_with_group_lock and prevent_sharing_groups_outside_hierarchy to true' do
|
||||
expect(group.new_user_signups_cap).to be_nil
|
||||
expect(group.share_with_group_lock).to be_falsey
|
||||
expect(settings.prevent_sharing_groups_outside_hierarchy).to be_falsey
|
||||
|
||||
settings.update!(new_user_signups_cap: 10)
|
||||
group.reload
|
||||
|
||||
expect(group.new_user_signups_cap).to eq(10)
|
||||
expect(group.share_with_group_lock).to be_truthy
|
||||
expect(settings.reload.prevent_sharing_groups_outside_hierarchy).to be_truthy
|
||||
end
|
||||
|
||||
it 'has share_with_group_lock and prevent_sharing_groups_outside_hierarchy returning true for descendent groups' do
|
||||
descendent = create(:group, parent: group)
|
||||
desc_settings = descendent.namespace_settings
|
||||
|
||||
expect(descendent.share_with_group_lock).to be_falsey
|
||||
expect(desc_settings.prevent_sharing_groups_outside_hierarchy).to be_falsey
|
||||
|
||||
settings.update!(new_user_signups_cap: 10)
|
||||
|
||||
expect(descendent.reload.share_with_group_lock).to be_truthy
|
||||
expect(desc_settings.reload.prevent_sharing_groups_outside_hierarchy).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when removing a user cap from namespace settings' do
|
||||
let(:user_cap) { 10 }
|
||||
|
||||
it 'leaves share_with_group_lock and prevent_sharing_groups_outside_hierarchy set to true to the related group' do
|
||||
expect(group.share_with_group_lock).to be_truthy
|
||||
expect(settings.prevent_sharing_groups_outside_hierarchy).to be_truthy
|
||||
|
||||
settings.update!(new_user_signups_cap: nil)
|
||||
|
||||
expect(group.reload.share_with_group_lock).to be_truthy
|
||||
expect(settings.reload.prevent_sharing_groups_outside_hierarchy).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -70,8 +70,9 @@ RSpec.describe Timelog do
|
|||
let_it_be(:medium_time_ago) { 15.days.ago }
|
||||
let_it_be(:long_time_ago) { 65.days.ago }
|
||||
|
||||
let_it_be(:timelog) { create(:issue_timelog, spent_at: long_time_ago) }
|
||||
let_it_be(:timelog1) { create(:issue_timelog, spent_at: medium_time_ago, issue: group_issue) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:timelog) { create(:issue_timelog, spent_at: long_time_ago, user: user) }
|
||||
let_it_be(:timelog1) { create(:issue_timelog, spent_at: medium_time_ago, issue: group_issue, user: user) }
|
||||
let_it_be(:timelog2) { create(:issue_timelog, spent_at: short_time_ago, issue: subgroup_issue) }
|
||||
let_it_be(:timelog3) { create(:merge_request_timelog, spent_at: long_time_ago) }
|
||||
let_it_be(:timelog4) { create(:merge_request_timelog, spent_at: medium_time_ago, merge_request: group_merge_request) }
|
||||
|
@ -83,6 +84,25 @@ RSpec.describe Timelog do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.for_user' do
|
||||
it 'return timelogs created by user' do
|
||||
expect(described_class.for_user(user)).to contain_exactly(timelog, timelog1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.in_project' do
|
||||
it 'returns timelogs created for project issues and merge requests' do
|
||||
project = create(:project, :empty_repo)
|
||||
|
||||
create(:issue_timelog)
|
||||
create(:merge_request_timelog)
|
||||
timelog1 = create(:issue_timelog, issue: create(:issue, project: project))
|
||||
timelog2 = create(:merge_request_timelog, merge_request: create(:merge_request, source_project: project))
|
||||
|
||||
expect(described_class.in_project(project.id)).to contain_exactly(timelog1, timelog2)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.at_or_after' do
|
||||
it 'returns timelogs at the time limit' do
|
||||
timelogs = described_class.at_or_after(short_time_ago)
|
||||
|
|
|
@ -124,6 +124,7 @@ RSpec.describe User do
|
|||
it { is_expected.to have_many(:merge_request_reviewers).inverse_of(:reviewer) }
|
||||
it { is_expected.to have_many(:created_custom_emoji).inverse_of(:creator) }
|
||||
it { is_expected.to have_many(:in_product_marketing_emails) }
|
||||
it { is_expected.to have_many(:timelogs) }
|
||||
|
||||
describe "#user_detail" do
|
||||
it 'does not persist `user_detail` by default' do
|
||||
|
@ -495,7 +496,7 @@ RSpec.describe User do
|
|||
describe 'email' do
|
||||
context 'when no signup domains allowed' do
|
||||
before do
|
||||
allow_any_instance_of(ApplicationSetting).to receive(:domain_allowlist).and_return([])
|
||||
stub_application_setting(domain_allowlist: [])
|
||||
end
|
||||
|
||||
it 'accepts any email' do
|
||||
|
@ -506,7 +507,7 @@ RSpec.describe User do
|
|||
|
||||
context 'bad regex' do
|
||||
before do
|
||||
allow_any_instance_of(ApplicationSetting).to receive(:domain_allowlist).and_return(['([a-zA-Z0-9]+)+\.com'])
|
||||
stub_application_setting(domain_allowlist: ['([a-zA-Z0-9]+)+\.com'])
|
||||
end
|
||||
|
||||
it 'does not hang on evil input' do
|
||||
|
@ -520,7 +521,7 @@ RSpec.describe User do
|
|||
|
||||
context 'when a signup domain is allowed and subdomains are allowed' do
|
||||
before do
|
||||
allow_any_instance_of(ApplicationSetting).to receive(:domain_allowlist).and_return(['example.com', '*.example.com'])
|
||||
stub_application_setting(domain_allowlist: ['example.com', '*.example.com'])
|
||||
end
|
||||
|
||||
it 'accepts info@example.com' do
|
||||
|
@ -536,12 +537,13 @@ RSpec.describe User do
|
|||
it 'rejects example@test.com' do
|
||||
user = build(:user, email: "example@test.com")
|
||||
expect(user).to be_invalid
|
||||
expect(user.errors.messages[:email].first).to eq(_('domain is not authorized for sign-up.'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a signup domain is allowed and subdomains are not allowed' do
|
||||
before do
|
||||
allow_any_instance_of(ApplicationSetting).to receive(:domain_allowlist).and_return(['example.com'])
|
||||
stub_application_setting(domain_allowlist: ['example.com'])
|
||||
end
|
||||
|
||||
it 'accepts info@example.com' do
|
||||
|
@ -552,11 +554,13 @@ RSpec.describe User do
|
|||
it 'rejects info@test.example.com' do
|
||||
user = build(:user, email: "info@test.example.com")
|
||||
expect(user).to be_invalid
|
||||
expect(user.errors.messages[:email].first).to eq(_('domain is not authorized for sign-up.'))
|
||||
end
|
||||
|
||||
it 'rejects example@test.com' do
|
||||
user = build(:user, email: "example@test.com")
|
||||
expect(user).to be_invalid
|
||||
expect(user.errors.messages[:email].first).to eq(_('domain is not authorized for sign-up.'))
|
||||
end
|
||||
|
||||
it 'accepts example@test.com when added by another user' do
|
||||
|
@ -567,13 +571,13 @@ RSpec.describe User do
|
|||
|
||||
context 'domain denylist' do
|
||||
before do
|
||||
allow_any_instance_of(ApplicationSetting).to receive(:domain_denylist_enabled?).and_return(true)
|
||||
allow_any_instance_of(ApplicationSetting).to receive(:domain_denylist).and_return(['example.com'])
|
||||
stub_application_setting(domain_denylist_enabled: true)
|
||||
stub_application_setting(domain_denylist: ['example.com'])
|
||||
end
|
||||
|
||||
context 'bad regex' do
|
||||
before do
|
||||
allow_any_instance_of(ApplicationSetting).to receive(:domain_denylist).and_return(['([a-zA-Z0-9]+)+\.com'])
|
||||
stub_application_setting(domain_denylist: ['([a-zA-Z0-9]+)+\.com'])
|
||||
end
|
||||
|
||||
it 'does not hang on evil input' do
|
||||
|
@ -594,6 +598,7 @@ RSpec.describe User do
|
|||
it 'rejects info@example.com' do
|
||||
user = build(:user, email: 'info@example.com')
|
||||
expect(user).not_to be_valid
|
||||
expect(user.errors.messages[:email].first).to eq(_('is not from an allowed domain.'))
|
||||
end
|
||||
|
||||
it 'accepts info@example.com when added by another user' do
|
||||
|
@ -604,8 +609,8 @@ RSpec.describe User do
|
|||
|
||||
context 'when a signup domain is denied but a wildcard subdomain is allowed' do
|
||||
before do
|
||||
allow_any_instance_of(ApplicationSetting).to receive(:domain_denylist).and_return(['test.example.com'])
|
||||
allow_any_instance_of(ApplicationSetting).to receive(:domain_allowlist).and_return(['*.example.com'])
|
||||
stub_application_setting(domain_denylist: ['test.example.com'])
|
||||
stub_application_setting(domain_allowlist: ['*.example.com'])
|
||||
end
|
||||
|
||||
it 'gives priority to allowlist and allow info@test.example.com' do
|
||||
|
@ -616,7 +621,7 @@ RSpec.describe User do
|
|||
|
||||
context 'with both lists containing a domain' do
|
||||
before do
|
||||
allow_any_instance_of(ApplicationSetting).to receive(:domain_allowlist).and_return(['test.com'])
|
||||
stub_application_setting(domain_allowlist: ['test.com'])
|
||||
end
|
||||
|
||||
it 'accepts info@test.com' do
|
||||
|
@ -627,6 +632,7 @@ RSpec.describe User do
|
|||
it 'rejects info@example.com' do
|
||||
user = build(:user, email: 'info@example.com')
|
||||
expect(user).not_to be_valid
|
||||
expect(user.errors.messages[:email].first).to eq(_('domain is not authorized for sign-up.'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,37 +8,41 @@ RSpec.describe Ci::AfterRequeueJobService do
|
|||
|
||||
let(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
|
||||
let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 0, name: 'build') }
|
||||
let!(:test1) { create(:ci_build, :success, pipeline: pipeline, stage_idx: 1) }
|
||||
let!(:test2) { create(:ci_build, :skipped, pipeline: pipeline, stage_idx: 1) }
|
||||
let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 0, name: 'build') }
|
||||
let!(:test3) { create(:ci_build, :skipped, :dependent, pipeline: pipeline, stage_idx: 1, needed: build) }
|
||||
let!(:deploy) { create(:ci_build, :skipped, :dependent, pipeline: pipeline, stage_idx: 2, needed: test3) }
|
||||
|
||||
subject(:execute_service) { described_class.new(project, user).execute(build) }
|
||||
|
||||
it 'marks subsequent skipped jobs as processable' do
|
||||
expect(test1.reload).to be_success
|
||||
expect(test2.reload).to be_skipped
|
||||
expect(test3.reload).to be_skipped
|
||||
expect(deploy.reload).to be_skipped
|
||||
|
||||
execute_service
|
||||
|
||||
expect(test1.reload).to be_success
|
||||
expect(test2.reload).to be_created
|
||||
expect(test3.reload).to be_created
|
||||
expect(deploy.reload).to be_created
|
||||
end
|
||||
|
||||
context 'when there is a job need from the same stage' do
|
||||
let!(:test3) do
|
||||
let!(:test4) do
|
||||
create(:ci_build,
|
||||
:skipped,
|
||||
:dependent,
|
||||
pipeline: pipeline,
|
||||
stage_idx: 0,
|
||||
scheduling_type: :dag)
|
||||
end
|
||||
|
||||
before do
|
||||
create(:ci_build_need, build: test3, name: 'build')
|
||||
scheduling_type: :dag,
|
||||
needed: build)
|
||||
end
|
||||
|
||||
it 'marks subsequent skipped jobs as processable' do
|
||||
expect { execute_service }.to change { test3.reload.status }.from('skipped').to('created')
|
||||
expect { execute_service }.to change { test4.reload.status }.from('skipped').to('created')
|
||||
end
|
||||
|
||||
context 'with ci_same_stage_job_needs FF disabled' do
|
||||
|
@ -47,7 +51,7 @@ RSpec.describe Ci::AfterRequeueJobService do
|
|||
end
|
||||
|
||||
it 'does nothing with the build' do
|
||||
expect { execute_service }.not_to change { test3.reload.status }
|
||||
expect { execute_service }.not_to change { test4.reload.status }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
config:
|
||||
stages: [first, second, third]
|
||||
|
||||
job_a:
|
||||
when: manual
|
||||
stage: first
|
||||
script:
|
||||
- echo
|
||||
|
||||
job_b:
|
||||
when: manual
|
||||
stage: second
|
||||
script:
|
||||
- echo
|
||||
|
||||
job_c:
|
||||
needs: ["job_b"]
|
||||
stage: third
|
||||
script:
|
||||
- echo
|
||||
|
||||
job_d:
|
||||
needs: ["job_a"]
|
||||
stage: third
|
||||
script:
|
||||
- echo
|
||||
|
||||
init:
|
||||
expect:
|
||||
pipeline: skipped
|
||||
stages:
|
||||
first: skipped
|
||||
second: skipped
|
||||
third: skipped
|
||||
jobs:
|
||||
job_a: manual
|
||||
job_b: manual
|
||||
job_c: skipped
|
||||
job_d: skipped
|
||||
|
||||
transitions:
|
||||
- event: play
|
||||
jobs: [job_b]
|
||||
expect:
|
||||
pipeline: pending
|
||||
stages:
|
||||
first: skipped
|
||||
second: pending
|
||||
third: pending
|
||||
jobs:
|
||||
job_a: manual
|
||||
job_b: pending
|
||||
job_c: created
|
||||
job_d: skipped
|
|
@ -0,0 +1,70 @@
|
|||
config:
|
||||
stages: [first, second, third, fourth]
|
||||
|
||||
first_job:
|
||||
stage: first
|
||||
script:
|
||||
- echo
|
||||
|
||||
second_job:
|
||||
stage: second
|
||||
script:
|
||||
- echo
|
||||
when: manual
|
||||
|
||||
third_job:
|
||||
stage: third
|
||||
needs: ["second_job"]
|
||||
script:
|
||||
- echo
|
||||
|
||||
fourth_job:
|
||||
stage: fourth
|
||||
needs: ["third_job"]
|
||||
script:
|
||||
- echo
|
||||
|
||||
init:
|
||||
expect:
|
||||
pipeline: pending
|
||||
stages:
|
||||
first: pending
|
||||
second: created
|
||||
third: created
|
||||
fourth: created
|
||||
jobs:
|
||||
first_job: pending
|
||||
second_job: created
|
||||
third_job: created
|
||||
fourth_job: created
|
||||
|
||||
transitions:
|
||||
- event: success
|
||||
jobs: [first_job]
|
||||
expect:
|
||||
pipeline: success
|
||||
stages:
|
||||
first: success
|
||||
second: skipped
|
||||
third: skipped
|
||||
fourth: skipped
|
||||
jobs:
|
||||
first_job: success
|
||||
second_job: manual
|
||||
third_job: skipped
|
||||
fourth_job: skipped
|
||||
|
||||
- event: play
|
||||
jobs: [second_job]
|
||||
expect:
|
||||
pipeline: running
|
||||
stages:
|
||||
first: success
|
||||
second: pending
|
||||
third: skipped
|
||||
fourth: skipped
|
||||
jobs:
|
||||
first_job: success
|
||||
second_job: pending
|
||||
third_job: created
|
||||
fourth_job: created
|
|
@ -90,6 +90,9 @@ module Ci
|
|||
context 'allow shared runners' do
|
||||
before do
|
||||
project.update!(shared_runners_enabled: true)
|
||||
pipeline.reload
|
||||
pending_job.reload
|
||||
pending_job.create_queuing_entry!
|
||||
end
|
||||
|
||||
context 'for multiple builds' do
|
||||
|
@ -703,7 +706,21 @@ module Ci
|
|||
stub_feature_flags(ci_pending_builds_queue_source: true)
|
||||
end
|
||||
|
||||
include_examples 'handles runner assignment'
|
||||
context 'with ci_queueing_denormalize_shared_runners_information enabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_queueing_denormalize_shared_runners_information: true)
|
||||
end
|
||||
|
||||
include_examples 'handles runner assignment'
|
||||
end
|
||||
|
||||
context 'with ci_queueing_denormalize_shared_runners_information disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_queueing_denormalize_shared_runners_information: false)
|
||||
end
|
||||
|
||||
include_examples 'handles runner assignment'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not using pending builds table' do
|
||||
|
@ -777,6 +794,11 @@ module Ci
|
|||
end
|
||||
|
||||
context 'when shared runner is used' do
|
||||
before do
|
||||
pending_job.reload
|
||||
pending_job.create_queuing_entry!
|
||||
end
|
||||
|
||||
let(:runner) { create(:ci_runner, :instance, tag_list: %w(tag1 tag2)) }
|
||||
let(:expected_shared_runner) { true }
|
||||
let(:expected_shard) { ::Gitlab::Ci::Queue::Metrics::DEFAULT_METRICS_SHARD }
|
||||
|
|
16
yarn.lock
16
yarn.lock
|
@ -898,20 +898,20 @@
|
|||
stylelint-declaration-strict-value "1.7.7"
|
||||
stylelint-scss "3.18.0"
|
||||
|
||||
"@gitlab/svgs@1.209.0":
|
||||
version "1.209.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.209.0.tgz#06020b74668df17b9bdaa33b561b4800ca34cc10"
|
||||
integrity sha512-V6NaXDhu899tNCuU4+VepY7Rv2Ge3ViOctrUAS3NQtGcXV1THDhGAg6+OCFpgHr1fhmxYNwpxQ8OTh+HaPvtIA==
|
||||
"@gitlab/svgs@1.210.0":
|
||||
version "1.210.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.210.0.tgz#f2d36a2073eb5059fe48a08130145d740c220efa"
|
||||
integrity sha512-IVALHZKM4QB0djEWJbwgWlpYFD4UF9sqml2SLS5vS/p/FVDeMe7fz7hYOH42xZaZn2iWE6XE9DOVyd9IDNWzPg==
|
||||
|
||||
"@gitlab/tributejs@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
|
||||
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
|
||||
|
||||
"@gitlab/ui@32.1.0":
|
||||
version "32.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.1.0.tgz#3b7a4b4d68a726f2e6a2d87db03cb604e4841398"
|
||||
integrity sha512-FtNWIOE00lCLXAPrRpFTOoFDD6mvDiCB8qr03NQsvFSbEuwfkvxhElaKNeKy+w0HeM8S8Tt2JqSyz9UjprgFUQ==
|
||||
"@gitlab/ui@32.2.0":
|
||||
version "32.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.2.0.tgz#cfff74221c62292bfe329274381352f2335ca493"
|
||||
integrity sha512-ASezPNr97rGmIsSbUWkmY9kDBtat/FSnErQYRx/xGYOf9KkCHzVvYV6s8536abAux7LyIIMv5iwtg2U39wEv9A==
|
||||
dependencies:
|
||||
"@babel/standalone" "^7.0.0"
|
||||
bootstrap-vue "2.18.1"
|
||||
|
|
Loading…
Reference in New Issue