Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-08-10 12:11:00 +00:00
parent 5adf6557e2
commit 63c306d960
86 changed files with 1444 additions and 425 deletions

View File

@ -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'

View File

@ -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,
};

View File

@ -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>

View File

@ -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"

View File

@ -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';

View File

@ -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.',
),
};

View File

@ -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,
},
});
},

View File

@ -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,

View File

@ -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])

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -7,7 +7,7 @@ module Mutations
field :integration,
Types::AlertManagement::HttpIntegrationType,
null: true,
description: "The HTTP integration."
description: "HTTP integration."
authorize :admin_operations

View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)
{

View File

@ -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

View File

@ -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'

View File

@ -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'

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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)

View File

@ -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?

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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),

View File

@ -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 ""

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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', () => {

View File

@ -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);
});
});
});

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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) }

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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"