Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f26f31d2fd
commit
fa7c331b76
|
|
@ -104,15 +104,20 @@ workflow:
|
|||
# If `$GITLAB_INTERNAL` isn't set, don't create a pipeline.
|
||||
- if: '$GITLAB_INTERNAL == null'
|
||||
when: never
|
||||
# For last 3 stable branches, create a pipeline with failure notifications.
|
||||
- if: '$CI_COMMIT_BRANCH =~ /^15-[6|7|8]-stable(-ee)?$/'
|
||||
variables:
|
||||
<<: *ruby2-variables
|
||||
NOTIFY_PIPELINE_FAILURE_CHANNEL: "releases"
|
||||
CREATE_INCIDENT_FOR_PIPELINE_FAILURE: "true"
|
||||
BROKEN_BRANCH_INCIDENTS_PROJECT: "gitlab-org/release/tasks"
|
||||
BROKEN_BRANCH_INCIDENTS_PROJECT_TOKEN: "${BROKEN_STABLE_INCIDENTS_PROJECT_TOKEN}"
|
||||
PIPELINE_NAME: 'Ruby 2 $CI_COMMIT_BRANCH branch pipeline'
|
||||
# For stable, auto-deploy, and security branches, create a pipeline.
|
||||
- if: '$CI_COMMIT_BRANCH =~ /^[\d-]+-stable(-ee)?$/'
|
||||
variables:
|
||||
<<: *ruby2-variables
|
||||
NOTIFY_PIPELINE_FAILURE_CHANNEL: "releases"
|
||||
PIPELINE_NAME: 'Ruby 2 $CI_COMMIT_BRANCH branch pipeline'
|
||||
CREATE_INCIDENT_FOR_PIPELINE_FAILURE: "true"
|
||||
BROKEN_BRANCH_INCIDENTS_PROJECT: "gitlab-org/release/tasks"
|
||||
BROKEN_BRANCH_INCIDENTS_PROJECT_TOKEN: "${BROKEN_STABLE_INCIDENTS_PROJECT_TOKEN}"
|
||||
- if: '$CI_COMMIT_BRANCH =~ /^\d+-\d+-auto-deploy-\d+$/'
|
||||
variables:
|
||||
<<: *ruby2-variables
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
994a537cefce6044125fa420cd7536a6efda0618
|
||||
a41c0909051714c0d6b58f8000589f82a66804a5
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const STATUS_REOPENED = 'reopened';
|
|||
|
||||
export const TITLE_LENGTH_MAX = 255;
|
||||
|
||||
export const TYPE_EPIC = 'epic';
|
||||
export const TYPE_ISSUE = 'issue';
|
||||
|
||||
export const IssuableStatusText = {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import Visibility from 'visibilityjs';
|
|||
import { createAlert } from '~/flash';
|
||||
import {
|
||||
IssuableStatusText,
|
||||
IssuableType,
|
||||
STATUS_CLOSED,
|
||||
TYPE_EPIC,
|
||||
TYPE_ISSUE,
|
||||
WorkspaceType,
|
||||
} from '~/issues/constants';
|
||||
|
|
@ -277,7 +277,7 @@ export default {
|
|||
return IssuableStatusText[this.issuableStatus];
|
||||
},
|
||||
shouldShowStickyHeader() {
|
||||
return [TYPE_ISSUE, IssuableType.Epic].includes(this.issuableType);
|
||||
return [TYPE_ISSUE, TYPE_EPIC].includes(this.issuableType);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
|
||||
import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
import { confidentialityQueries } from '~/sidebar/constants';
|
||||
import { defaultClient as gqlClient } from '~/graphql_shared/issuable_client';
|
||||
|
|
@ -28,7 +28,7 @@ export default {
|
|||
},
|
||||
},
|
||||
created() {
|
||||
if (this.issuableType !== TYPE_ISSUE && this.issuableType !== IssuableType.Epic) {
|
||||
if (this.issuableType !== TYPE_ISSUE && this.issuableType !== TYPE_EPIC) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -171,11 +171,12 @@ export const generateColumnsFromLayersListBare = ({ stages, stagesLookup }, pipe
|
|||
|
||||
export const generateColumnsFromLayersListMemoized = memoize(generateColumnsFromLayersListBare);
|
||||
|
||||
// TODO: handle REST / MR values
|
||||
// See https://gitlab.com/gitlab-org/gitlab/-/issues/367547
|
||||
export const keepLatestDownstreamPipelines = (downstreamPipelines = []) => {
|
||||
// handles GraphQL
|
||||
return downstreamPipelines.filter((pipeline) => {
|
||||
if (pipeline.source_job) {
|
||||
return !pipeline?.source_job?.retried || false;
|
||||
}
|
||||
|
||||
return !pipeline?.sourceJob?.retried || false;
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { GlTableLite, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { s__, __ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils';
|
||||
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
|
||||
import eventHub from '../../event_hub';
|
||||
import { TRACKING_CATEGORIES } from '../../constants';
|
||||
|
|
@ -115,6 +116,10 @@ export default {
|
|||
eventHub.$off('openConfirmationModal', this.setModalData);
|
||||
},
|
||||
methods: {
|
||||
getDownstreamPipelines(pipeline) {
|
||||
const downstream = pipeline.triggered;
|
||||
return keepLatestDownstreamPipelines(downstream);
|
||||
},
|
||||
setModalData(data) {
|
||||
this.pipelineId = data.pipeline.id;
|
||||
this.pipeline = data.pipeline;
|
||||
|
|
@ -171,7 +176,7 @@ export default {
|
|||
|
||||
<template #cell(stages)="{ item }">
|
||||
<pipeline-mini-graph
|
||||
:downstream-pipelines="item.triggered"
|
||||
:downstream-pipelines="getDownstreamPipelines(item)"
|
||||
:pipeline-path="item.path"
|
||||
:stages="item.details.stages"
|
||||
:update-dropdown="updateGraphDropdown"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlIcon, GlAlert, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import { IssuableType, WorkspaceType } from '~/issues/constants';
|
||||
import { TYPE_EPIC, WorkspaceType } from '~/issues/constants';
|
||||
import { confidentialityInfoText } from '~/vue_shared/constants';
|
||||
|
||||
export default {
|
||||
|
|
@ -25,7 +25,7 @@ export default {
|
|||
computed: {
|
||||
confidentialBodyText() {
|
||||
return confidentialityInfoText(
|
||||
this.issuableType === IssuableType.Epic ? WorkspaceType.group : WorkspaceType.project,
|
||||
this.issuableType === TYPE_EPIC ? WorkspaceType.group : WorkspaceType.project,
|
||||
this.issuableType,
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import issuableLabelsSubscription from 'ee_else_ce/sidebar/queries/issuable_labe
|
|||
import { MutationOperationMode, getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { createAlert } from '~/flash';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
|
||||
import { IssuableType, TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
|
||||
|
||||
import { __ } from '~/locale';
|
||||
import { issuableLabelsQueries } from '../../../constants';
|
||||
|
|
@ -269,7 +269,7 @@ export default {
|
|||
...updateVariables,
|
||||
operationMode: MutationOperationMode.Replace,
|
||||
};
|
||||
case IssuableType.Epic:
|
||||
case TYPE_EPIC:
|
||||
return {
|
||||
iid: currentIid,
|
||||
groupPath: this.fullPath,
|
||||
|
|
@ -330,7 +330,7 @@ export default {
|
|||
labelIds: [labelId],
|
||||
operationMode: MutationOperationMode.Remove,
|
||||
};
|
||||
case IssuableType.Epic:
|
||||
case TYPE_EPIC:
|
||||
return {
|
||||
iid: this.iid,
|
||||
removeLabelIds: [getIdFromGraphQLId(labelId)],
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
GlSearchBoxByType,
|
||||
} from '@gitlab/ui';
|
||||
import { kebabCase, snakeCase } from 'lodash';
|
||||
import { IssuableType, TYPE_ISSUE, WorkspaceType } from '~/issues/constants';
|
||||
import { IssuableType, TYPE_EPIC, TYPE_ISSUE, WorkspaceType } from '~/issues/constants';
|
||||
import { __ } from '~/locale';
|
||||
import {
|
||||
defaultEpicSort,
|
||||
|
|
@ -155,7 +155,7 @@ export default {
|
|||
},
|
||||
isEpic() {
|
||||
// MV to EE https://gitlab.com/gitlab-org/gitlab/-/issues/345311
|
||||
return this.issuableAttribute === IssuableType.Epic;
|
||||
return this.issuableAttribute === TYPE_EPIC;
|
||||
},
|
||||
issuableAttributeQuery() {
|
||||
return this.issuableAttributesQueries[this.issuableAttribute];
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { GlButton, GlIcon, GlLink, GlPopover, GlTooltipDirective } from '@gitlab
|
|||
import { kebabCase, snakeCase } from 'lodash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
|
||||
import { IssuableType, TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
|
||||
import { timeFor } from '~/lib/utils/datetime_utility';
|
||||
import { __ } from '~/locale';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
|
@ -153,7 +153,7 @@ export default {
|
|||
},
|
||||
isEpic() {
|
||||
// MV to EE https://gitlab.com/gitlab-org/gitlab/-/issues/345311
|
||||
return this.issuableAttribute === IssuableType.Epic;
|
||||
return this.issuableAttribute === TYPE_EPIC;
|
||||
},
|
||||
formatIssuableAttribute() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlDropdownForm, GlIcon, GlLoadingIcon, GlToggle, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { createAlert } from '~/flash';
|
||||
import { IssuableType } from '~/issues/constants';
|
||||
import { IssuableType, TYPE_EPIC } from '~/issues/constants';
|
||||
import { isLoggedIn } from '~/lib/utils/common_utils';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
|
@ -105,7 +105,7 @@ export default {
|
|||
return ICON_ON;
|
||||
},
|
||||
parentIsGroup() {
|
||||
return this.issuableType === IssuableType.Epic;
|
||||
return this.issuableType === TYPE_EPIC;
|
||||
},
|
||||
subscribeDisabledDescription() {
|
||||
return sprintf(__('Disabled by %{parent} owner'), {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { s__, __, sprintf } from '~/locale';
|
|||
import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
|
||||
import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
|
||||
import userSearchWithMRPermissionsQuery from '~/graphql_shared/queries/users_search_with_mr_permissions.graphql';
|
||||
import { IssuableType, TYPE_ISSUE, WorkspaceType } from '~/issues/constants';
|
||||
import { IssuableType, TYPE_EPIC, TYPE_ISSUE, WorkspaceType } from '~/issues/constants';
|
||||
import updateAlertAssigneesMutation from '~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql';
|
||||
import updateTestCaseLabelsMutation from './components/labels/labels_select_widget/graphql/update_test_case_labels.mutation.graphql';
|
||||
import epicLabelsQuery from './components/labels/labels_select_widget/graphql/epic_labels.query.graphql';
|
||||
|
|
@ -86,7 +86,7 @@ export const participantsQueries = {
|
|||
[IssuableType.MergeRequest]: {
|
||||
query: getMergeRequestParticipants,
|
||||
},
|
||||
[IssuableType.Epic]: {
|
||||
[TYPE_EPIC]: {
|
||||
query: epicParticipantsQuery,
|
||||
},
|
||||
[IssuableType.Alert]: {
|
||||
|
|
@ -109,7 +109,7 @@ export const confidentialityQueries = {
|
|||
query: issueConfidentialQuery,
|
||||
mutation: updateIssueConfidentialMutation,
|
||||
},
|
||||
[IssuableType.Epic]: {
|
||||
[TYPE_EPIC]: {
|
||||
query: epicConfidentialQuery,
|
||||
mutation: updateEpicConfidentialMutation,
|
||||
},
|
||||
|
|
@ -122,7 +122,7 @@ export const referenceQueries = {
|
|||
[IssuableType.MergeRequest]: {
|
||||
query: mergeRequestReferenceQuery,
|
||||
},
|
||||
[IssuableType.Epic]: {
|
||||
[TYPE_EPIC]: {
|
||||
query: epicReferenceQuery,
|
||||
},
|
||||
};
|
||||
|
|
@ -147,7 +147,7 @@ export const issuableLabelsQueries = {
|
|||
mutation: updateMergeRequestLabelsMutation,
|
||||
mutationName: 'mergeRequestSetLabels',
|
||||
},
|
||||
[IssuableType.Epic]: {
|
||||
[TYPE_EPIC]: {
|
||||
issuableQuery: epicLabelsQuery,
|
||||
mutation: updateEpicLabelsMutation,
|
||||
mutationName: 'updateEpic',
|
||||
|
|
@ -182,7 +182,7 @@ export const subscribedQueries = {
|
|||
query: issueSubscribedQuery,
|
||||
mutation: updateIssueSubscriptionMutation,
|
||||
},
|
||||
[IssuableType.Epic]: {
|
||||
[TYPE_EPIC]: {
|
||||
query: epicSubscribedQuery,
|
||||
mutation: updateEpicSubscriptionMutation,
|
||||
},
|
||||
|
|
@ -211,14 +211,14 @@ export const dueDateQueries = {
|
|||
query: issueDueDateQuery,
|
||||
mutation: updateIssueDueDateMutation,
|
||||
},
|
||||
[IssuableType.Epic]: {
|
||||
[TYPE_EPIC]: {
|
||||
query: epicDueDateQuery,
|
||||
mutation: updateEpicDueDateMutation,
|
||||
},
|
||||
};
|
||||
|
||||
export const startDateQueries = {
|
||||
[IssuableType.Epic]: {
|
||||
[TYPE_EPIC]: {
|
||||
query: epicStartDateQuery,
|
||||
mutation: updateEpicStartDateMutation,
|
||||
},
|
||||
|
|
@ -283,7 +283,7 @@ export const issuableAttributesQueries = {
|
|||
};
|
||||
|
||||
export const todoQueries = {
|
||||
[IssuableType.Epic]: {
|
||||
[TYPE_EPIC]: {
|
||||
query: epicTodoQuery,
|
||||
},
|
||||
[TYPE_ISSUE]: {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { GlBadge, GlButton, GlDisclosureDropdown, GlDisclosureDropdownGroup } from '@gitlab/ui';
|
||||
import GitlabVersionCheckBadge from '~/gitlab_version_check/components/gitlab_version_check_badge.vue';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { PROMO_URL } from 'jh_else_ce/lib/utils/url_utility';
|
||||
import { __ } from '~/locale';
|
||||
|
|
@ -11,6 +12,7 @@ export default {
|
|||
GlButton,
|
||||
GlDisclosureDropdown,
|
||||
GlDisclosureDropdownGroup,
|
||||
GitlabVersionCheckBadge,
|
||||
},
|
||||
i18n: {
|
||||
help: __('Help'),
|
||||
|
|
@ -21,6 +23,7 @@ export default {
|
|||
contribute: __('Contribute to GitLab'),
|
||||
feedback: __('Provide feedback'),
|
||||
shortcuts: __('Keyboard shortcuts'),
|
||||
version: __('Your GitLab version'),
|
||||
whatsnew: __("What's new"),
|
||||
},
|
||||
props: {
|
||||
|
|
@ -35,9 +38,18 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
items() {
|
||||
return [
|
||||
{
|
||||
itemGroups() {
|
||||
return {
|
||||
versionCheck: {
|
||||
items: [
|
||||
{
|
||||
text: this.$options.i18n.version,
|
||||
href: helpPagePath('update/index'),
|
||||
version: `${this.sidebarData.gitlab_version.major}.${this.sidebarData.gitlab_version.minor}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
helpLinks: {
|
||||
items: [
|
||||
{ text: this.$options.i18n.help, href: helpPagePath() },
|
||||
{ text: this.$options.i18n.support, href: this.sidebarData.support_path },
|
||||
|
|
@ -51,7 +63,7 @@ export default {
|
|||
{ text: this.$options.i18n.feedback, href: 'https://about.gitlab.com/submit-feedback' },
|
||||
],
|
||||
},
|
||||
{
|
||||
helpActions: {
|
||||
items: [
|
||||
{
|
||||
text: this.$options.i18n.shortcuts,
|
||||
|
|
@ -67,7 +79,10 @@ export default {
|
|||
},
|
||||
].filter(Boolean),
|
||||
},
|
||||
];
|
||||
};
|
||||
},
|
||||
updateSeverity() {
|
||||
return this.sidebarData.gitlab_version_check?.severity;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -120,8 +135,34 @@ export default {
|
|||
</gl-button>
|
||||
</template>
|
||||
|
||||
<gl-disclosure-dropdown-group :group="items[0]" />
|
||||
<gl-disclosure-dropdown-group :group="items[1]" bordered @action="handleAction">
|
||||
<gl-disclosure-dropdown-group
|
||||
v-if="sidebarData.show_version_check"
|
||||
:group="itemGroups.versionCheck"
|
||||
>
|
||||
<template #list-item="{ item }">
|
||||
<a
|
||||
:href="item.href"
|
||||
tabindex="-1"
|
||||
class="gl-display-flex gl-flex-direction-column gl-line-height-24 gl-text-gray-900 gl-hover-text-gray-900 gl-hover-text-decoration-none"
|
||||
>
|
||||
<span class="gl-font-sm gl-font-weight-bold">
|
||||
{{ item.text }}
|
||||
<gl-emoji data-name="rocket" />
|
||||
</span>
|
||||
<span>
|
||||
<span class="gl-mr-2">{{ item.version }}</span>
|
||||
<gitlab-version-check-badge v-if="updateSeverity" :status="updateSeverity" size="sm" />
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
</gl-disclosure-dropdown-group>
|
||||
|
||||
<gl-disclosure-dropdown-group
|
||||
:group="itemGroups.helpLinks"
|
||||
:bordered="sidebarData.show_version_check"
|
||||
/>
|
||||
|
||||
<gl-disclosure-dropdown-group :group="itemGroups.helpActions" bordered @action="handleAction">
|
||||
<template #list-item="{ item }">
|
||||
<button
|
||||
tabindex="-1"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
import { s__, n__ } from '~/locale';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils';
|
||||
import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue';
|
||||
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
|
|
@ -86,6 +87,10 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
downstreamPipelines() {
|
||||
const downstream = this.pipeline.triggered;
|
||||
return keepLatestDownstreamPipelines(downstream);
|
||||
},
|
||||
hasPipeline() {
|
||||
return this.pipeline && Object.keys(this.pipeline).length > 0;
|
||||
},
|
||||
|
|
@ -274,7 +279,7 @@ export default {
|
|||
<span class="gl-align-items-center gl-display-inline-flex">
|
||||
<pipeline-mini-graph
|
||||
v-if="pipeline.details.stages"
|
||||
:downstream-pipelines="pipeline.triggered"
|
||||
:downstream-pipelines="downstreamPipelines"
|
||||
:is-merge-train="isMergeTrain"
|
||||
:pipeline-path="pipeline.path"
|
||||
:stages="pipeline.details.stages"
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ module IssuableActions
|
|||
end
|
||||
|
||||
def destroy
|
||||
Issuable::DestroyService.new(project: issuable.project, current_user: current_user).execute(issuable)
|
||||
Issuable::DestroyService.new(container: issuable.project, current_user: current_user).execute(issuable)
|
||||
|
||||
name = issuable.human_class_name
|
||||
flash[:notice] = "The #{name} was successfully deleted."
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
new_project = Project.find(params[:move_to_project_id])
|
||||
return render_404 unless issue.can_move?(current_user, new_project)
|
||||
|
||||
@issue = ::Issues::MoveService.new(project: project, current_user: current_user).execute(issue, new_project)
|
||||
@issue = ::Issues::MoveService.new(container: project, current_user: current_user).execute(issue, new_project)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ module Mutations
|
|||
target_project = resolve_project(full_path: target_project_path).sync
|
||||
|
||||
begin
|
||||
moved_issue = ::Issues::MoveService.new(project: source_project, current_user: current_user).execute(issue, target_project)
|
||||
moved_issue = ::Issues::MoveService.new(container: source_project, current_user: current_user).execute(issue, target_project)
|
||||
rescue ::Issues::MoveService::MoveError => e
|
||||
errors = e.message
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ module Mutations
|
|||
work_item = authorized_find!(id: id)
|
||||
|
||||
result = ::WorkItems::DeleteService.new(
|
||||
project: work_item.project,
|
||||
container: work_item.project,
|
||||
current_user: current_user
|
||||
).execute(work_item)
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,13 @@ module Types
|
|||
field :merge_requests_count, GraphQL::Types::Int, null: false,
|
||||
description: 'Number of merge requests that close the issue on merge.',
|
||||
resolver: Resolvers::MergeRequestsCountResolver
|
||||
|
||||
field :related_merge_requests, Types::MergeRequestType.connection_type,
|
||||
null: true,
|
||||
description: 'Merge requests related to the issue. This field can only be resolved for one issue in any single request.' do
|
||||
extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
|
||||
end
|
||||
|
||||
field :relative_position, GraphQL::Types::Int, null: true,
|
||||
description: 'Relative position of the issue (used for positioning in epic tree and issue boards).'
|
||||
field :upvotes, GraphQL::Types::Int,
|
||||
|
|
@ -180,6 +187,17 @@ module Types
|
|||
Gitlab::Graphql::Loaders::BatchModelLoader.new(Issue, object.duplicated_to_id).find
|
||||
end
|
||||
|
||||
def related_merge_requests
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
MergeRequest.where(
|
||||
id: ::Issues::ReferencedMergeRequestsService.new(project: object.project, current_user: current_user)
|
||||
.execute(object)
|
||||
.first
|
||||
.map(&:id)
|
||||
)
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
def discussion_locked
|
||||
!!object.discussion_locked
|
||||
end
|
||||
|
|
|
|||
|
|
@ -48,7 +48,10 @@ module SidebarsHelper
|
|||
support_path: support_url,
|
||||
display_whats_new: display_whats_new?,
|
||||
whats_new_most_recent_release_items_count: whats_new_most_recent_release_items_count,
|
||||
whats_new_version_digest: whats_new_version_digest
|
||||
whats_new_version_digest: whats_new_version_digest,
|
||||
show_version_check: show_version_check?,
|
||||
gitlab_version: Gitlab.version_info,
|
||||
gitlab_version_check: gitlab_version_check
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
module WebHooks
|
||||
module WebHooksHelper
|
||||
EXPIRY_TTL = 1.hour
|
||||
|
||||
def show_project_hook_failed_callout?(project:)
|
||||
return false if project_hook_page?
|
||||
return false unless current_user
|
||||
|
|
@ -12,17 +10,11 @@ module WebHooks
|
|||
# Assumes include of Users::CalloutsHelper
|
||||
return false if web_hook_disabled_dismissed?(project)
|
||||
|
||||
any_project_hook_failed?(project) # Most expensive query last
|
||||
project.fetch_web_hook_failure
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def any_project_hook_failed?(project)
|
||||
Rails.cache.fetch("any_web_hook_failed:#{project.id}", expires_in: EXPIRY_TTL) do
|
||||
ProjectHook.for_projects(project).disabled.exists?
|
||||
end
|
||||
end
|
||||
|
||||
def project_hook_page?
|
||||
current_controller?('projects/hooks') || current_controller?('projects/hook_logs')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WebHooks
|
||||
module HasWebHooks
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
WEB_HOOK_CACHE_EXPIRY = 1.hour
|
||||
|
||||
def any_hook_failed?
|
||||
hooks.disabled.exists?
|
||||
end
|
||||
|
||||
def web_hook_failure_redis_key
|
||||
"any_web_hook_failed:#{id}"
|
||||
end
|
||||
|
||||
def last_failure_redis_key
|
||||
"web_hooks:last_failure:project-#{id}"
|
||||
end
|
||||
|
||||
def get_web_hook_failure
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
current = redis.get(web_hook_failure_redis_key)
|
||||
|
||||
Gitlab::Utils.to_boolean(current) if current
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_web_hook_failure
|
||||
Gitlab::Redis::SharedState.with do |_redis|
|
||||
current = get_web_hook_failure
|
||||
next current unless current.nil?
|
||||
|
||||
cache_web_hook_failure
|
||||
end
|
||||
end
|
||||
|
||||
def cache_web_hook_failure(state = any_hook_failed?)
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.set(web_hook_failure_redis_key, state.to_s, ex: WEB_HOOK_CACHE_EXPIRY)
|
||||
|
||||
state
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -46,14 +46,18 @@ class ProjectHook < WebHook
|
|||
|
||||
override :update_last_failure
|
||||
def update_last_failure
|
||||
return if executable?
|
||||
if executable?
|
||||
project.cache_web_hook_failure if project.get_web_hook_failure # may need update
|
||||
else
|
||||
project.cache_web_hook_failure(true) # definitely failing, no need to check
|
||||
|
||||
key = "web_hooks:last_failure:project-#{project_id}"
|
||||
time = Time.current.utc.iso8601
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
last_failure_key = project.last_failure_redis_key
|
||||
time = Time.current.utc.iso8601
|
||||
prev = redis.get(last_failure_key)
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
prev = redis.get(key)
|
||||
redis.set(key, time) if !prev || prev < time
|
||||
redis.set(last_failure_key, time) if !prev || prev < time
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ class Project < ApplicationRecord
|
|||
include BlocksUnsafeSerialization
|
||||
include Subquery
|
||||
include IssueParent
|
||||
include WebHooks::HasWebHooks
|
||||
|
||||
extend Gitlab::Cache::RequestCache
|
||||
extend Gitlab::Utils::Override
|
||||
|
|
|
|||
|
|
@ -872,10 +872,24 @@ class Repository
|
|||
end
|
||||
|
||||
def merge(user, source_sha, merge_request, message)
|
||||
merge_to_branch(user,
|
||||
source_sha: source_sha,
|
||||
target_branch: merge_request.target_branch,
|
||||
message: message) do |commit_id|
|
||||
merge_request.update_and_mark_in_progress_merge_commit_sha(commit_id)
|
||||
nil # Return value does not matter.
|
||||
end
|
||||
end
|
||||
|
||||
def merge_to_branch(user, source_sha:, target_branch:, message:, target_sha: nil)
|
||||
with_cache_hooks do
|
||||
raw_repository.merge(user, source_sha, merge_request.target_branch, message) do |commit_id|
|
||||
merge_request.update_and_mark_in_progress_merge_commit_sha(commit_id)
|
||||
nil # Return value does not matter.
|
||||
raw_repository.merge(user,
|
||||
source_sha: source_sha,
|
||||
target_branch: target_branch,
|
||||
message: message,
|
||||
target_sha: target_sha
|
||||
) do |commit_id|
|
||||
yield commit_id if block_given?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -884,13 +898,19 @@ class Repository
|
|||
raw.delete_refs(...)
|
||||
end
|
||||
|
||||
def ff_merge(user, source, target_branch, merge_request: nil)
|
||||
def ff_merge(user, source, target_branch, target_sha: nil, merge_request: nil)
|
||||
their_commit_id = commit(source)&.id
|
||||
raise 'Invalid merge source' if their_commit_id.nil?
|
||||
|
||||
merge_request&.update_and_mark_in_progress_merge_commit_sha(their_commit_id)
|
||||
|
||||
with_cache_hooks { raw.ff_merge(user, their_commit_id, target_branch) }
|
||||
with_cache_hooks do
|
||||
raw.ff_merge(user,
|
||||
source_sha: their_commit_id,
|
||||
target_branch: target_branch,
|
||||
target_sha: target_sha
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def revert(
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class UserSyncedAttributesMetadata < ApplicationRecord
|
|||
def read_only_attributes
|
||||
return [] unless sync_profile_from_provider?
|
||||
|
||||
SYNCABLE_ATTRIBUTES.select { |key| synced?(key) }
|
||||
self.class.syncable_attributes.select { |key| synced?(key) }
|
||||
end
|
||||
|
||||
def synced?(attribute)
|
||||
|
|
@ -25,6 +25,20 @@ class UserSyncedAttributesMetadata < ApplicationRecord
|
|||
write_attribute("#{attribute}_synced", value)
|
||||
end
|
||||
|
||||
class << self
|
||||
def syncable_attributes
|
||||
return SYNCABLE_ATTRIBUTES if sync_name?
|
||||
|
||||
SYNCABLE_ATTRIBUTES - %i[name]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sync_name?
|
||||
Gitlab.config.ldap.sync_name
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sync_profile_from_provider?
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ class TriggeredPipelineEntity < Grape::Entity
|
|||
expose :name do |pipeline|
|
||||
pipeline.source_job&.name
|
||||
end
|
||||
expose :retried do |pipeline|
|
||||
pipeline.source_job&.retried
|
||||
end
|
||||
end
|
||||
|
||||
expose :path do |pipeline|
|
||||
|
|
|
|||
|
|
@ -128,9 +128,9 @@ module DesignManagement
|
|||
|
||||
target_repository.raw.merge(
|
||||
git_user,
|
||||
source_sha,
|
||||
merge_branch,
|
||||
'CopyDesignCollectionService finalize merge'
|
||||
source_sha: source_sha,
|
||||
target_branch: merge_branch,
|
||||
message: 'CopyDesignCollectionService finalize merge'
|
||||
) { nil }
|
||||
|
||||
target_design_collection.end_copy!
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ module Issuable
|
|||
|
||||
alias_method :old_project, :project
|
||||
|
||||
# TODO: this is to be removed once we get to rename the IssuableBaseService project param to container
|
||||
def initialize(container:, current_user: nil, params: {})
|
||||
super(project: container, current_user: current_user, params: params)
|
||||
end
|
||||
|
||||
def execute(original_entity, target_parent)
|
||||
@original_entity = original_entity
|
||||
@target_parent = target_parent
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
module Issuable
|
||||
class DestroyService < IssuableBaseService
|
||||
# TODO: this is to be removed once we get to rename the IssuableBaseService project param to container
|
||||
def initialize(container:, current_user: nil, params: {})
|
||||
super(project: container, current_user: current_user, params: params)
|
||||
end
|
||||
|
||||
def execute(issuable)
|
||||
after_destroy(issuable) if issuable.destroy
|
||||
end
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ module Issues
|
|||
target_project != issue.project
|
||||
|
||||
update(issue)
|
||||
Issues::MoveService.new(project: project, current_user: current_user).execute(issue, target_project)
|
||||
Issues::MoveService.new(container: project, current_user: current_user).execute(issue, target_project)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -139,7 +139,7 @@ module Issues
|
|||
|
||||
# we've pre-empted this from running in #execute, so let's go ahead and update the Issue now.
|
||||
update(issue)
|
||||
Issues::CloneService.new(project: project, current_user: current_user).execute(issue, target_project, with_notes: with_notes)
|
||||
Issues::CloneService.new(container: project, current_user: current_user).execute(issue, target_project, with_notes: with_notes)
|
||||
end
|
||||
|
||||
def create_merge_request_from_quick_action
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ module TasksToBeDone
|
|||
class BaseService < ::IssuableBaseService
|
||||
LABEL_PREFIX = 'tasks to be done'
|
||||
|
||||
def initialize(project:, current_user:, assignee_ids: [])
|
||||
def initialize(container:, current_user:, assignee_ids: [])
|
||||
params = {
|
||||
assignee_ids: assignee_ids,
|
||||
title: title,
|
||||
description: description,
|
||||
add_labels: label_name
|
||||
}
|
||||
super(project: project, current_user: current_user, params: params)
|
||||
super(project: container, current_user: current_user, params: params)
|
||||
end
|
||||
|
||||
def execute
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ module WorkItems
|
|||
break ::ServiceResponse.error(message: replacement_result.errors, http_status: 422) if replacement_result.error?
|
||||
|
||||
delete_result = ::WorkItems::DeleteService.new(
|
||||
project: @task.project,
|
||||
container: @task.project,
|
||||
current_user: @current_user
|
||||
).execute(@task)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
.form-check.gl-mb-3
|
||||
= check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input'
|
||||
= label_tag :only_protected_branches, _('Mirror only protected branches'), class: 'form-check-label'
|
||||
.form-text.text-muted
|
||||
= _('If enabled, only protected branches will be mirrored.')
|
||||
= link_to _('Learn more.'), help_page_path('user/project/repository/mirror/index.md', anchor: 'mirror-only-protected-branches'), target: '_blank', rel: 'noopener noreferrer'
|
||||
|
|
@ -27,12 +27,7 @@
|
|||
|
||||
= render 'projects/mirrors/mirror_repos_form', f: f
|
||||
|
||||
.form-check.gl-mb-3
|
||||
= check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input'
|
||||
= label_tag :only_protected_branches, _('Mirror only protected branches'), class: 'form-check-label'
|
||||
.form-text.text-muted
|
||||
= _('If enabled, only protected branches will be mirrored.')
|
||||
= link_to _('Learn more.'), help_page_path('user/project/repository/mirror/index.md', anchor: 'mirror-only-protected-branches'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= render 'projects/mirrors/branch_filter'
|
||||
|
||||
.panel-footer
|
||||
= f.submit _('Mirror repository'), class: 'js-mirror-submit', name: :update_remote_mirror, pajamas_button: true, data: { qa_selector: 'mirror_repository_button' }
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@
|
|||
- @project.remote_mirrors.each_with_index do |mirror, index|
|
||||
- next if mirror.new_record?
|
||||
%tr.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?), data: { qa_selector: 'mirrored_repository_row_container' } }
|
||||
%td{ data: { qa_selector: 'mirror_repository_url_content' } }= mirror.safe_url || _('Invalid URL')
|
||||
%td{ data: { qa_selector: 'mirror_repository_url_content' } }
|
||||
= mirror.safe_url || _('Invalid URL')
|
||||
= render_if_exists 'projects/mirrors/mirror_branches_setting_badge', record: mirror
|
||||
%td= _('Push')
|
||||
%td
|
||||
= mirror.last_update_started_at.present? ? time_ago_with_tooltip(mirror.last_update_started_at) : _('Never')
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
= rm_f.hidden_field :enabled, value: '1'
|
||||
= rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+"
|
||||
= rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden'
|
||||
= render_if_exists partial: 'projects/mirrors/branch_name_regex', locals: { f: rm_f }
|
||||
= rm_f.hidden_field :keep_divergent_refs, class: 'js-mirror-keep-divergent-refs-hidden'
|
||||
= render partial: 'projects/mirrors/ssh_host_keys', locals: { f: rm_f }
|
||||
= render partial: 'projects/mirrors/authentication_method', locals: { f: rm_f }
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ module TasksToBeDone
|
|||
|
||||
member_task.tasks_to_be_done.each do |task|
|
||||
service_class(task)
|
||||
.new(project: project, current_user: current_user, assignee_ids: assignee_ids)
|
||||
.new(container: project, current_user: current_user, assignee_ids: assignee_ids)
|
||||
.execute
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ end
|
|||
|
||||
# backwards compatibility, we only have one host
|
||||
if Settings.ldap['enabled'] || Rails.env.test?
|
||||
Settings.ldap['sync_name'] = true if Settings.ldap['sync_name'].nil?
|
||||
if Settings.ldap['host'].present?
|
||||
# We detected old LDAP configuration syntax. Update the config to make it
|
||||
# look like it was entered with the new syntax.
|
||||
|
|
|
|||
|
|
@ -8,57 +8,11 @@ end
|
|||
# This overrides the display name for Ukraine to 'Ukraine (except the Crimea, Donetsk, and Luhansk regions)'
|
||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/374946
|
||||
# To be removed after https://gitlab.com/gitlab-org/gitlab/issues/14784 is implemented
|
||||
# Data fetched is based on https://github.com/countries/countries/blob/master/lib/countries/data/countries/UA.yaml
|
||||
ISO3166::Data.register(
|
||||
continent: "Europe",
|
||||
address_format: "|-
|
||||
{{recipient}}
|
||||
{{street}}
|
||||
{{city}} {{region_short}}
|
||||
{{postalcode}}
|
||||
{{country}}",
|
||||
alpha2: "UA",
|
||||
alpha3: "UKR",
|
||||
country_code: '380',
|
||||
international_prefix: '810',
|
||||
ioc: "UKR",
|
||||
gec: "UP",
|
||||
name: "Ukraine (except the Crimea, Donetsk, and Luhansk regions)",
|
||||
national_destination_code_lengths: [2],
|
||||
national_number_lengths: [8, 9],
|
||||
national_prefix: '8',
|
||||
number: '804',
|
||||
region: "Europe",
|
||||
subregion: "Eastern Europe",
|
||||
world_region: "EMEA",
|
||||
un_locode: "UA",
|
||||
nationality: "Ukrainian",
|
||||
vat_rates: {
|
||||
standard: 20
|
||||
},
|
||||
reduced: [7],
|
||||
super_reduced: {
|
||||
parking: { postal_code: true }
|
||||
},
|
||||
unofficial_names: %w(Ukraine Ucrania ウクライナ Oekraïne Украина Україна Украіна),
|
||||
languages_official: ["uk"],
|
||||
languages_spoken: ["uk"],
|
||||
geo: {
|
||||
latitude: 48.379433,
|
||||
latitude_dec: '48.92656326293945',
|
||||
longitude: 31.16558,
|
||||
longitude_dec: '31.47578239440918',
|
||||
max_latitude: 52.37958099999999,
|
||||
max_longitude: 40.2285809,
|
||||
min_latitude: 44.2924,
|
||||
min_longitude: 22.137159,
|
||||
bounds: {
|
||||
northeast: { lat: 52.37958099999999, lng: 40.2285809 },
|
||||
southwest: { lat: 44.2924, lng: 22.137159 }
|
||||
}
|
||||
},
|
||||
currency_code: "UAH",
|
||||
start_of_week: "monday"
|
||||
ISO3166::Data.new('UA')
|
||||
.call
|
||||
.deep_symbolize_keys
|
||||
.merge({ name: 'Ukraine (except the Crimea, Donetsk, and Luhansk regions)' })
|
||||
)
|
||||
|
||||
# Updating the display name of Taiwan, from `Taiwan, Province of China` to `Taiwan`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
- title: "External field in GraphQL ReleaseAssetLink type"
|
||||
announcement_milestone: "15.9"
|
||||
announcement_date: "2023-02-22"
|
||||
removal_milestone: "16.0"
|
||||
removal_date: "2023-05-22"
|
||||
breaking_change: true
|
||||
reporter: ahmed.hemdan
|
||||
body: |
|
||||
In the [GraphQL API](https://docs.gitlab.com/ee/api/graphql), the `external` field of [`ReleaseAssetLink` type](https://docs.gitlab.com/ee/api/graphql/reference/index.html#releaseassetlink) was used to indicate whether a [release link](https://docs.gitlab.com/ee/user/project/releases/release_fields.html#links) is internal or external to your GitLab instance.
|
||||
As of GitLab 15.9, we treat all release links as external, and therefore, this field is deprecated in GitLab 15.9, and will be removed in GitLab 16.0.
|
||||
To avoid any disruptions to your workflow, please stop using the `external` field because it will be removed and will not be replaced.
|
||||
stage: Release
|
||||
issue_url:
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RecountEpicCacheCountsV3 < Gitlab::Database::Migration[2.1]
|
||||
MIGRATION = 'ThirdRecountEpicCacheCounts'
|
||||
DELAY_INTERVAL = 2.minutes.to_i
|
||||
BATCH_SIZE = 200
|
||||
MAX_BATCH_SIZE = 1000
|
||||
SUB_BATCH_SIZE = 20
|
||||
|
||||
disable_ddl_transaction!
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:epics,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
max_batch_size: MAX_BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE,
|
||||
gitlab_schema: :gitlab_main
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :epics, :id, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
48c3039b24ab063a550419d3883b3c6308709e0ef9eacc0f4f1cdc3c99fb4148
|
||||
|
|
@ -13263,6 +13263,7 @@ Relationship between an epic and an issue.
|
|||
| <a id="epicissuenotes"></a>`notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="epicissueparticipants"></a>`participants` | [`UserCoreConnection`](#usercoreconnection) | List of participants in the issue. (see [Connections](#connections)) |
|
||||
| <a id="epicissueprojectid"></a>`projectId` | [`Int!`](#int) | ID of the issue project. |
|
||||
| <a id="epicissuerelatedmergerequests"></a>`relatedMergeRequests` | [`MergeRequestConnection`](#mergerequestconnection) | Merge requests related to the issue. This field can only be resolved for one issue in any single request. (see [Connections](#connections)) |
|
||||
| <a id="epicissuerelatedvulnerabilities"></a>`relatedVulnerabilities` | [`VulnerabilityConnection`](#vulnerabilityconnection) | Related vulnerabilities of the issue. (see [Connections](#connections)) |
|
||||
| <a id="epicissuerelationpath"></a>`relationPath` | [`String`](#string) | URI path of the epic-issue relation. |
|
||||
| <a id="epicissuerelativeposition"></a>`relativePosition` | [`Int`](#int) | Relative position of the issue (used for positioning in epic tree and issue boards). |
|
||||
|
|
@ -15009,6 +15010,7 @@ Describes an issuable resource link for incident issues.
|
|||
| <a id="issuenotes"></a>`notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="issueparticipants"></a>`participants` | [`UserCoreConnection`](#usercoreconnection) | List of participants in the issue. (see [Connections](#connections)) |
|
||||
| <a id="issueprojectid"></a>`projectId` | [`Int!`](#int) | ID of the issue project. |
|
||||
| <a id="issuerelatedmergerequests"></a>`relatedMergeRequests` | [`MergeRequestConnection`](#mergerequestconnection) | Merge requests related to the issue. This field can only be resolved for one issue in any single request. (see [Connections](#connections)) |
|
||||
| <a id="issuerelatedvulnerabilities"></a>`relatedVulnerabilities` | [`VulnerabilityConnection`](#vulnerabilityconnection) | Related vulnerabilities of the issue. (see [Connections](#connections)) |
|
||||
| <a id="issuerelativeposition"></a>`relativePosition` | [`Int`](#int) | Relative position of the issue (used for positioning in epic tree and issue boards). |
|
||||
| <a id="issueseverity"></a>`severity` | [`IssuableSeverity`](#issuableseverity) | Severity level of the incident. |
|
||||
|
|
|
|||
|
|
@ -161,6 +161,22 @@ During the transition to the GitLab Observability UI, we will migrate the [GitLa
|
|||
|
||||
<div class="deprecation removal-160 breaking-change">
|
||||
|
||||
### External field in GraphQL ReleaseAssetLink type
|
||||
|
||||
Planned removal: GitLab <span class="removal-milestone">16.0</span> <span class="removal-date"></span>
|
||||
|
||||
WARNING:
|
||||
This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
|
||||
Review the details carefully before upgrading.
|
||||
|
||||
In the [GraphQL API](https://docs.gitlab.com/ee/api/graphql), the `external` field of [`ReleaseAssetLink` type](https://docs.gitlab.com/ee/api/graphql/reference/index.html#releaseassetlink) was used to indicate whether a [release link](https://docs.gitlab.com/ee/user/project/releases/release_fields.html#links) is internal or external to your GitLab instance.
|
||||
As of GitLab 15.9, we treat all release links as external, and therefore, this field is deprecated in GitLab 15.9, and will be removed in GitLab 16.0.
|
||||
To avoid any disruptions to your workflow, please stop using the `external` field because it will be removed and will not be replaced.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="deprecation removal-160 breaking-change">
|
||||
|
||||
### External field in Releases and Release Links APIs
|
||||
|
||||
Planned removal: GitLab <span class="removal-milestone">16.0</span> <span class="removal-date"></span>
|
||||
|
|
|
|||
|
|
@ -86,9 +86,7 @@ To disable these notifications:
|
|||
|
||||
### Custom additional text in deactivation emails **(FREE SELF)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/355964) in GitLab 15.9
|
||||
[with a flag](../../../administration/feature_flags.md) named `deactivation_email_additional_text`.
|
||||
Disabled by default.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/355964) in GitLab 15.9 [with a flag](../../../administration/feature_flags.md) named `deactivation_email_additional_text`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available, ask an
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ To do it:
|
|||
|
||||
issues.each do |issue|
|
||||
if issue.state != "closed" && issue.moved_to.nil?
|
||||
Issues::MoveService.new(project: project, current_user: admin_user).execute(issue, target_project)
|
||||
Issues::MoveService.new(container: project, current_user: admin_user).execute(issue, target_project)
|
||||
else
|
||||
puts "issue with id: #{issue.id} and title: #{issue.title} was not moved"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -378,5 +378,5 @@ with a backup of the instance ready to be restored, just in case.
|
|||
u = User.find_by_username('<username>')
|
||||
p = Project.find_by_full_path('<namespace/project>')
|
||||
m = p.merge_requests.find_by(iid: <iid>)
|
||||
Issuable::DestroyService.new(project: m.project, current_user: u).execute(m)
|
||||
Issuable::DestroyService.new(container: m.project, current_user: u).execute(m)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ module API
|
|||
not_found!('Project') unless new_project
|
||||
|
||||
begin
|
||||
issue = ::Issues::MoveService.new(project: user_project, current_user: current_user).execute(issue, new_project)
|
||||
issue = ::Issues::MoveService.new(container: user_project, current_user: current_user).execute(issue, new_project)
|
||||
present issue, with: Entities::Issue, current_user: current_user, project: user_project
|
||||
rescue ::Issues::MoveService::MoveError => error
|
||||
render_api_error!(error.message, 400)
|
||||
|
|
@ -403,7 +403,7 @@ module API
|
|||
not_found!('Project') unless target_project
|
||||
|
||||
begin
|
||||
issue = ::Issues::CloneService.new(project: user_project, current_user: current_user)
|
||||
issue = ::Issues::CloneService.new(container: user_project, current_user: current_user)
|
||||
.execute(issue, target_project, with_notes: params[:with_notes])
|
||||
present issue, with: Entities::Issue, current_user: current_user, project: target_project
|
||||
rescue ::Issues::CloneService::CloneError => error
|
||||
|
|
@ -424,7 +424,7 @@ module API
|
|||
authorize!(:destroy_issue, issue)
|
||||
|
||||
destroy_conditionally!(issue) do |issue|
|
||||
Issuable::DestroyService.new(project: user_project, current_user: current_user).execute(issue)
|
||||
Issuable::DestroyService.new(container: user_project, current_user: current_user).execute(issue)
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ module API
|
|||
authorize!(:destroy_merge_request, merge_request)
|
||||
|
||||
destroy_conditionally!(merge_request) do |merge_request|
|
||||
Issuable::DestroyService.new(project: user_project, current_user: current_user).execute(merge_request)
|
||||
Issuable::DestroyService.new(container: user_project, current_user: current_user).execute(merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ module Gitlab
|
|||
metadata = gl_user.build_user_synced_attributes_metadata
|
||||
|
||||
if sync_profile_from_provider?
|
||||
UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES.each do |key|
|
||||
UserSyncedAttributesMetadata.syncable_attributes.each do |key|
|
||||
if auth_hash.has_attribute?(key) && gl_user.sync_attribute?(key)
|
||||
gl_user.public_send("#{key}=".to_sym, auth_hash.public_send(key)) # rubocop:disable GitlabSecurity/PublicSend
|
||||
metadata.set_attribute_synced(key, true)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# rubocop: disable Style/Documentation
|
||||
class ThirdRecountEpicCacheCounts < Gitlab::BackgroundMigration::BatchedMigrationJob
|
||||
feature_category :database
|
||||
|
||||
def perform; end
|
||||
end
|
||||
# rubocop: enable Style/Documentation
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop: disable Layout/LineLength
|
||||
# we just want to re-enqueue the previous BackfillEpicCacheCounts migration,
|
||||
# because it's a EE-only migation and it's a module, we just prepend new
|
||||
# RecountEpicCacheCounts with existing batched migration module (which is same in both cases)
|
||||
Gitlab::BackgroundMigration::ThirdRecountEpicCacheCounts.prepend_mod_with('Gitlab::BackgroundMigration::BackfillEpicCacheCounts')
|
||||
# rubocop: enable Layout/LineLength
|
||||
|
|
@ -652,15 +652,23 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def merge(user, source_sha, target_branch, message, &block)
|
||||
def merge(user, source_sha:, target_branch:, message:, target_sha: nil, &block)
|
||||
wrapped_gitaly_errors do
|
||||
gitaly_operation_client.user_merge_branch(user, source_sha, target_branch, message, &block)
|
||||
gitaly_operation_client.user_merge_branch(user,
|
||||
source_sha: source_sha,
|
||||
target_branch: target_branch,
|
||||
message: message,
|
||||
target_sha: target_sha,
|
||||
&block)
|
||||
end
|
||||
end
|
||||
|
||||
def ff_merge(user, source_sha, target_branch)
|
||||
def ff_merge(user, source_sha:, target_branch:, target_sha: nil)
|
||||
wrapped_gitaly_errors do
|
||||
gitaly_operation_client.user_ff_branch(user, source_sha, target_branch)
|
||||
gitaly_operation_client.user_ff_branch(user,
|
||||
source_sha: source_sha,
|
||||
target_branch: target_branch,
|
||||
target_sha: target_sha)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ module Gitlab
|
|||
response.commit_id
|
||||
end
|
||||
|
||||
def user_merge_branch(user, source_sha, target_branch, message)
|
||||
def user_merge_branch(user, source_sha:, target_branch:, message:, target_sha: nil)
|
||||
request_enum = QueueEnumerator.new
|
||||
response_enum = gitaly_client_call(
|
||||
@repository.storage,
|
||||
|
|
@ -168,6 +168,7 @@ module Gitlab
|
|||
user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
|
||||
commit_id: source_sha,
|
||||
branch: encode_binary(target_branch),
|
||||
expected_old_oid: target_sha,
|
||||
message: encode_binary(message),
|
||||
timestamp: Google::Protobuf::Timestamp.new(seconds: Time.now.utc.to_i)
|
||||
)
|
||||
|
|
@ -184,7 +185,6 @@ module Gitlab
|
|||
raise Gitlab::Git::CommitError, 'failed to apply merge to branch' unless branch_update.commit_id.present?
|
||||
|
||||
Gitlab::Git::OperationService::BranchUpdate.from_gitaly(branch_update)
|
||||
|
||||
rescue GRPC::BadStatus => e
|
||||
detailed_error = GitalyClient.decode_detailed_error(e)
|
||||
|
||||
|
|
@ -207,12 +207,13 @@ module Gitlab
|
|||
request_enum.close
|
||||
end
|
||||
|
||||
def user_ff_branch(user, source_sha, target_branch)
|
||||
def user_ff_branch(user, source_sha:, target_branch:, target_sha: nil)
|
||||
request = Gitaly::UserFFBranchRequest.new(
|
||||
repository: @gitaly_repo,
|
||||
user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
|
||||
commit_id: source_sha,
|
||||
branch: encode_binary(target_branch)
|
||||
branch: encode_binary(target_branch),
|
||||
expected_old_oid: target_sha
|
||||
)
|
||||
|
||||
response = gitaly_client_call(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,16 @@
|
|||
|
||||
module Gitlab
|
||||
module Redis
|
||||
# Match signature in
|
||||
# https://github.com/rails/rails/blob/v6.1.7.2/activesupport/lib/active_support/cache/redis_cache_store.rb#L59
|
||||
ERROR_HANDLER = ->(method:, returning:, exception:) do
|
||||
Gitlab::ErrorTracking.log_exception(
|
||||
exception,
|
||||
method: method,
|
||||
returning: returning.inspect
|
||||
)
|
||||
end
|
||||
|
||||
class Cache < ::Gitlab::Redis::Wrapper
|
||||
CACHE_NAMESPACE = 'cache:gitlab'
|
||||
|
||||
|
|
@ -12,7 +22,8 @@ module Gitlab
|
|||
redis: pool,
|
||||
compress: Gitlab::Utils.to_boolean(ENV.fetch('ENABLE_REDIS_CACHE_COMPRESSION', '1')),
|
||||
namespace: CACHE_NAMESPACE,
|
||||
expires_in: default_ttl_seconds
|
||||
expires_in: default_ttl_seconds,
|
||||
error_handler: ::Gitlab::Redis::ERROR_HANDLER
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ module Gitlab
|
|||
end
|
||||
|
||||
def cache_store
|
||||
@cache_store ||= ActiveSupport::Cache::RedisCacheStore.new(redis: pool, namespace: Cache::CACHE_NAMESPACE)
|
||||
@cache_store ||= ActiveSupport::Cache::RedisCacheStore.new(
|
||||
redis: pool,
|
||||
namespace: Cache::CACHE_NAMESPACE,
|
||||
error_handler: ::Gitlab::Redis::ERROR_HANDLER
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ module Gitlab
|
|||
redis: pool,
|
||||
compress: Gitlab::Utils.to_boolean(ENV.fetch('ENABLE_REDIS_CACHE_COMPRESSION', '1')),
|
||||
namespace: Cache::CACHE_NAMESPACE,
|
||||
expires_in: Cache.default_ttl_seconds
|
||||
expires_in: Cache.default_ttl_seconds,
|
||||
error_handler: ::Gitlab::Redis::ERROR_HANDLER
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ module Gitlab
|
|||
return Gitlab::SlashCommands::Presenters::Access.new.not_found
|
||||
end
|
||||
|
||||
new_issue = ::Issues::MoveService.new(project: project, current_user: current_user)
|
||||
new_issue = ::Issues::MoveService.new(container: project, current_user: current_user)
|
||||
.execute(old_issue, target_project)
|
||||
|
||||
presenter(new_issue).present(old_issue)
|
||||
|
|
|
|||
|
|
@ -4107,6 +4107,9 @@ msgstr ""
|
|||
msgid "All Members"
|
||||
msgstr ""
|
||||
|
||||
msgid "All branch names must match %{link_start}this regular expression%{link_end}. If empty, any branch name is allowed."
|
||||
msgstr ""
|
||||
|
||||
msgid "All branches"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -8740,6 +8743,9 @@ msgstr ""
|
|||
msgid "Choose which Git strategy to use when fetching the project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose which branches should be mirrored"
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -16773,6 +16779,9 @@ msgstr ""
|
|||
msgid "Exactly one of %{attributes} is required"
|
||||
msgstr ""
|
||||
|
||||
msgid "Example"
|
||||
msgstr ""
|
||||
|
||||
msgid "Example: (feature|hotfix)\\/*"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -21088,6 +21097,9 @@ msgstr ""
|
|||
msgid "IdentityVerification|For added security, you'll need to verify your identity in a few quick steps."
|
||||
msgstr ""
|
||||
|
||||
msgid "IdentityVerification|For added security, you'll need to verify your identity."
|
||||
msgstr ""
|
||||
|
||||
msgid "IdentityVerification|For added security, you'll need to verify your identity. We've sent a verification code to %{email}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -21187,6 +21199,9 @@ msgstr ""
|
|||
msgid "IdentityVerification|We sent a new code to +%{phoneNumber}"
|
||||
msgstr ""
|
||||
|
||||
msgid "IdentityVerification|We've sent a verification code to %{email}"
|
||||
msgstr ""
|
||||
|
||||
msgid "IdentityVerification|We've sent a verification code to +%{phoneNumber}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -21235,6 +21250,9 @@ msgstr ""
|
|||
msgid "If disabled, only administrators can configure repository mirroring."
|
||||
msgstr ""
|
||||
|
||||
msgid "If enabled, all branches will be mirrored."
|
||||
msgstr ""
|
||||
|
||||
msgid "If enabled, only protected branches will be mirrored."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -27329,6 +27347,12 @@ msgstr ""
|
|||
msgid "Minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Mirror all branches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Mirror branches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Mirror direction"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -27341,6 +27365,9 @@ msgstr ""
|
|||
msgid "Mirror settings are only available to GitLab administrators."
|
||||
msgstr ""
|
||||
|
||||
msgid "Mirror specific branches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Mirror user"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -29481,9 +29508,6 @@ msgstr ""
|
|||
msgid "OnDemandScans|Unable to fetch runner tags. Try reloading the page."
|
||||
msgstr ""
|
||||
|
||||
msgid "OnDemandScans|Verify"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnDemandScans|Verify configuration"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -38614,6 +38638,9 @@ msgstr ""
|
|||
msgid "SecurityReports|Activity"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|Add comment and dismiss"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|Add or remove projects to monitor in the security area. Projects included in this list will have their results displayed in the security dashboard and vulnerability report."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -40781,6 +40808,9 @@ msgstr ""
|
|||
msgid "Spam log successfully submitted as ham."
|
||||
msgstr ""
|
||||
|
||||
msgid "Specific branches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Specified URL cannot be used: \"%{reason}\""
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -49362,6 +49392,9 @@ msgstr ""
|
|||
msgid "Your GitLab instance allows anyone to register for an account, which is a security risk on public-facing GitLab instances. You should deactivate new sign ups if public users aren't expected to register for an account."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your GitLab version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Groups"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :p
|
|||
|
||||
before do
|
||||
stub_feature_flags(branch_rules: false)
|
||||
stub_feature_flags(mirror_only_branches_match_regex: false)
|
||||
project.add_role(user, role)
|
||||
sign_in(user)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const downstream = {
|
||||
export const mockDownstreamPipelinesGraphql = ({ includeSourceJobRetried = true } = {}) => ({
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/Ci::Pipeline/612',
|
||||
|
|
@ -17,7 +17,7 @@ const downstream = {
|
|||
},
|
||||
sourceJob: {
|
||||
id: 'gid://gitlab/Ci::Bridge/532',
|
||||
retried: false,
|
||||
retried: includeSourceJobRetried ? false : null,
|
||||
},
|
||||
__typename: 'Pipeline',
|
||||
},
|
||||
|
|
@ -38,7 +38,7 @@ const downstream = {
|
|||
},
|
||||
sourceJob: {
|
||||
id: 'gid://gitlab/Ci::Bridge/531',
|
||||
retried: true,
|
||||
retried: includeSourceJobRetried ? true : null,
|
||||
},
|
||||
__typename: 'Pipeline',
|
||||
},
|
||||
|
|
@ -59,13 +59,13 @@ const downstream = {
|
|||
},
|
||||
sourceJob: {
|
||||
id: 'gid://gitlab/Ci::Bridge/530',
|
||||
retried: true,
|
||||
retried: includeSourceJobRetried ? true : null,
|
||||
},
|
||||
__typename: 'Pipeline',
|
||||
},
|
||||
],
|
||||
__typename: 'PipelineConnection',
|
||||
};
|
||||
});
|
||||
|
||||
const upstream = {
|
||||
id: 'gid://gitlab/Ci::Pipeline/610',
|
||||
|
|
@ -161,7 +161,7 @@ export const mockDownstreamQueryResponse = {
|
|||
pipeline: {
|
||||
path: '/root/ci-project/-/pipelines/790',
|
||||
id: 'pipeline-1',
|
||||
downstream,
|
||||
downstream: mockDownstreamPipelinesGraphql(),
|
||||
upstream: null,
|
||||
},
|
||||
__typename: 'Project',
|
||||
|
|
@ -176,7 +176,7 @@ export const mockUpstreamDownstreamQueryResponse = {
|
|||
pipeline: {
|
||||
id: 'pipeline-1',
|
||||
path: '/root/ci-project/-/pipelines/790',
|
||||
downstream,
|
||||
downstream: mockDownstreamPipelinesGraphql(),
|
||||
upstream,
|
||||
},
|
||||
__typename: 'Project',
|
||||
|
|
|
|||
|
|
@ -23,8 +23,19 @@ RSpec.describe Projects::PipelinesController, '(JavaScript fixtures)', type: :co
|
|||
let!(:build_test) { create(:ci_build, pipeline: pipeline, stage: 'test') }
|
||||
let!(:build_deploy_failed) { create(:ci_build, status: :failed, pipeline: pipeline, stage: 'deploy') }
|
||||
|
||||
let(:bridge) { create(:ci_bridge, pipeline: pipeline) }
|
||||
let(:retried_bridge) { create(:ci_bridge, :retried, pipeline: pipeline) }
|
||||
|
||||
let(:downstream_pipeline) { create(:ci_pipeline, :with_job) }
|
||||
let(:retried_downstream_pipeline) { create(:ci_pipeline, :with_job) }
|
||||
let!(:ci_sources_pipeline) { create(:ci_sources_pipeline, pipeline: downstream_pipeline, source_job: bridge) }
|
||||
let!(:retried_ci_sources_pipeline) do
|
||||
create(:ci_sources_pipeline, pipeline: retried_downstream_pipeline, source_job: retried_bridge)
|
||||
end
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'pipelines/pipelines.json' do
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
import { createAlert } from '~/flash';
|
||||
import {
|
||||
IssuableStatusText,
|
||||
IssuableType,
|
||||
STATUS_CLOSED,
|
||||
STATUS_OPEN,
|
||||
STATUS_REOPENED,
|
||||
TYPE_EPIC,
|
||||
TYPE_ISSUE,
|
||||
} from '~/issues/constants';
|
||||
import IssuableApp from '~/issues/show/components/app.vue';
|
||||
|
|
@ -485,11 +485,11 @@ describe('Issuable output', () => {
|
|||
});
|
||||
|
||||
it.each`
|
||||
issuableType | issuableStatus | statusIcon
|
||||
${TYPE_ISSUE} | ${STATUS_OPEN} | ${'issues'}
|
||||
${TYPE_ISSUE} | ${STATUS_CLOSED} | ${'issue-closed'}
|
||||
${IssuableType.Epic} | ${STATUS_OPEN} | ${'epic'}
|
||||
${IssuableType.Epic} | ${STATUS_CLOSED} | ${'epic-closed'}
|
||||
issuableType | issuableStatus | statusIcon
|
||||
${TYPE_ISSUE} | ${STATUS_OPEN} | ${'issues'}
|
||||
${TYPE_ISSUE} | ${STATUS_CLOSED} | ${'issue-closed'}
|
||||
${TYPE_EPIC} | ${STATUS_OPEN} | ${'epic'}
|
||||
${TYPE_EPIC} | ${STATUS_CLOSED} | ${'epic-closed'}
|
||||
`(
|
||||
'shows with state icon "$statusIcon" for $issuableType when status is $issuableStatus',
|
||||
async ({ issuableType, issuableStatus, statusIcon }) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { GlAlert, GlLink } from '@gitlab/ui';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { sprintf } from '~/locale';
|
||||
import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
|
||||
import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
|
||||
import LockedWarning, { i18n } from '~/issues/show/components/locked_warning.vue';
|
||||
|
||||
describe('LockedWarning component', () => {
|
||||
|
|
@ -21,7 +21,7 @@ describe('LockedWarning component', () => {
|
|||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findLink = () => wrapper.findComponent(GlLink);
|
||||
|
||||
describe.each([TYPE_ISSUE, IssuableType.Epic])('with issuableType set to %s', (issuableType) => {
|
||||
describe.each([TYPE_ISSUE, TYPE_EPIC])('with issuableType set to %s', (issuableType) => {
|
||||
let alert;
|
||||
let link;
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -121,6 +121,14 @@ describe('Pipelines Table', () => {
|
|||
expect(findPipelineMiniGraph().props('stages').length).toBe(stagesLength);
|
||||
});
|
||||
|
||||
it('should render the latest downstream pipelines only', () => {
|
||||
// component receives two downstream pipelines. one of them is already outdated
|
||||
// because we retried the trigger job, so the mini pipeline graph will only
|
||||
// render the newly created downstream pipeline instead
|
||||
expect(pipeline.triggered).toHaveLength(2);
|
||||
expect(findPipelineMiniGraph().props('downstreamPipelines')).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('when pipeline does not have stages', () => {
|
||||
beforeEach(() => {
|
||||
pipeline = createMockPipeline();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
makeLinksFromNodes,
|
||||
filterByAncestors,
|
||||
generateColumnsFromLayersListBare,
|
||||
keepLatestDownstreamPipelines,
|
||||
listByLayers,
|
||||
parseData,
|
||||
removeOrphanNodes,
|
||||
|
|
@ -10,6 +11,8 @@ import {
|
|||
} from '~/pipelines/components/parsing_utils';
|
||||
import { createNodeDict } from '~/pipelines/utils';
|
||||
|
||||
import { mockDownstreamPipelinesRest } from '../vue_merge_request_widget/mock_data';
|
||||
import { mockDownstreamPipelinesGraphql } from '../commit/mock_data';
|
||||
import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data';
|
||||
import { generateResponse, mockPipelineResponse } from './graph/mock_data';
|
||||
|
||||
|
|
@ -159,3 +162,37 @@ describe('DAG visualization parsing utilities', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('linked pipeline utilities', () => {
|
||||
describe('keepLatestDownstreamPipelines', () => {
|
||||
it('filters data from GraphQL', () => {
|
||||
const downstream = mockDownstreamPipelinesGraphql().nodes;
|
||||
const latestDownstream = keepLatestDownstreamPipelines(downstream);
|
||||
|
||||
expect(downstream).toHaveLength(3);
|
||||
expect(latestDownstream).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('filters data from REST', () => {
|
||||
const downstream = mockDownstreamPipelinesRest();
|
||||
const latestDownstream = keepLatestDownstreamPipelines(downstream);
|
||||
|
||||
expect(downstream).toHaveLength(2);
|
||||
expect(latestDownstream).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('returns downstream pipelines if sourceJob.retried is null', () => {
|
||||
const downstream = mockDownstreamPipelinesGraphql({ includeSourceJobRetried: false }).nodes;
|
||||
const latestDownstream = keepLatestDownstreamPipelines(downstream);
|
||||
|
||||
expect(latestDownstream).toHaveLength(downstream.length);
|
||||
});
|
||||
|
||||
it('returns downstream pipelines if source_job.retried is null', () => {
|
||||
const downstream = mockDownstreamPipelinesRest({ includeSourceJobRetried: false });
|
||||
const latestDownstream = keepLatestDownstreamPipelines(downstream);
|
||||
|
||||
expect(latestDownstream).toHaveLength(downstream.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
|
|||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert } from '~/flash';
|
||||
import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
|
||||
import { IssuableType, TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
|
||||
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
|
||||
import DropdownContents from '~/sidebar/components/labels/labels_select_widget/dropdown_contents.vue';
|
||||
import DropdownValue from '~/sidebar/components/labels/labels_select_widget/dropdown_value.vue';
|
||||
|
|
@ -37,7 +37,7 @@ const errorQueryHandler = jest.fn().mockRejectedValue('Houston, we have a proble
|
|||
const updateLabelsMutation = {
|
||||
[TYPE_ISSUE]: updateIssueLabelsMutation,
|
||||
[IssuableType.MergeRequest]: updateMergeRequestLabelsMutation,
|
||||
[IssuableType.Epic]: updateEpicLabelsMutation,
|
||||
[TYPE_EPIC]: updateEpicLabelsMutation,
|
||||
[IssuableType.TestCase]: updateTestCaseLabelsMutation,
|
||||
};
|
||||
|
||||
|
|
@ -215,7 +215,7 @@ describe('LabelsSelectRoot', () => {
|
|||
issuableType
|
||||
${TYPE_ISSUE}
|
||||
${IssuableType.MergeRequest}
|
||||
${IssuableType.Epic}
|
||||
${TYPE_EPIC}
|
||||
${IssuableType.TestCase}
|
||||
`('when updating labels for $issuableType', ({ issuableType }) => {
|
||||
const label = { id: 'gid://gitlab/ProjectLabel/2' };
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ jest.mock('~/whats_new');
|
|||
describe('HelpCenter component', () => {
|
||||
let wrapper;
|
||||
|
||||
const GlEmoji = { template: '<img/>' };
|
||||
|
||||
const findDropdownGroup = (i = 0) => {
|
||||
return wrapper.findAllComponents(GlDisclosureDropdownGroup).at(i);
|
||||
};
|
||||
|
|
@ -24,6 +26,7 @@ describe('HelpCenter component', () => {
|
|||
const createWrapper = (sidebarData) => {
|
||||
wrapper = mountExtended(HelpCenter, {
|
||||
propsData: { sidebarData },
|
||||
stubs: { GlEmoji },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -52,6 +55,18 @@ describe('HelpCenter component', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
describe('with Gitlab version check feature enabled', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({ ...sidebarData, show_version_check: true });
|
||||
});
|
||||
|
||||
it('shows version information as first item', () => {
|
||||
expect(findDropdownGroup(0).props('group').items).toEqual([
|
||||
{ text: HelpCenter.i18n.version, href: helpPagePath('update/index'), version: '16.0' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('showKeyboardShortcuts', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(wrapper.vm.$refs.dropdown, 'close');
|
||||
|
|
|
|||
|
|
@ -71,4 +71,7 @@ export const sidebarData = {
|
|||
display_whats_new: true,
|
||||
whats_new_most_recent_release_items_count: 5,
|
||||
whats_new_version_digest: 1,
|
||||
show_version_check: false,
|
||||
gitlab_version: { major: 16, minor: 0 },
|
||||
gitlab_version_check: { severity: 'success' },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -111,6 +111,14 @@ describe('MRWidgetPipeline', () => {
|
|||
expect(findPipelineMiniGraph().props('stages')).toHaveLength(stagesCount);
|
||||
});
|
||||
|
||||
it('should render the latest downstream pipelines only', () => {
|
||||
// component receives two downstream pipelines. one of them is already outdated
|
||||
// because we retried the trigger job, so the mini pipeline graph will only
|
||||
// render the newly created downstream pipeline instead
|
||||
expect(mockData.pipeline.triggered).toHaveLength(2);
|
||||
expect(findPipelineMiniGraph().props('downstreamPipelines')).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('should render pipeline coverage information', () => {
|
||||
it('should render coverage percentage', () => {
|
||||
expect(findPipelineCoverage().text()).toMatch(
|
||||
|
|
|
|||
|
|
@ -19,7 +19,13 @@ describe('app/assets/javascripts/vue_merge_request_widget/components/report_widg
|
|||
expect(wrapper.isVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows the container when children have no content', async () => {
|
||||
it('hides the container when children has only empty spaces', async () => {
|
||||
createComponent({ slot: `<span><b> <br/>\t\r\n</b></span> ` });
|
||||
await nextTick();
|
||||
expect(wrapper.isVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows the container when a child has content', async () => {
|
||||
createComponent({ slot: `<span><b>test</b></span>` });
|
||||
await nextTick();
|
||||
expect(wrapper.isVisible()).toBe(true);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,94 @@
|
|||
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
|
||||
|
||||
export const mockDownstreamPipelinesRest = ({ includeSourceJobRetried = true } = {}) => [
|
||||
{
|
||||
id: 632,
|
||||
user: {
|
||||
id: 1,
|
||||
username: 'root',
|
||||
name: 'Administrator',
|
||||
state: 'active',
|
||||
avatar_url:
|
||||
'https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
|
||||
web_url: 'https://gdk.test:3000/root',
|
||||
show_status: false,
|
||||
path: '/root',
|
||||
},
|
||||
active: false,
|
||||
coverage: null,
|
||||
source: 'parent_pipeline',
|
||||
source_job: {
|
||||
name: 'bridge_job',
|
||||
retried: includeSourceJobRetried ? false : null,
|
||||
},
|
||||
path: '/kitchen-sink/bakery/-/pipelines/632',
|
||||
details: {
|
||||
status: {
|
||||
icon: 'status_success',
|
||||
text: 'passed',
|
||||
label: 'passed',
|
||||
group: 'success',
|
||||
tooltip: 'passed',
|
||||
has_details: true,
|
||||
details_path: '/kitchen-sink/bakery/-/pipelines/632',
|
||||
illustration: null,
|
||||
favicon:
|
||||
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
|
||||
},
|
||||
},
|
||||
project: {
|
||||
id: 21,
|
||||
name: 'bakery',
|
||||
full_path: '/kitchen-sink/bakery',
|
||||
full_name: 'kitchen-sink / bakery',
|
||||
refs_url: '/kitchen-sink/bakery/refs',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 633,
|
||||
user: {
|
||||
id: 1,
|
||||
username: 'root',
|
||||
name: 'Administrator',
|
||||
state: 'active',
|
||||
avatar_url:
|
||||
'https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
|
||||
web_url: 'https://gdk.test:3000/root',
|
||||
show_status: false,
|
||||
path: '/root',
|
||||
},
|
||||
active: false,
|
||||
coverage: null,
|
||||
source: 'parent_pipeline',
|
||||
source_job: {
|
||||
name: 'bridge_job',
|
||||
retried: includeSourceJobRetried ? true : null,
|
||||
},
|
||||
path: '/kitchen-sink/bakery/-/pipelines/633',
|
||||
details: {
|
||||
status: {
|
||||
icon: 'status_success',
|
||||
text: 'passed',
|
||||
label: 'passed',
|
||||
group: 'success',
|
||||
tooltip: 'passed',
|
||||
has_details: true,
|
||||
details_path: '/kitchen-sink/bakery/-/pipelines/633',
|
||||
illustration: null,
|
||||
favicon:
|
||||
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
|
||||
},
|
||||
},
|
||||
project: {
|
||||
id: 21,
|
||||
name: 'bakery',
|
||||
full_path: '/kitchen-sink/bakery',
|
||||
full_name: 'kitchen-sink / bakery',
|
||||
refs_url: '/kitchen-sink/bakery/refs',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const artifacts = [
|
||||
{
|
||||
text: 'result.txt',
|
||||
|
|
@ -207,6 +296,7 @@ export default {
|
|||
retry_path: '/root/acets-app/pipelines/172/retry',
|
||||
created_at: '2017-04-07T12:27:19.520Z',
|
||||
updated_at: '2017-04-07T15:28:44.800Z',
|
||||
triggered: mockDownstreamPipelinesRest(),
|
||||
},
|
||||
pipelineCoverageDelta: '15.25',
|
||||
buildsWithCoverage: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { GlBadge } from '@gitlab/ui';
|
||||
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { WorkspaceType, IssuableType, TYPE_ISSUE } from '~/issues/constants';
|
||||
import { WorkspaceType, TYPE_ISSUE, TYPE_EPIC } from '~/issues/constants';
|
||||
|
||||
import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
|
||||
|
||||
|
|
@ -28,9 +28,9 @@ describe('ConfidentialityBadge', () => {
|
|||
});
|
||||
|
||||
it.each`
|
||||
workspaceType | issuableType | expectedTooltip
|
||||
${WorkspaceType.project} | ${TYPE_ISSUE} | ${'Only project members with at least the Reporter role, the author, and assignees can view or be notified about this issue.'}
|
||||
${WorkspaceType.group} | ${IssuableType.Epic} | ${'Only group members with at least the Reporter role can view or be notified about this epic.'}
|
||||
workspaceType | issuableType | expectedTooltip
|
||||
${WorkspaceType.project} | ${TYPE_ISSUE} | ${'Only project members with at least the Reporter role, the author, and assignees can view or be notified about this issue.'}
|
||||
${WorkspaceType.group} | ${TYPE_EPIC} | ${'Only group members with at least the Reporter role can view or be notified about this epic.'}
|
||||
`(
|
||||
'should render gl-badge with correct tooltip when workspaceType is $workspaceType and issuableType is $issuableType',
|
||||
({ workspaceType, issuableType, expectedTooltip }) => {
|
||||
|
|
|
|||
|
|
@ -72,7 +72,10 @@ RSpec.describe SidebarsHelper do
|
|||
support_path: helper.support_url,
|
||||
display_whats_new: helper.display_whats_new?,
|
||||
whats_new_most_recent_release_items_count: helper.whats_new_most_recent_release_items_count,
|
||||
whats_new_version_digest: helper.whats_new_version_digest
|
||||
whats_new_version_digest: helper.whats_new_version_digest,
|
||||
show_version_check: helper.show_version_check?,
|
||||
gitlab_version: Gitlab.version_info,
|
||||
gitlab_version_check: helper.gitlab_version_check
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WebHooks::WebHooksHelper do
|
||||
RSpec.describe WebHooks::WebHooksHelper, :clean_gitlab_redis_shared_state, feature_category: :integrations do
|
||||
let_it_be_with_reload(:project) { create(:project) }
|
||||
|
||||
let(:current_user) { nil }
|
||||
|
|
@ -43,20 +43,12 @@ RSpec.describe WebHooks::WebHooksHelper do
|
|||
expect(helper).to be_show_project_hook_failed_callout(project: project)
|
||||
end
|
||||
|
||||
it 'caches the DB calls until the TTL', :use_clean_rails_memory_store_caching, :request_store do
|
||||
it 'stores a value' do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
expect(redis).to receive(:set).with(anything, 'true', ex: 1.hour)
|
||||
end
|
||||
|
||||
helper.show_project_hook_failed_callout?(project: project)
|
||||
|
||||
travel_to((described_class::EXPIRY_TTL - 1.second).from_now) do
|
||||
expect do
|
||||
helper.show_project_hook_failed_callout?(project: project)
|
||||
end.not_to exceed_query_limit(0)
|
||||
end
|
||||
|
||||
travel_to((described_class::EXPIRY_TTL + 1.second).from_now) do
|
||||
expect do
|
||||
helper.show_project_hook_failed_callout?(project: project)
|
||||
end.to exceed_query_limit(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -418,25 +418,54 @@ RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :authentication_and_
|
|||
end
|
||||
|
||||
context "and LDAP user has an account already" do
|
||||
let!(:existing_user) { create(:omniauth_user, name: 'John Doe', email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') }
|
||||
context 'when sync_name is disabled' do
|
||||
before do
|
||||
allow(Gitlab.config.ldap).to receive(:sync_name).and_return(false)
|
||||
end
|
||||
|
||||
it "adds the omniauth identity to the LDAP account" do
|
||||
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
|
||||
let!(:existing_user) { create(:omniauth_user, name: 'John Doe', email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') }
|
||||
|
||||
oauth_user.save # rubocop:disable Rails/SaveBang
|
||||
it "adds the omniauth identity to the LDAP account" do
|
||||
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
|
||||
|
||||
expect(gl_user).to be_valid
|
||||
expect(gl_user.username).to eql 'john'
|
||||
expect(gl_user.name).to eql 'John Doe'
|
||||
expect(gl_user.email).to eql 'john@example.com'
|
||||
expect(gl_user.identities.length).to be 2
|
||||
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
|
||||
expect(identities_as_hash).to match_array(
|
||||
[
|
||||
{ provider: 'ldapmain', extern_uid: dn },
|
||||
{ provider: 'twitter', extern_uid: uid }
|
||||
]
|
||||
)
|
||||
oauth_user.save # rubocop:disable Rails/SaveBang
|
||||
|
||||
expect(gl_user).to be_valid
|
||||
expect(gl_user.username).to eql 'john'
|
||||
expect(gl_user.name).to eql 'John Doe'
|
||||
expect(gl_user.email).to eql 'john@example.com'
|
||||
expect(gl_user.identities.length).to be 2
|
||||
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
|
||||
expect(identities_as_hash).to match_array(
|
||||
[
|
||||
{ provider: 'ldapmain', extern_uid: dn },
|
||||
{ provider: 'twitter', extern_uid: uid }
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sync_name is enabled' do
|
||||
let!(:existing_user) { create(:omniauth_user, name: 'John Swift', email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') }
|
||||
|
||||
it "adds the omniauth identity to the LDAP account" do
|
||||
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
|
||||
|
||||
oauth_user.save # rubocop:disable Rails/SaveBang
|
||||
|
||||
expect(gl_user).to be_valid
|
||||
expect(gl_user.username).to eql 'john'
|
||||
expect(gl_user.name).to eql 'John Swift'
|
||||
expect(gl_user.email).to eql 'john@example.com'
|
||||
expect(gl_user.identities.length).to be 2
|
||||
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
|
||||
expect(identities_as_hash).to match_array(
|
||||
[
|
||||
{ provider: 'ldapmain', extern_uid: dn },
|
||||
{ provider: 'twitter', extern_uid: uid }
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2106,16 +2106,22 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
|
|||
let(:repository) { mutable_repository }
|
||||
let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
|
||||
let(:target_branch) { 'test-merge-target-branch' }
|
||||
let(:target_sha) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
|
||||
|
||||
before do
|
||||
repository.create_branch(target_branch, '6d394385cf567f80a8fd85055db1ab4c5295806f')
|
||||
repository.create_branch(target_branch, target_sha)
|
||||
end
|
||||
|
||||
it 'can perform a merge' do
|
||||
merge_commit_id = nil
|
||||
result = repository.merge(user, source_sha, target_branch, 'Test merge') do |commit_id|
|
||||
merge_commit_id = commit_id
|
||||
end
|
||||
result =
|
||||
repository.merge(user,
|
||||
source_sha: source_sha,
|
||||
target_branch: target_branch,
|
||||
target_sha: target_sha,
|
||||
message: 'Test merge') do |commit_id|
|
||||
merge_commit_id = commit_id
|
||||
end
|
||||
|
||||
expect(result.newrev).to eq(merge_commit_id)
|
||||
expect(result.repo_created).to eq(false)
|
||||
|
|
@ -2124,10 +2130,15 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
|
|||
|
||||
it 'returns nil if there was a concurrent branch update' do
|
||||
concurrent_update_id = '33f3729a45c02fc67d00adb1b8bca394b0e761d9'
|
||||
result = repository.merge(user, source_sha, target_branch, 'Test merge') do
|
||||
# This ref update should make the merge fail
|
||||
repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id)
|
||||
end
|
||||
result =
|
||||
repository.merge(user,
|
||||
source_sha: source_sha,
|
||||
target_branch: target_branch,
|
||||
target_sha: target_sha,
|
||||
message: 'Test merge') do |_commit_id|
|
||||
# This ref update should make the merge fail
|
||||
repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id)
|
||||
end
|
||||
|
||||
# This 'nil' signals that the merge was not applied
|
||||
expect(result).to be_nil
|
||||
|
|
@ -2147,7 +2158,13 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
|
|||
repository.create_branch(target_branch, branch_head)
|
||||
end
|
||||
|
||||
subject { repository.ff_merge(user, source_sha, target_branch) }
|
||||
subject do
|
||||
repository.ff_merge(user,
|
||||
source_sha: source_sha,
|
||||
target_branch: target_branch,
|
||||
target_sha: branch_head
|
||||
)
|
||||
end
|
||||
|
||||
shared_examples '#ff_merge' do
|
||||
it 'performs a ff_merge' do
|
||||
|
|
@ -2159,7 +2176,7 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
|
|||
end
|
||||
|
||||
context 'with a non-existing target branch' do
|
||||
subject { repository.ff_merge(user, source_sha, 'this-isnt-real') }
|
||||
subject { repository.ff_merge(user, source_sha: source_sha, target_branch: 'this-isnt-real') }
|
||||
|
||||
it 'throws an ArgumentError' do
|
||||
expect { subject }.to raise_error(ArgumentError)
|
||||
|
|
@ -2187,8 +2204,9 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
|
|||
|
||||
it "calls Gitaly's OperationService" do
|
||||
expect_any_instance_of(Gitlab::GitalyClient::OperationService)
|
||||
.to receive(:user_ff_branch).with(user, source_sha, target_branch)
|
||||
.and_return(nil)
|
||||
.to receive(:user_ff_branch).with(
|
||||
user, source_sha: source_sha, target_branch: target_branch, target_sha: branch_head
|
||||
).and_return(nil)
|
||||
|
||||
subject
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::GitalyClient::OperationService do
|
||||
RSpec.describe Gitlab::GitalyClient::OperationService, feature_category: :source_code_management do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
|
|
@ -279,12 +279,39 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
|
|||
|
||||
describe '#user_merge_branch' do
|
||||
let(:target_branch) { 'master' }
|
||||
let(:target_sha) { repository.commit(target_branch).sha }
|
||||
let(:source_sha) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' }
|
||||
let(:message) { 'Merge a branch' }
|
||||
|
||||
subject { client.user_merge_branch(user, source_sha, target_branch, message) {} }
|
||||
subject do
|
||||
client.user_merge_branch(user,
|
||||
source_sha: source_sha,
|
||||
target_branch: target_branch,
|
||||
target_sha: target_sha,
|
||||
message: message
|
||||
) {}
|
||||
end
|
||||
|
||||
it 'sends a user_merge_branch message', :freeze_time do
|
||||
first_request =
|
||||
Gitaly::UserMergeBranchRequest.new(
|
||||
repository: repository.gitaly_repository,
|
||||
user: gitaly_user,
|
||||
commit_id: source_sha,
|
||||
branch: target_branch,
|
||||
expected_old_oid: target_sha,
|
||||
message: message,
|
||||
timestamp: Google::Protobuf::Timestamp.new(seconds: Time.now.utc.to_i)
|
||||
)
|
||||
|
||||
second_request = Gitaly::UserMergeBranchRequest.new(apply: true)
|
||||
|
||||
expect_next_instance_of(Gitlab::GitalyClient::QueueEnumerator) do |instance|
|
||||
expect(instance).to receive(:push).with(first_request).and_call_original
|
||||
expect(instance).to receive(:push).with(second_request).and_call_original
|
||||
expect(instance).to receive(:close)
|
||||
end
|
||||
|
||||
it 'sends a user_merge_branch message' do
|
||||
expect(subject).to be_a(Gitlab::Git::OperationService::BranchUpdate)
|
||||
expect(subject.newrev).to be_present
|
||||
expect(subject.repo_created).to be(false)
|
||||
|
|
@ -431,12 +458,14 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
|
|||
|
||||
describe '#user_ff_branch' do
|
||||
let(:target_branch) { 'my-branch' }
|
||||
let(:target_sha) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
|
||||
let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
|
||||
let(:request) do
|
||||
Gitaly::UserFFBranchRequest.new(
|
||||
repository: repository.gitaly_repository,
|
||||
branch: target_branch,
|
||||
commit_id: source_sha,
|
||||
expected_old_oid: target_sha,
|
||||
user: gitaly_user
|
||||
)
|
||||
end
|
||||
|
|
@ -457,7 +486,13 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
|
|||
.and_return(response)
|
||||
end
|
||||
|
||||
subject { client.user_ff_branch(user, source_sha, target_branch) }
|
||||
subject do
|
||||
client.user_ff_branch(user,
|
||||
source_sha: source_sha,
|
||||
target_branch: target_branch,
|
||||
target_sha: target_sha
|
||||
)
|
||||
end
|
||||
|
||||
it 'sends a user_ff_branch message and returns a BranchUpdate object' do
|
||||
expect(subject).to be_a(Gitlab::Git::OperationService::BranchUpdate)
|
||||
|
|
|
|||
|
|
@ -26,5 +26,22 @@ RSpec.describe Gitlab::Redis::Cache do
|
|||
|
||||
expect(described_class.active_support_config[:expires_in]).to eq(1.day)
|
||||
end
|
||||
|
||||
context 'when encountering an error' do
|
||||
let(:cache) { ActiveSupport::Cache::RedisCacheStore.new(**described_class.active_support_config) }
|
||||
|
||||
subject { cache.read('x') }
|
||||
|
||||
before do
|
||||
described_class.with do |redis|
|
||||
allow(redis).to receive(:get).and_raise(::Redis::CommandError)
|
||||
end
|
||||
end
|
||||
|
||||
it 'logs error' do
|
||||
expect(::Gitlab::ErrorTracking).to receive(:log_exception)
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,4 +4,21 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::Redis::RateLimiting do
|
||||
include_examples "redis_new_instance_shared_examples", 'rate_limiting', Gitlab::Redis::Cache
|
||||
|
||||
describe '.cache_store' do
|
||||
context 'when encountering an error' do
|
||||
subject { described_class.cache_store.read('x') }
|
||||
|
||||
before do
|
||||
described_class.with do |redis|
|
||||
allow(redis).to receive(:get).and_raise(::Redis::CommandError)
|
||||
end
|
||||
end
|
||||
|
||||
it 'logs error' do
|
||||
expect(::Gitlab::ErrorTracking).to receive(:log_exception)
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,5 +17,20 @@ RSpec.describe Gitlab::Redis::RepositoryCache, feature_category: :scalability do
|
|||
it 'has a default ttl of 8 hours' do
|
||||
expect(described_class.cache_store.options[:expires_in]).to eq(8.hours)
|
||||
end
|
||||
|
||||
context 'when encountering an error' do
|
||||
subject { described_class.cache_store.read('x') }
|
||||
|
||||
before do
|
||||
described_class.with do |redis|
|
||||
allow(redis).to receive(:get).and_raise(::Redis::CommandError)
|
||||
end
|
||||
end
|
||||
|
||||
it 'logs error' do
|
||||
expect(::Gitlab::ErrorTracking).to receive(:log_exception)
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ RSpec.describe Gitlab::SlashCommands::Presenters::IssueMove do
|
|||
let_it_be(:other_project) { create(:project) }
|
||||
let_it_be(:old_issue, reload: true) { create(:issue, project: project) }
|
||||
|
||||
let(:new_issue) { Issues::MoveService.new(project: project, current_user: user).execute(old_issue, other_project) }
|
||||
let(:new_issue) { Issues::MoveService.new(container: project, current_user: user).execute(old_issue, other_project) }
|
||||
let(:attachment) { subject[:attachments].first }
|
||||
|
||||
subject { described_class.new(new_issue).present(old_issue) }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe RecountEpicCacheCountsV3, :migration, feature_category: :portfolio_management do
|
||||
let(:migration) { described_class::MIGRATION }
|
||||
|
||||
describe '#up' do
|
||||
it 'schedules a batched background migration' do
|
||||
migrate!
|
||||
|
||||
expect(migration).to have_scheduled_batched_migration(
|
||||
table_name: :epics,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
max_batch_size: described_class::MAX_BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#down' do
|
||||
it 'deletes all batched migration records' do
|
||||
migrate!
|
||||
schema_migrate_down!
|
||||
|
||||
expect(migration).not_to have_scheduled_batched_migration
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -79,17 +79,91 @@ RSpec.describe ProjectHook, feature_category: :integrations do
|
|||
end
|
||||
|
||||
describe '#update_last_failure', :clean_gitlab_redis_shared_state do
|
||||
let(:hook) { build(:project_hook) }
|
||||
let_it_be(:hook) { create(:project_hook) }
|
||||
|
||||
def last_failure
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.get(hook.project.last_failure_redis_key)
|
||||
end
|
||||
end
|
||||
|
||||
def any_failed?
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
Gitlab::Utils.to_boolean(redis.get(hook.project.web_hook_failure_redis_key))
|
||||
end
|
||||
end
|
||||
|
||||
it 'is a method of this class' do
|
||||
expect { hook.update_last_failure }.not_to raise_error
|
||||
end
|
||||
|
||||
context 'when the hook is executable' do
|
||||
it 'does not update the state' do
|
||||
expect(Gitlab::Redis::SharedState).not_to receive(:with)
|
||||
let(:redis_key) { hook.project.web_hook_failure_redis_key }
|
||||
|
||||
hook.update_last_failure
|
||||
def redis_value
|
||||
any_failed?
|
||||
end
|
||||
|
||||
context 'when the state was previously failing' do
|
||||
before do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.set(redis_key, true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does update the state' do
|
||||
expect { hook.update_last_failure }.to change { redis_value }.to(false)
|
||||
end
|
||||
|
||||
context 'when there is another failing sibling hook' do
|
||||
before do
|
||||
create(:project_hook, :permanently_disabled, project: hook.project)
|
||||
end
|
||||
|
||||
it 'does not update the state' do
|
||||
expect { hook.update_last_failure }.not_to change { redis_value }.from(true)
|
||||
end
|
||||
|
||||
it 'caches the current value' do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
expect(redis).to receive(:set).with(redis_key, 'true', ex: 1.hour).and_call_original
|
||||
end
|
||||
|
||||
hook.update_last_failure
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the state was previously unknown' do
|
||||
before do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.del(redis_key)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not update the state' do
|
||||
expect { hook.update_last_failure }.not_to change { redis_value }.from(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the state was previously not failing' do
|
||||
before do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.set(redis_key, false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not update the state' do
|
||||
expect { hook.update_last_failure }.not_to change { redis_value }.from(false)
|
||||
end
|
||||
|
||||
it 'does not cache the current value' do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
expect(redis).not_to receive(:set)
|
||||
end
|
||||
|
||||
hook.update_last_failure
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -98,28 +172,34 @@ RSpec.describe ProjectHook, feature_category: :integrations do
|
|||
allow(hook).to receive(:executable?).and_return(false)
|
||||
end
|
||||
|
||||
def last_failure
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.get("web_hooks:last_failure:project-#{hook.project.id}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'there is no prior value', :freeze_time do
|
||||
it 'updates the state' do
|
||||
it 'updates last_failure' do
|
||||
expect { hook.update_last_failure }.to change { last_failure }.to(Time.current)
|
||||
end
|
||||
|
||||
it 'updates any_failed?' do
|
||||
expect { hook.update_last_failure }.to change { any_failed? }.to(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'there is a prior value, from before now' do
|
||||
context 'when there is a prior last_failure, from before now' do
|
||||
it 'updates the state' do
|
||||
the_future = 1.minute.from_now
|
||||
|
||||
hook.update_last_failure
|
||||
|
||||
travel_to(the_future) do
|
||||
expect { hook.update_last_failure }.to change { last_failure }.to(the_future.iso8601)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not change the failing state' do
|
||||
the_future = 1.minute.from_now
|
||||
hook.update_last_failure
|
||||
|
||||
travel_to(the_future) do
|
||||
expect { hook.update_last_failure }.not_to change { any_failed? }.from(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'there is a prior value, from after now' do
|
||||
|
|
|
|||
|
|
@ -443,7 +443,7 @@ RSpec.describe WebHook, feature_category: :integrations do
|
|||
|
||||
describe '#update_last_failure' do
|
||||
it 'is a method of this class' do
|
||||
expect { described_class.new.update_last_failure }.not_to raise_error
|
||||
expect { described_class.new(project: project).update_last_failure }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -8826,6 +8826,14 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'something that has web-hooks' do
|
||||
let_it_be_with_reload(:object) { create(:project) }
|
||||
|
||||
def create_hook
|
||||
create(:project_hook, project: object)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def finish_job(export_job)
|
||||
|
|
|
|||
|
|
@ -1848,6 +1848,8 @@ RSpec.describe Repository, feature_category: :source_code_management do
|
|||
end
|
||||
|
||||
describe '#expire_root_ref_cache' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it 'expires the root reference cache' do
|
||||
repository.root_ref
|
||||
|
||||
|
|
@ -1949,6 +1951,40 @@ RSpec.describe Repository, feature_category: :source_code_management do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#merge_to_branch' do
|
||||
let(:merge_request) do
|
||||
create(:merge_request, source_branch: 'feature', target_branch: project.default_branch, source_project: project)
|
||||
end
|
||||
|
||||
it 'merges two branches and returns the merge commit id' do
|
||||
message = 'New merge commit'
|
||||
merge_commit_id =
|
||||
repository.merge_to_branch(user,
|
||||
source_sha: merge_request.diff_head_sha,
|
||||
target_branch: merge_request.target_branch,
|
||||
target_sha: repository.commit(merge_request.target_branch).sha,
|
||||
message: message)
|
||||
|
||||
expect(repository.commit(merge_commit_id).message).to eq(message)
|
||||
expect(repository.commit(merge_request.target_branch).sha).to eq(merge_commit_id)
|
||||
end
|
||||
|
||||
it 'does not merge if target branch has been changed' do
|
||||
target_sha = project.commit.sha
|
||||
|
||||
repository.create_file(user, 'file.txt', 'CONTENT', message: 'Add file', branch_name: project.default_branch)
|
||||
|
||||
merge_commit_id =
|
||||
repository.merge_to_branch(user,
|
||||
source_sha: merge_request.diff_head_sha,
|
||||
target_branch: merge_request.target_branch,
|
||||
target_sha: target_sha,
|
||||
message: 'New merge commit')
|
||||
|
||||
expect(merge_commit_id).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#merge_to_ref' do
|
||||
let(:merge_request) do
|
||||
create(:merge_request, source_branch: 'feature',
|
||||
|
|
@ -1975,15 +2011,20 @@ RSpec.describe Repository, feature_category: :source_code_management do
|
|||
end
|
||||
|
||||
describe '#ff_merge' do
|
||||
let(:target_branch) { 'ff-target' }
|
||||
let(:merge_request) do
|
||||
create(:merge_request, source_branch: 'feature', target_branch: target_branch, source_project: project)
|
||||
end
|
||||
|
||||
before do
|
||||
repository.add_branch(user, 'ff-target', 'feature~5')
|
||||
repository.add_branch(user, target_branch, 'feature~5')
|
||||
end
|
||||
|
||||
it 'merges the code and return the commit id' do
|
||||
merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'ff-target', source_project: project)
|
||||
merge_commit_id = repository.ff_merge(user,
|
||||
merge_request.diff_head_sha,
|
||||
merge_request.target_branch,
|
||||
target_sha: repository.commit(merge_request.target_branch).sha,
|
||||
merge_request: merge_request)
|
||||
merge_commit = repository.commit(merge_commit_id)
|
||||
|
||||
|
|
@ -1992,14 +2033,24 @@ RSpec.describe Repository, feature_category: :source_code_management do
|
|||
end
|
||||
|
||||
it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do
|
||||
merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'ff-target', source_project: project)
|
||||
merge_commit_id = repository.ff_merge(user,
|
||||
merge_request.diff_head_sha,
|
||||
merge_request.target_branch,
|
||||
target_sha: repository.commit(merge_request.target_branch).sha,
|
||||
merge_request: merge_request)
|
||||
|
||||
expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id)
|
||||
end
|
||||
|
||||
it 'does not merge if target branch has been changed' do
|
||||
target_sha = project.commit(target_branch).sha
|
||||
|
||||
repository.create_file(user, 'file.txt', 'CONTENT', message: 'Add file', branch_name: target_branch)
|
||||
|
||||
merge_commit_id = repository.ff_merge(user, merge_request.diff_head_sha, target_branch, target_sha: target_sha)
|
||||
|
||||
expect(merge_commit_id).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#rebase' do
|
||||
|
|
|
|||
|
|
@ -154,6 +154,47 @@ RSpec.describe 'Query.issue(id)', feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when selecting `related_merge_requests`' do
|
||||
let(:issue_fields) { ['relatedMergeRequests { nodes { id } }'] }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:mr_project) { project }
|
||||
let!(:merge_request) do
|
||||
attributes = {
|
||||
author: user,
|
||||
source_project: mr_project,
|
||||
target_project: mr_project,
|
||||
source_branch: 'master',
|
||||
target_branch: 'test',
|
||||
description: "See #{issue.to_reference}"
|
||||
}
|
||||
|
||||
create(:merge_request, attributes).tap do |merge_request|
|
||||
create(:note, :system, project: issue.project, noteable: issue,
|
||||
author: user, note: merge_request.to_reference(full: true))
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
|
||||
post_graphql(query, current_user: current_user)
|
||||
end
|
||||
|
||||
it 'returns the related merge request' do
|
||||
expect(issue_data['relatedMergeRequests']['nodes']).to include a_hash_including({
|
||||
'id' => merge_request.to_global_id.to_s
|
||||
})
|
||||
end
|
||||
|
||||
context 'no permission to related merge request' do
|
||||
let_it_be(:mr_project) { create(:project, :private) }
|
||||
|
||||
it 'does not return the related merge request' do
|
||||
expect(issue_data['relatedMergeRequests']['nodes']).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a confidential issue' do
|
||||
let!(:confidential_issue) do
|
||||
create(:issue, :confidential, project: project)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ RSpec.describe 'getting Alert Management Alerts', feature_category: :incident_ma
|
|||
let(:fields) do
|
||||
<<~QUERY
|
||||
nodes {
|
||||
#{all_graphql_fields_for('AlertManagementAlert', excluded: ['assignees'])}
|
||||
#{all_graphql_fields_for('AlertManagementAlert', excluded: %w[assignees relatedMergeRequests])}
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ RSpec.describe IssueEntity do
|
|||
before do
|
||||
project.add_developer(member)
|
||||
public_project.add_developer(member)
|
||||
Issues::MoveService.new(project: public_project, current_user: member).execute(issue, project)
|
||||
Issues::MoveService.new(container: public_project, current_user: member).execute(issue, project)
|
||||
end
|
||||
|
||||
context 'when user cannot read target project' do
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ RSpec.describe PipelineDetailsEntity do
|
|||
|
||||
expect(source_jobs[cross_project_pipeline.id][:name]).to eq('cross-project')
|
||||
expect(source_jobs[child_pipeline.id][:name]).to eq('child')
|
||||
expect(source_jobs[child_pipeline.id][:retried]).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ RSpec.describe Issuable::DestroyService do
|
|||
let(:group) { create(:group, :public) }
|
||||
let(:project) { create(:project, :public, group: group) }
|
||||
|
||||
subject(:service) { described_class.new(project: project, current_user: user) }
|
||||
subject(:service) { described_class.new(container: project, current_user: user) }
|
||||
|
||||
describe '#execute' do
|
||||
context 'when issuable is an issue' do
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ RSpec.describe Issues::CloneService do
|
|||
let(:with_notes) { false }
|
||||
|
||||
subject(:clone_service) do
|
||||
described_class.new(project: old_project, current_user: user)
|
||||
described_class.new(container: old_project, current_user: user)
|
||||
end
|
||||
|
||||
shared_context 'user can clone issue' do
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ RSpec.describe Issues::MoveService do
|
|||
end
|
||||
|
||||
subject(:move_service) do
|
||||
described_class.new(project: old_project, current_user: user)
|
||||
described_class.new(container: old_project, current_user: user)
|
||||
end
|
||||
|
||||
shared_context 'user can move issue' do
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ RSpec.describe TasksToBeDone::BaseService do
|
|||
|
||||
subject(:service) do
|
||||
TasksToBeDone::CreateCiTaskService.new(
|
||||
project: project,
|
||||
container: project,
|
||||
current_user: current_user,
|
||||
assignee_ids: assignee_ids
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ RSpec.describe WorkItems::DeleteService do
|
|||
end
|
||||
|
||||
describe '#execute' do
|
||||
subject(:result) { described_class.new(project: project, current_user: user).execute(work_item) }
|
||||
subject(:result) { described_class.new(container: project, current_user: user).execute(work_item) }
|
||||
|
||||
context 'when user can delete the work item' do
|
||||
it { is_expected.to be_success }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'something that has web-hooks' do
|
||||
describe '#any_hook_failed?', :clean_gitlab_redis_shared_state do
|
||||
subject { object.any_hook_failed? }
|
||||
|
||||
context 'when there are no hooks' do
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
|
||||
context 'when there are hooks' do
|
||||
before do
|
||||
create_hook
|
||||
create_hook
|
||||
end
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
|
||||
context 'when there is a failed hook' do
|
||||
before do
|
||||
hook = create_hook
|
||||
hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD + 1)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#cache_web_hook_failure', :clean_gitlab_redis_shared_state do
|
||||
context 'when no value is passed' do
|
||||
it 'stores the return value of #any_hook_failed? and passes it back' do
|
||||
allow(object).to receive(:any_hook_failed?).and_return(true)
|
||||
|
||||
Gitlab::Redis::SharedState.with do |r|
|
||||
expect(r).to receive(:set)
|
||||
.with(object.web_hook_failure_redis_key, 'true', ex: 1.hour)
|
||||
.and_call_original
|
||||
end
|
||||
|
||||
expect(object.cache_web_hook_failure).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a value is passed' do
|
||||
it 'stores the value and passes it back' do
|
||||
expect(object).not_to receive(:any_hook_failed?)
|
||||
|
||||
Gitlab::Redis::SharedState.with do |r|
|
||||
expect(r).to receive(:set)
|
||||
.with(object.web_hook_failure_redis_key, 'foo', ex: 1.hour)
|
||||
.and_call_original
|
||||
end
|
||||
|
||||
expect(object.cache_web_hook_failure(:foo)).to eq(:foo)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_web_hook_failure', :clean_gitlab_redis_shared_state do
|
||||
subject { object.get_web_hook_failure }
|
||||
|
||||
context 'when no value is stored' do
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'when stored as true' do
|
||||
before do
|
||||
object.cache_web_hook_failure(true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
|
||||
context 'when stored as false' do
|
||||
before do
|
||||
object.cache_web_hook_failure(false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#fetch_web_hook_failure', :clean_gitlab_redis_shared_state do
|
||||
context 'when a value has not been stored' do
|
||||
it 'does not call #any_hook_failed?' do
|
||||
expect(object.get_web_hook_failure).to be_nil
|
||||
expect(object).to receive(:any_hook_failed?).and_return(true)
|
||||
|
||||
expect(object.fetch_web_hook_failure).to eq(true)
|
||||
expect(object.get_web_hook_failure).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a value has been stored' do
|
||||
before do
|
||||
object.cache_web_hook_failure(true)
|
||||
end
|
||||
|
||||
it 'does not call #any_hook_failed?' do
|
||||
expect(object).not_to receive(:any_hook_failed?)
|
||||
|
||||
expect(object.fetch_web_hook_failure).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -5,7 +5,7 @@ RSpec.shared_examples 'graphql issue list request spec' do
|
|||
let(:fields) do
|
||||
<<~QUERY
|
||||
nodes {
|
||||
#{all_graphql_fields_for('issues'.classify)}
|
||||
#{all_graphql_fields_for('issues'.classify, excluded: ['relatedMergeRequests'])}
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
|
@ -683,6 +683,28 @@ RSpec.shared_examples 'graphql issue list request spec' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when selecting `related_merge_requests`' do
|
||||
let(:fields) do
|
||||
<<~QUERY
|
||||
nodes {
|
||||
relatedMergeRequests {
|
||||
nodes {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
||||
it 'limits the field to 1 execution' do
|
||||
post_query
|
||||
|
||||
expect_graphql_errors_to_include(
|
||||
'"relatedMergeRequests" field can be requested only for 1 Issue(s) at a time.'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
it 'includes a web_url' do
|
||||
post_query
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ RSpec.describe TasksToBeDone::CreateWorker do
|
|||
|
||||
expect(service_class)
|
||||
.to receive(:new)
|
||||
.with(project: member_task.project, current_user: current_user, assignee_ids: assignee_ids)
|
||||
.with(container: member_task.project, current_user: current_user, assignee_ids: assignee_ids)
|
||||
.and_call_original
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue