Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
eacc15ffb1
commit
a3b88e15d8
|
|
@ -160,10 +160,21 @@ detect-tests:
|
|||
filter_rspec_matched_foss_tests ${RSPEC_MATCHING_TESTS_PATH} ${RSPEC_MATCHING_TESTS_FOSS_PATH};
|
||||
filter_rspec_matched_ee_tests ${RSPEC_MATCHING_TESTS_PATH} ${RSPEC_MATCHING_TESTS_EE_PATH};
|
||||
|
||||
echoinfo "Changed files: $(cat $RSPEC_CHANGED_FILES_PATH)";
|
||||
echoinfo "Related FOSS RSpec tests: $(cat $RSPEC_MATCHING_TESTS_FOSS_PATH)";
|
||||
echoinfo "Related EE RSpec tests: $(cat $RSPEC_MATCHING_TESTS_EE_PATH)";
|
||||
echoinfo "Related JS files: $(cat $RSPEC_MATCHING_JS_FILES_PATH)";
|
||||
echoinfo 'Changed files:'
|
||||
echoinfo "$(tr ' ' '\n' < $RSPEC_CHANGED_FILES_PATH)"
|
||||
echo ""
|
||||
|
||||
echoinfo 'Related FOSS RSpec tests:'
|
||||
echoinfo "$(tr ' ' '\n' < $RSPEC_MATCHING_TESTS_FOSS_PATH)"
|
||||
echo ""
|
||||
|
||||
echoinfo 'Related EE RSpec tests:'
|
||||
echoinfo "$(tr ' ' '\n' < $RSPEC_MATCHING_TESTS_EE_PATH)"
|
||||
echo ""
|
||||
|
||||
echoinfo 'Related JS files:'
|
||||
echoinfo "$(tr ' ' '\n' < $RSPEC_MATCHING_JS_FILES_PATH)"
|
||||
echo ""
|
||||
fi
|
||||
artifacts:
|
||||
expire_in: 7d
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import {
|
|||
import { __ } from '~/locale';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
|
||||
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
|
||||
import CiVerificationBadge from '../shared/ci_verification_badge.vue';
|
||||
import CiResourceAbout from './ci_resource_about.vue';
|
||||
import CiResourceHeaderSkeletonLoader from './ci_resource_header_skeleton_loader.vue';
|
||||
|
||||
|
|
@ -22,9 +22,9 @@ export default {
|
|||
},
|
||||
components: {
|
||||
AbuseCategorySelector,
|
||||
CiIcon,
|
||||
CiResourceAbout,
|
||||
CiResourceHeaderSkeletonLoader,
|
||||
CiVerificationBadge,
|
||||
GlAvatar,
|
||||
GlAvatarLink,
|
||||
GlDisclosureDropdown,
|
||||
|
|
@ -54,11 +54,6 @@ export default {
|
|||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
pipelineStatus: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
resource: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
|
@ -81,8 +76,8 @@ export default {
|
|||
hasLatestVersion() {
|
||||
return this.latestVersion?.name;
|
||||
},
|
||||
hasPipelineStatus() {
|
||||
return this.pipelineStatus?.text;
|
||||
isVerified() {
|
||||
return this.resource?.verificationLevel !== 'UNVERIFIED';
|
||||
},
|
||||
latestVersion() {
|
||||
return this.resource?.versions?.nodes[0] || {};
|
||||
|
|
@ -139,11 +134,11 @@ export default {
|
|||
{{ versionBadgeText }}
|
||||
</gl-badge>
|
||||
</span>
|
||||
<ci-icon
|
||||
v-if="hasPipelineStatus"
|
||||
:status="pipelineStatus"
|
||||
show-status-text
|
||||
class="gl-mt-2"
|
||||
<ci-verification-badge
|
||||
v-if="isVerified"
|
||||
:verification-level="resource.verificationLevel"
|
||||
:resource-id="resource.id"
|
||||
show-text
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
|
|||
import { toNounSeriesText } from '~/lib/utils/grammar';
|
||||
import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
|
||||
import { CI_RESOURCE_DETAILS_PAGE_NAME } from '../../router/constants';
|
||||
import CiVerificationBadge from '../shared/ci_verification_badge.vue';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
|
|
@ -22,6 +23,7 @@ export default {
|
|||
releasedMessage: s__('CiCatalog|Released %{timeAgo} by %{author}'),
|
||||
},
|
||||
components: {
|
||||
CiVerificationBadge,
|
||||
GlAvatar,
|
||||
GlBadge,
|
||||
GlButton,
|
||||
|
|
@ -80,6 +82,9 @@ export default {
|
|||
hasReleasedVersion() {
|
||||
return Boolean(this.latestVersion?.createdAt);
|
||||
},
|
||||
isVerified() {
|
||||
return this.resource?.verificationLevel !== 'UNVERIFIED';
|
||||
},
|
||||
latestVersion() {
|
||||
return this.resource?.versions?.nodes[0] || [];
|
||||
},
|
||||
|
|
@ -143,7 +148,14 @@ export default {
|
|||
@click="navigateToDetailsPage"
|
||||
/>
|
||||
<div class="gl-display-flex gl-flex-direction-column gl-flex-grow-1">
|
||||
<span class="gl-font-sm gl-mb-1">{{ webPath }}</span>
|
||||
<div>
|
||||
<span class="gl-font-sm gl-mb-1">{{ webPath }}</span>
|
||||
<ci-verification-badge
|
||||
v-if="isVerified"
|
||||
:resource-id="resource.id"
|
||||
:verification-level="resource.verificationLevel"
|
||||
/>
|
||||
</div>
|
||||
<div class="gl-display-flex gl-flex-wrap gl-gap-2 gl-mb-1">
|
||||
<gl-link
|
||||
class="gl-text-gray-900! gl-mr-1"
|
||||
|
|
|
|||
|
|
@ -64,17 +64,8 @@ export default {
|
|||
isLoadingSharedData() {
|
||||
return this.$apollo.queries.resourceSharedData.loading;
|
||||
},
|
||||
versions() {
|
||||
return this.resourceAdditionalDetails?.versions?.nodes || [];
|
||||
},
|
||||
version() {
|
||||
return this.resourceAdditionalDetails?.versions?.nodes[0]?.name || '';
|
||||
},
|
||||
pipelineStatus() {
|
||||
return (
|
||||
this.resourceAdditionalDetails?.versions?.nodes[0]?.commit?.pipelines?.nodes[0]
|
||||
?.detailedStatus || null
|
||||
);
|
||||
return this.resourceSharedData?.versions?.nodes[0]?.name || '';
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
|
|
@ -102,7 +93,6 @@ export default {
|
|||
:open-merge-requests-count="resourceAdditionalDetails.openMergeRequestsCount"
|
||||
:is-loading-details="isLoadingDetails"
|
||||
:is-loading-shared-data="isLoadingSharedData"
|
||||
:pipeline-status="pipelineStatus"
|
||||
:resource="resourceSharedData"
|
||||
/>
|
||||
<ci-resource-details :resource-path="cleanFullPath" :version="version" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
<script>
|
||||
import { GlIcon, GlLink, GlPopover, GlSprintf } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { VerificationLevel } from '../../constants';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
verificationLevelPopoverLink: s__('CiCatalog|Learn more about designated creators'),
|
||||
},
|
||||
VerificationLevel,
|
||||
verificationHelpPagePath: helpPagePath('ci/components/index', { anchor: 'verified-components' }),
|
||||
components: {
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlPopover,
|
||||
GlSprintf,
|
||||
},
|
||||
props: {
|
||||
resourceId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
showText: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
verificationLevel: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
popoverTarget() {
|
||||
return `${this.resourceId}-verification-icon`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<span :id="popoverTarget">
|
||||
<gl-icon
|
||||
class="gl-text-blue-500 gl-ml-1"
|
||||
:name="$options.VerificationLevel[verificationLevel].icon"
|
||||
/>
|
||||
<span
|
||||
v-if="showText"
|
||||
data-testid="verification-badge-text"
|
||||
class="gl-text-blue-500 gl-font-weight-bold gl-cursor-default"
|
||||
>
|
||||
{{ $options.VerificationLevel[verificationLevel].badgeText }}
|
||||
</span>
|
||||
</span>
|
||||
<gl-popover :target="popoverTarget" triggers="hover focus">
|
||||
<div class="gl-display-flex gl-flex-direction-column gl-gap-4">
|
||||
<span>
|
||||
<gl-sprintf :message="$options.VerificationLevel[verificationLevel].popoverText">
|
||||
<template #bold="{ content }">
|
||||
<strong>
|
||||
{{ content }}
|
||||
</strong>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
<gl-link :href="$options.verificationHelpPagePath" target="_blank">
|
||||
{{ $options.i18n.verificationLevelPopoverLink }}
|
||||
</gl-link>
|
||||
</div>
|
||||
</gl-popover>
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
export const CATALOG_FEEDBACK_DISMISSED_KEY = 'catalog_feedback_dismissed';
|
||||
|
||||
|
|
@ -7,6 +8,19 @@ export const SCOPE = {
|
|||
namespaces: 'NAMESPACES',
|
||||
};
|
||||
|
||||
export const VerificationLevel = {
|
||||
GITLAB: {
|
||||
badgeText: s__('CiCatalog|GitLab-maintained'),
|
||||
icon: 'tanuki-verified',
|
||||
popoverText: s__('CiCatalog|Created and maintained by %{boldStart}GitLab%{boldEnd}'),
|
||||
},
|
||||
PARTNER: {
|
||||
badgeText: s__('CiCatalog|Partner'),
|
||||
icon: 'partner-verified',
|
||||
popoverText: s__('CiCatalog|Created and maintained by a %{boldStart}GitLab Partner%{boldEnd}'),
|
||||
},
|
||||
};
|
||||
|
||||
export const SORT_OPTION_CREATED = 'CREATED';
|
||||
export const SORT_OPTION_RELEASED = 'LATEST_RELEASED_AT';
|
||||
export const SORT_ASC = 'ASC';
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
fragment CatalogResourceFields on CiCatalogResource {
|
||||
id
|
||||
webPath
|
||||
description
|
||||
icon
|
||||
name
|
||||
description
|
||||
starCount
|
||||
starrersPath
|
||||
verificationLevel
|
||||
versions(first: 1) {
|
||||
nodes {
|
||||
id
|
||||
|
|
@ -26,4 +26,5 @@ fragment CatalogResourceFields on CiCatalogResource {
|
|||
}
|
||||
}
|
||||
}
|
||||
webPath
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,27 +4,5 @@ query getCiCatalogResourceDetails($fullPath: ID!) {
|
|||
webPath
|
||||
openIssuesCount
|
||||
openMergeRequestsCount
|
||||
versions(first: 1) {
|
||||
nodes {
|
||||
id
|
||||
commit {
|
||||
id
|
||||
pipelines(first: 1) {
|
||||
nodes {
|
||||
id
|
||||
detailedStatus {
|
||||
id
|
||||
detailsPath
|
||||
icon
|
||||
text
|
||||
group
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
name
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<script>
|
||||
import { GlAlert } from '@gitlab/ui';
|
||||
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlAlert,
|
||||
UserCalloutDismisser,
|
||||
},
|
||||
i18n: {
|
||||
header: s__('Deployment|What would you like to see here?'),
|
||||
body: s__(
|
||||
"Deployment|We know this page is a bit empty at the moment, but we're working hard to bring you a great experience! In the meantime, we'd love to hear your feedback.",
|
||||
),
|
||||
action: s__('Deployment|Leave feedback'),
|
||||
},
|
||||
FEEDBACK_ISSUE_LINK: 'https://gitlab.com/gitlab-org/gitlab/-/issues/450700',
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<user-callout-dismisser feature-name="deployment_details_feedback">
|
||||
<template #default="{ dismiss, shouldShowCallout }">
|
||||
<gl-alert
|
||||
v-if="shouldShowCallout"
|
||||
:title="$options.i18n.header"
|
||||
:primary-button-text="$options.i18n.action"
|
||||
:primary-button-link="$options.FEEDBACK_ISSUE_LINK"
|
||||
@dismiss="dismiss"
|
||||
>
|
||||
{{ $options.i18n.body }}
|
||||
</gl-alert>
|
||||
</template>
|
||||
</user-callout-dismisser>
|
||||
</template>
|
||||
|
|
@ -8,6 +8,7 @@ import environmentQuery from '../graphql/queries/environment.query.graphql';
|
|||
import DeploymentHeader from './deployment_header.vue';
|
||||
import DeploymentAside from './deployment_aside.vue';
|
||||
import DeploymentDeployBlock from './deployment_deploy_block.vue';
|
||||
import DetailsFeedback from './details_feedback.vue';
|
||||
|
||||
const DEPLOYMENT_QUERY_POLLING_INTERVAL = 3000;
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ export default {
|
|||
DeploymentHeader,
|
||||
DeploymentAside,
|
||||
DeploymentDeployBlock,
|
||||
DetailsFeedback,
|
||||
DeploymentApprovals: () =>
|
||||
import('ee_component/deployments/components/deployment_approvals.vue'),
|
||||
DeploymentTimeline: () => import('ee_component/deployments/components/deployment_timeline.vue'),
|
||||
|
|
@ -99,6 +101,7 @@ export default {
|
|||
:environment="environment"
|
||||
:loading="$apollo.queries.deployment.loading"
|
||||
/>
|
||||
<details-feedback class="gl-mt-6 gl-w-90p" />
|
||||
<deployment-approvals
|
||||
v-if="hasApprovalSummary"
|
||||
:approval-summary="deployment.approvalSummary"
|
||||
|
|
|
|||
|
|
@ -1,84 +1,13 @@
|
|||
<script>
|
||||
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import { isLoggedIn } from '~/lib/utils/common_utils';
|
||||
import ForksButton from '~/forks/components/forks_button.vue';
|
||||
import MoreActionsDropdown from '~/groups_projects/components/more_actions_dropdown.vue';
|
||||
import NotificationsDropdown from '~/notifications/components/notifications_dropdown.vue';
|
||||
import StarCount from '~/stars/components/star_count.vue';
|
||||
import HomePanelActions from './home_panel_actions.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ForksButton,
|
||||
GlButton,
|
||||
MoreActionsDropdown,
|
||||
NotificationsDropdown,
|
||||
StarCount,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
inject: {
|
||||
adminPath: {
|
||||
default: '',
|
||||
},
|
||||
canReadProject: {
|
||||
default: false,
|
||||
},
|
||||
isProjectEmpty: {
|
||||
default: false,
|
||||
},
|
||||
projectId: {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoggedIn: isLoggedIn(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canForkProject() {
|
||||
return !this.isProjectEmpty && isLoggedIn() && this.canReadProject;
|
||||
},
|
||||
copyProjectId() {
|
||||
return sprintf(s__('ProjectPage|Project ID: %{id}'), { id: this.projectId });
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
adminButtonTooltip: __('View project in admin area'),
|
||||
HomePanelActions,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="gl-align-items-center gl-display-flex gl-flex-wrap gl-gap-3 gl-justify-content-md-end project-repo-buttons"
|
||||
>
|
||||
<gl-button
|
||||
v-if="adminPath"
|
||||
v-gl-tooltip
|
||||
:aria-label="$options.i18n.adminButtonTooltip"
|
||||
:href="adminPath"
|
||||
:title="$options.i18n.adminButtonTooltip"
|
||||
data-testid="admin-button"
|
||||
icon="admin"
|
||||
/>
|
||||
|
||||
<template v-if="isLoggedIn && canReadProject">
|
||||
<notifications-dropdown />
|
||||
</template>
|
||||
|
||||
<star-count />
|
||||
|
||||
<forks-button v-if="canForkProject" />
|
||||
|
||||
<template v-if="canReadProject">
|
||||
<span class="gl-sr-only" itemprop="identifier" data-testid="project-id-content">
|
||||
{{ copyProjectId }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<more-actions-dropdown />
|
||||
</div>
|
||||
<home-panel-actions />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
<script>
|
||||
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import { isLoggedIn } from '~/lib/utils/common_utils';
|
||||
import ForksButton from '~/forks/components/forks_button.vue';
|
||||
import MoreActionsDropdown from '~/groups_projects/components/more_actions_dropdown.vue';
|
||||
import NotificationsDropdown from '~/notifications/components/notifications_dropdown.vue';
|
||||
import StarCount from '~/stars/components/star_count.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ForksButton,
|
||||
GlButton,
|
||||
MoreActionsDropdown,
|
||||
NotificationsDropdown,
|
||||
StarCount,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
inject: {
|
||||
adminPath: {
|
||||
default: '',
|
||||
},
|
||||
canReadProject: {
|
||||
default: false,
|
||||
},
|
||||
isProjectEmpty: {
|
||||
default: false,
|
||||
},
|
||||
projectId: {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoggedIn: isLoggedIn(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canForkProject() {
|
||||
return !this.isProjectEmpty && isLoggedIn() && this.canReadProject;
|
||||
},
|
||||
copyProjectId() {
|
||||
return sprintf(s__('ProjectPage|Project ID: %{id}'), { id: this.projectId });
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
adminButtonTooltip: __('View project in admin area'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="gl-align-items-center gl-display-flex gl-flex-wrap gl-gap-3 gl-justify-content-md-end project-repo-buttons"
|
||||
>
|
||||
<gl-button
|
||||
v-if="adminPath"
|
||||
v-gl-tooltip
|
||||
:aria-label="$options.i18n.adminButtonTooltip"
|
||||
:href="adminPath"
|
||||
:title="$options.i18n.adminButtonTooltip"
|
||||
data-testid="admin-button"
|
||||
icon="admin"
|
||||
/>
|
||||
|
||||
<template v-if="isLoggedIn && canReadProject">
|
||||
<notifications-dropdown />
|
||||
</template>
|
||||
|
||||
<star-count />
|
||||
|
||||
<forks-button v-if="canForkProject" />
|
||||
|
||||
<template v-if="canReadProject">
|
||||
<span class="gl-sr-only" itemprop="identifier" data-testid="project-id-content">
|
||||
{{ copyProjectId }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<more-actions-dropdown />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -830,13 +830,6 @@ class Namespace < ApplicationRecord
|
|||
Rails.cache.delete_multi(keys)
|
||||
end
|
||||
|
||||
def write_projects_repository_config
|
||||
all_projects.find_each do |project|
|
||||
project.set_full_path
|
||||
project.track_project_repository
|
||||
end
|
||||
end
|
||||
|
||||
def enforce_minimum_path_length?
|
||||
path_changed? && !project_namespace?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2283,16 +2283,6 @@ class Project < ApplicationRecord
|
|||
ensure_pages_metadatum.update!(onboarding_complete: true)
|
||||
end
|
||||
|
||||
def set_full_path(gl_full_path: full_path)
|
||||
# We'd need to keep track of project full path otherwise directory tree
|
||||
# created with hashed storage enabled cannot be usefully imported using
|
||||
# the import rake task.
|
||||
repository.raw_repository.set_full_path(full_path: gl_full_path)
|
||||
rescue Gitlab::Git::Repository::NoRepository => e
|
||||
Gitlab::AppLogger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.")
|
||||
nil
|
||||
end
|
||||
|
||||
def after_import
|
||||
repository.expire_content_cache
|
||||
repository.remove_prohibited_branches
|
||||
|
|
@ -2315,7 +2305,6 @@ class Project < ApplicationRecord
|
|||
after_create_default_branch
|
||||
join_pool_repository
|
||||
refresh_markdown_cache!
|
||||
set_full_path
|
||||
end
|
||||
|
||||
def update_project_counter_caches
|
||||
|
|
|
|||
|
|
@ -85,7 +85,8 @@ module Users
|
|||
joining_a_project_alert: 83, # EE-only
|
||||
transition_to_jihu_callout: 84,
|
||||
summarize_code_changes: 85, # EE-only
|
||||
duo_pro_trial_alert: 86 # EE-only
|
||||
duo_pro_trial_alert: 86, # EE-only
|
||||
deployment_details_feedback: 87
|
||||
}
|
||||
|
||||
validates :feature_name,
|
||||
|
|
|
|||
|
|
@ -97,7 +97,6 @@ module Projects
|
|||
|
||||
def update_repository_configuration
|
||||
project.reload_repository!
|
||||
project.set_full_path
|
||||
project.track_project_repository
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -112,11 +112,6 @@ module Projects
|
|||
|
||||
if @project.import?
|
||||
Gitlab::Tracking.event(self.class.name, 'import_project', user: current_user)
|
||||
else
|
||||
# Skip writing the config for project imports/forks because it
|
||||
# will always fail since the Git directory doesn't exist until
|
||||
# a background job creates it (see Project#add_import_job).
|
||||
@project.set_full_path
|
||||
end
|
||||
|
||||
unless @project.gitlab_project_import?
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ module Projects
|
|||
|
||||
project.old_path_with_namespace = @old_path
|
||||
|
||||
update_repository_configuration(@new_path)
|
||||
update_repository_configuration
|
||||
|
||||
remove_issue_contacts
|
||||
|
||||
|
|
@ -196,8 +196,7 @@ module Projects
|
|||
project.visibility_level = to_namespace.visibility_level unless project.visibility_level_allowed_by_group?
|
||||
end
|
||||
|
||||
def update_repository_configuration(full_path)
|
||||
project.set_full_path(gl_full_path: full_path)
|
||||
def update_repository_configuration
|
||||
project.track_project_repository
|
||||
end
|
||||
|
||||
|
|
@ -233,7 +232,7 @@ module Projects
|
|||
def rollback_side_effects
|
||||
project.reset
|
||||
update_namespace_and_visibility(@old_namespace)
|
||||
update_repository_configuration(@old_path)
|
||||
update_repository_configuration
|
||||
end
|
||||
|
||||
def execute_system_hooks
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95689
|
|||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/371669
|
||||
milestone: '15.6'
|
||||
type: development
|
||||
group: group::incubation
|
||||
group: group::mlops
|
||||
default_enabled: true
|
||||
log_state_changes: true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
master_pipeline_status.check!
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../tooling/danger/master_pipeline_status'
|
||||
|
||||
module Danger
|
||||
class MasterPipelineStatus < ::Danger::Plugin
|
||||
include Tooling::Danger::MasterPipelineStatus
|
||||
end
|
||||
end
|
||||
|
|
@ -95,8 +95,8 @@ If a replica cannot start or rejoin the cluster, or when it lags behind and cann
|
|||
sudo gitlab-ctl patroni reinitialize-replica --member gitlab-database-2.example.com
|
||||
```
|
||||
|
||||
This can be run on any Patroni node, but be aware that `sudo gitlab-ctl patroni
|
||||
reinitialize-replica` without `--member` restarts the server it is run on.
|
||||
This can be run on any Patroni node, but be aware that `sudo gitlab-ctl patroni reinitialize-replica`
|
||||
without `--member` restarts the server it is run on.
|
||||
You should run it locally on the broken server to reduce the risk of
|
||||
unintended data loss.
|
||||
1. Monitor the logs:
|
||||
|
|
|
|||
|
|
@ -33342,6 +33342,7 @@ Name of the feature that the callout is for.
|
|||
| <a id="usercalloutfeaturenameenumci_deprecation_warning_for_types_keyword"></a>`CI_DEPRECATION_WARNING_FOR_TYPES_KEYWORD` | Callout feature name for ci_deprecation_warning_for_types_keyword. |
|
||||
| <a id="usercalloutfeaturenameenumcloud_licensing_subscription_activation_banner"></a>`CLOUD_LICENSING_SUBSCRIPTION_ACTIVATION_BANNER` | Callout feature name for cloud_licensing_subscription_activation_banner. |
|
||||
| <a id="usercalloutfeaturenameenumcluster_security_warning"></a>`CLUSTER_SECURITY_WARNING` | Callout feature name for cluster_security_warning. |
|
||||
| <a id="usercalloutfeaturenameenumdeployment_details_feedback"></a>`DEPLOYMENT_DETAILS_FEEDBACK` | Callout feature name for deployment_details_feedback. |
|
||||
| <a id="usercalloutfeaturenameenumduo_chat_callout"></a>`DUO_CHAT_CALLOUT` | Callout feature name for duo_chat_callout. |
|
||||
| <a id="usercalloutfeaturenameenumduo_pro_trial_alert"></a>`DUO_PRO_TRIAL_ALERT` | Callout feature name for duo_pro_trial_alert. |
|
||||
| <a id="usercalloutfeaturenameenumfeature_flags_new_version"></a>`FEATURE_FLAGS_NEW_VERSION` | Callout feature name for feature_flags_new_version. |
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: Create
|
||||
group: Code Review
|
||||
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments"
|
||||
description: "Documentation for the REST API for merge request approvals in GitLab."
|
||||
---
|
||||
|
||||
# Merge request approvals API
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: Create
|
||||
group: Source Code
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: "Documentation for the REST API for Git tags in GitLab."
|
||||
---
|
||||
|
||||
# Tags API
|
||||
|
|
|
|||
|
|
@ -292,6 +292,17 @@ in the catalog. The project and its repository still exist, but are not visible
|
|||
|
||||
To publish the component project in the catalog again, you need to [publish a new release](#publish-a-new-release).
|
||||
|
||||
### Verified component creators
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/433443) in GitLab 16.11
|
||||
|
||||
Some CI/CD components are badged with an icon to show that the component was created
|
||||
and is maintained by users verified by GitLab:
|
||||
|
||||
- GitLab-maintained (**{tanuki-verified}**): Components that are created and maintained by GitLab.
|
||||
- GitLab Partner (**{partner-verified}**): Components that are created and maintained by
|
||||
a GitLab-verified partner.
|
||||
|
||||
## Best practices
|
||||
|
||||
This section describes some best practices for creating high quality component projects.
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ When the state of a feature flag changes, the developer who made the change
|
|||
|
||||
## When to document features behind a feature flag
|
||||
|
||||
Every feature introduced to the codebase, even if it's behind a disabled feature flag,
|
||||
Every feature introduced to the codebase, even if it's behind a disabled flag,
|
||||
must be documented. For more information, see
|
||||
[the discussion that led to this decision](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47917#note_459984428). [Experiment or Beta](../../policy/experiment-beta-support.md) features are usually behind a feature flag, and must also be documented. For more information, see [Document Experiment or Beta features](experiment_beta.md).
|
||||
[the discussion that led to this decision](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47917#note_459984428). [Experiment or Beta](../../policy/experiment-beta-support.md) features are usually behind a feature flag and must also be documented. For more information, see [Document Experiment or Beta features](experiment_beta.md).
|
||||
|
||||
When the feature is [implemented in multiple merge requests](../feature_flags/index.md#feature-flags-in-gitlab-development),
|
||||
discuss the plan with your technical writer.
|
||||
|
|
@ -39,7 +39,7 @@ even when the feature is not fully functional or otherwise documented.
|
|||
When you document feature flags, you must:
|
||||
|
||||
- [Add history text](#add-history-text).
|
||||
- [Add a note at the start of the topic](#use-a-note-to-describe-the-state-of-the-feature-flag).
|
||||
- [Use a note to describe the state of the feature flag](#use-a-note-to-describe-the-state-of-the-feature-flag).
|
||||
|
||||
## Add history text
|
||||
|
||||
|
|
@ -50,9 +50,9 @@ Possible history entries are:
|
|||
|
||||
```markdown
|
||||
> - [Introduced](issue-link) in GitLab X.X [with a flag](../../administration/feature_flags.md) named `flag_name`. Disabled by default.
|
||||
> - [Enabled on GitLab.com](issue-link) in GitLab X.X.
|
||||
> - [Enabled on GitLab.com](issue-link) in GitLab X.X. Available to GitLab.com administrators only.
|
||||
> - [Enabled on self-managed](issue-link) in GitLab X.X.
|
||||
> - [Enabled on GitLab.com](issue-link) in GitLab X.X.
|
||||
> - [Enabled on GitLab Dedicated](issue-link) in GitLab X.X.
|
||||
> - [Generally available](issue-link) in GitLab X.Y. Feature flag `flag_name` removed.
|
||||
```
|
||||
|
||||
|
|
@ -60,7 +60,8 @@ Possible history entries are:
|
|||
|
||||
Information about feature flags should be in a `FLAG` note at the start of the topic (just below the history).
|
||||
|
||||
The note has three parts, and follows this structure:
|
||||
The note has three required parts and one optional part.
|
||||
The note follows this exact structure and order:
|
||||
|
||||
```markdown
|
||||
FLAG:
|
||||
|
|
@ -73,7 +74,8 @@ FLAG:
|
|||
A `FLAG` note renders on the GitLab documentation site as:
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../../administration/feature_flags.md) named `example_flag`.
|
||||
On self-managed GitLab, by default this feature is not available.
|
||||
To make it available, an administrator can [enable the feature flag](../../administration/feature_flags.md) named `example_flag`.
|
||||
On GitLab.com and GitLab Dedicated, this feature is not available.
|
||||
This feature is not ready for production use.
|
||||
|
||||
|
|
@ -95,65 +97,78 @@ This feature is not ready for production use.
|
|||
|
||||
| If the feature is... | Use this text |
|
||||
|---------------------------------------------|---------------|
|
||||
| Available | `On GitLab.com, this feature is available. On GitLab Dedicated, this feature is not available.` |
|
||||
| Available | `On GitLab.com, this feature is available.` |
|
||||
| Available to GitLab.com administrators only | `On GitLab.com, this feature is available but can be configured by GitLab.com administrators only.` |
|
||||
| Unavailable | `On GitLab.com and GitLab Dedicated, this feature is not available.`|
|
||||
| Unavailable | `On GitLab.com, this feature is not available.` |
|
||||
|
||||
### GitLab Dedicated availability information
|
||||
|
||||
| If the feature is... | Use this text |
|
||||
|---------------------------------------------|---------------|
|
||||
| Available | `On GitLab Dedicated, this feature is available.` |
|
||||
| Unavailable | `On GitLab Dedicated, this feature is not available.`|
|
||||
| Unavailable | `On GitLab Dedicated, this feature is not available.` |
|
||||
|
||||
- You can combine GitLab.com and GitLab Dedicated like this:
|
||||
`On GitLab.com and GitLab Dedicated, this feature is not available.`
|
||||
- If the feature is behind a feature flag that is disabled for self-managed,
|
||||
- If the feature is behind a flag that is disabled for self-managed GitLab,
|
||||
the feature is not available for GitLab Dedicated.
|
||||
|
||||
### Optional information
|
||||
|
||||
If needed, you can add this sentence:
|
||||
|
||||
`The feature is not ready for production use.`
|
||||
`This feature is not ready for production use.`
|
||||
|
||||
## Feature flag documentation examples
|
||||
|
||||
The following examples show the progression of a feature flag.
|
||||
The following examples show the progression of a feature flag. Update the history and the `FLAG` note with every change:
|
||||
|
||||
```markdown
|
||||
> - Introduced in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default.
|
||||
> - [Introduced](issue-link) in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available,
|
||||
an administrator can [enable the feature flag](../administration/feature_flags.md) named `forti_token_cloud`.
|
||||
The feature is not ready for production use. On GitLab.com and GitLab Dedicated, this feature is not available.
|
||||
On self-managed GitLab, by default this feature is not available.
|
||||
To make it available, an administrator can [enable the feature flag](../administration/feature_flags.md) named `forti_token_cloud`.
|
||||
On GitLab.com and GitLab Dedicated, this feature is not available.
|
||||
```
|
||||
|
||||
When the feature is enabled in production, you can update the history:
|
||||
When the feature is enabled by default on self-managed GitLab:
|
||||
|
||||
```markdown
|
||||
> - Introduced in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default.
|
||||
> - [Enabled on self-managed](https://gitlab.com/issue/etc) GitLab 13.8.
|
||||
> - [Introduced](issue-link) in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default.
|
||||
> - [Enabled on self-managed and GitLab Dedicated](issue-link) in GitLab 13.8.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is available. To hide the feature per user,
|
||||
an administrator can [disable the feature flag](../administration/feature_flags.md) named `forti_token_cloud`.
|
||||
On self-managed GitLab, by default this feature is available.
|
||||
To hide the feature, an administrator can [disable the feature flag](../administration/feature_flags.md) named `forti_token_cloud`.
|
||||
On GitLab.com, this feature is not available. On GitLab Dedicated, this feature is available.
|
||||
```
|
||||
|
||||
When the feature is enabled by default for all offerings:
|
||||
|
||||
```markdown
|
||||
> - [Introduced](issue-link) in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default.
|
||||
> - [Enabled on self-managed and GitLab Dedicated](issue-link) in GitLab 13.8.
|
||||
> - [Enabled on GitLab.com](issue-link) in GitLab 13.9.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is available.
|
||||
To hide the feature, an administrator can [disable the feature flag](../administration/feature_flags.md) named `forti_token_cloud`.
|
||||
On GitLab.com and GitLab Dedicated, this feature is available.
|
||||
```
|
||||
|
||||
And, when the feature is done and fully available to all users:
|
||||
When the flag is removed, add the `Generally available` entry and delete the `FLAG` note:
|
||||
|
||||
```markdown
|
||||
> - Introduced in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default.
|
||||
> - [Enabled on self-managed](https://gitlab.com/issue/etc) in GitLab 13.8.
|
||||
> - [Enabled on GitLab.com](https://gitlab.com/issue/etc) in GitLab 13.9.
|
||||
> - [Introduced](issue-link) in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default.
|
||||
> - [Enabled on self-managed and GitLab Dedicated](issue-link) in GitLab 13.8.
|
||||
> - [Enabled on GitLab.com](issue-link) in GitLab 13.9.
|
||||
> - [Generally available](issue-link) in GitLab 14.0. Feature flag `forti_token_cloud` removed.
|
||||
```
|
||||
|
||||
## Simplify long history
|
||||
|
||||
The history can get long, but you can sometimes simplify or remove entries.
|
||||
The history can get long, but you can sometimes simplify or delete entries.
|
||||
|
||||
Combine entries if they happened in the same release:
|
||||
|
||||
|
|
@ -161,8 +176,8 @@ Combine entries if they happened in the same release:
|
|||
|
||||
```markdown
|
||||
> - [Introduced](issue-link) in GitLab 14.2 [with a flag](../../administration/feature_flags.md) named `ci_include_rules`. Disabled by default.
|
||||
> - [Enabled on GitLab.com](issue-link) in GitLab 14.3.
|
||||
> - [Enabled on self-managed](issue-link) in GitLab 14.3.
|
||||
> - [Enabled on GitLab.com](issue-link) in GitLab 14.3.
|
||||
> - [Enabled on GitLab Dedicated](issue-link) in GitLab 14.3.
|
||||
```
|
||||
|
||||
|
|
@ -170,22 +185,23 @@ Combine entries if they happened in the same release:
|
|||
|
||||
```markdown
|
||||
> - [Introduced](issue-link) in GitLab 14.2 [with a flag](../../administration/feature_flags.md) named `ci_include_rules`. Disabled by default.
|
||||
> - [Enabled on GitLab.com, self-managed, and GitLab Dedicated](issue-link) in GitLab 14.3.
|
||||
> - [Enabled on self-managed, GitLab.com, and GitLab Dedicated](issue-link) in GitLab 14.3.
|
||||
```
|
||||
|
||||
Remove `Enabled on GitLab.com` entries when the feature is enabled by default for all offerings:
|
||||
Delete `Enabled` entries when the feature is enabled by default for all offerings and the flag is removed:
|
||||
|
||||
- Before:
|
||||
|
||||
```markdown
|
||||
> - [Introduced](issue-link) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. Disabled by default.
|
||||
> - [Enabled on GitLab.com](issue-link) in GitLab 15.9.
|
||||
> - [Generally available](issue-link) in GitLab 15.10. Feature flag `ci_hooks_pre_get_sources_script` removed.
|
||||
> - [Enabled on self-managed and GitLab Dedicated](issue-link) in GitLab 15.7.
|
||||
> - [Enabled on GitLab.com](issue-link) in GitLab 15.8.
|
||||
> - [Generally available](issue-link) in GitLab 15.9. Feature flag `ci_hooks_pre_get_sources_script` removed.
|
||||
```
|
||||
|
||||
- After:
|
||||
|
||||
```markdown
|
||||
> - [Introduced](issue-link) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. Disabled by default.
|
||||
> - [Generally available](issue-link) in GitLab 15.10. Feature flag `ci_hooks_pre_get_sources_script` removed.
|
||||
> - [Generally available](issue-link) in GitLab 15.9. Feature flag `ci_hooks_pre_get_sources_script` removed.
|
||||
```
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: Create
|
||||
group: Source Code
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: "How to install Git on your local machine."
|
||||
---
|
||||
|
||||
# Installing Git
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: Create
|
||||
group: Source Code
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: "Use the unstage command in Git to stop tracking your changes to a file."
|
||||
---
|
||||
|
||||
# Unstage a file in Git
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: Create
|
||||
group: Source Code
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: "Define approval rules and limits in GitLab with merge request approval settings. Options include preventing author approval, requiring re-authentication, and removing approvals on new commits."
|
||||
---
|
||||
|
||||
# Merge request approval settings
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: Create
|
||||
group: Code Review
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: "Set auto-merge on a merge request when you have reviewed its content, so it can merge without intervention when all merge checks pass."
|
||||
---
|
||||
|
||||
# Auto-merge
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: Create
|
||||
group: Code Review
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: "Suggest improvements to the code in a merge request, and commit those improvements to the merge request directly from your browser."
|
||||
---
|
||||
|
||||
# Suggest changes
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: Create
|
||||
group: IDE
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: "Write and compile code in your browser, using a secure cloud-based environment."
|
||||
---
|
||||
|
||||
# Remote development
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: Create
|
||||
group: Source Code
|
||||
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments"
|
||||
description: "Use snippets to store and share code, text, and files from your browser. Snippets support version control, commenting, and embedding."
|
||||
---
|
||||
|
||||
# Snippets
|
||||
|
|
|
|||
|
|
@ -1038,21 +1038,6 @@ module Gitlab
|
|||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
def set_full_path(full_path:)
|
||||
return unless full_path.present?
|
||||
|
||||
# This guard avoids Gitaly log/error spam
|
||||
raise NoRepository, 'repository does not exist' unless exists?
|
||||
|
||||
gitaly_repository_client.set_full_path(full_path)
|
||||
end
|
||||
|
||||
def full_path
|
||||
wrapped_gitaly_errors do
|
||||
gitaly_repository_client.full_path
|
||||
end
|
||||
end
|
||||
|
||||
def disconnect_alternates
|
||||
wrapped_gitaly_errors do
|
||||
gitaly_repository_client.disconnect_alternates
|
||||
|
|
|
|||
|
|
@ -251,33 +251,6 @@ module Gitlab
|
|||
gitaly_client_call(@storage, :repository_service, :write_ref, request, timeout: GitalyClient.fast_timeout)
|
||||
end
|
||||
|
||||
def set_full_path(path)
|
||||
gitaly_client_call(
|
||||
@storage,
|
||||
:repository_service,
|
||||
:set_full_path,
|
||||
Gitaly::SetFullPathRequest.new(
|
||||
repository: @gitaly_repo,
|
||||
path: path
|
||||
),
|
||||
timeout: GitalyClient.fast_timeout
|
||||
)
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def full_path
|
||||
response = gitaly_client_call(
|
||||
@storage,
|
||||
:repository_service,
|
||||
:full_path,
|
||||
Gitaly::FullPathRequest.new(repository: @gitaly_repo),
|
||||
timeout: GitalyClient.fast_timeout
|
||||
)
|
||||
|
||||
response.path.presence
|
||||
end
|
||||
|
||||
def find_license
|
||||
request = Gitaly::FindLicenseRequest.new(repository: @gitaly_repo)
|
||||
|
||||
|
|
|
|||
|
|
@ -10774,6 +10774,12 @@ msgstr ""
|
|||
msgid "CiCatalog|Create a pipeline component repository and make reusing pipeline configurations faster and easier."
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|Created and maintained by %{boldStart}GitLab%{boldEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|Created and maintained by a %{boldStart}GitLab Partner%{boldEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|Discover CI/CD components that can improve your pipeline with additional functionality."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -10783,12 +10789,18 @@ msgstr ""
|
|||
msgid "CiCatalog|Get started with the CI/CD Catalog"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|GitLab-maintained"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|Go to the project"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|How do I publish a component?"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|Learn more about designated creators"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|No component available"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -10798,6 +10810,9 @@ msgstr ""
|
|||
msgid "CiCatalog|No result found"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|Partner"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|Publish the CI/CD components in this project to the CI/CD Catalog"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17633,6 +17648,9 @@ msgstr ""
|
|||
msgid "DeploymentApproval| Current approvals: %{current}"
|
||||
msgstr ""
|
||||
|
||||
msgid "DeploymentApproval|After deployment #%{deploymentIid} has the required approvals, you can run the manual job. Rejecting will fail the manual job."
|
||||
msgstr ""
|
||||
|
||||
msgid "DeploymentApproval|Approval options"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17645,9 +17663,6 @@ msgstr ""
|
|||
msgid "DeploymentApproval|Approved by you %{time}"
|
||||
msgstr ""
|
||||
|
||||
msgid "DeploymentApproval|Approving will run the manual job from deployment #%{deploymentIid}. Rejecting will fail the manual job."
|
||||
msgstr ""
|
||||
|
||||
msgid "DeploymentApproval|Deployment approved"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17785,6 +17800,9 @@ msgstr ""
|
|||
msgid "Deployment|Latest Deployed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deployment|Leave feedback"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deployment|Needs Approval"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17850,6 +17868,12 @@ msgstr ""
|
|||
msgid "Deployment|Waiting to be deployed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Deployment|We know this page is a bit empty at the moment, but we're working hard to bring you a great experience! In the meantime, we'd love to hear your feedback."
|
||||
msgstr ""
|
||||
|
||||
msgid "Deployment|What would you like to see here?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deprecated API rate limits"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -468,6 +468,10 @@ module QA
|
|||
api_post_to(api_releases_path, tag_name: tag, ref: ref, **params)
|
||||
end
|
||||
|
||||
def has_release?(tag)
|
||||
releases.any? { |release| release[:tag_name] == tag }
|
||||
end
|
||||
|
||||
def protected_branches
|
||||
response = api_get_from(api_protected_branches_path)
|
||||
parse_body(response)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Verify', :skip_live_env, product_group: :pipeline_authoring do
|
||||
RSpec.describe 'Verify', product_group: :pipeline_authoring do
|
||||
describe 'CI catalog' do
|
||||
let(:project_count) { 3 }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Verify', :runner, :skip_live_env, product_group: :pipeline_authoring do
|
||||
RSpec.describe 'Verify', :runner, product_group: :pipeline_authoring do
|
||||
describe 'CI component' do
|
||||
let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" }
|
||||
let(:tag) { '1.0.0' }
|
||||
|
|
@ -66,6 +66,9 @@ module QA
|
|||
add_ci_file(component_project, 'templates/new-component.yml', component_content)
|
||||
component_project.create_release(tag)
|
||||
|
||||
QA::Runtime::Logger.info("Waiting for #{component_project.name}'s release #{tag} to be available")
|
||||
Support::Waiter.wait_until { component_project.has_release?(tag) }
|
||||
|
||||
test_project.visit!
|
||||
add_ci_file(test_project, '.gitlab-ci.yml', ci_yml_content)
|
||||
end
|
||||
|
|
@ -76,9 +79,11 @@ module QA
|
|||
|
||||
it 'runs in project pipeline with correct inputs', :aggregate_failures,
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/451582' do
|
||||
Flow::Pipeline.visit_latest_pipeline(status: 'Passed')
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |show|
|
||||
Support::Waiter.wait_until { show.has_passed? }
|
||||
|
||||
expect(show).to have_stage(test_stage), "Expected pipeline to have stage #{test_stage} but not found."
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
|||
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
|
||||
import CiResourceHeader from '~/ci/catalog/components/details/ci_resource_header.vue';
|
||||
import CiResourceAbout from '~/ci/catalog/components/details/ci_resource_about.vue';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
|
||||
import CiVerificationBadge from '~/ci/catalog/components/shared/ci_verification_badge.vue';
|
||||
import { catalogSharedDataMock, catalogAdditionalDetailsMock } from '../../mock';
|
||||
|
||||
describe('CiResourceHeader', () => {
|
||||
|
|
@ -26,8 +26,8 @@ describe('CiResourceHeader', () => {
|
|||
const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector);
|
||||
const findAvatar = () => wrapper.findComponent(GlAvatar);
|
||||
const findAvatarLink = () => wrapper.findComponent(GlAvatarLink);
|
||||
const findVerificationBadge = () => wrapper.findComponent(CiVerificationBadge);
|
||||
const findVersionBadge = () => wrapper.findComponent(GlBadge);
|
||||
const findPipelineStatusBadge = () => wrapper.findComponent(CiIcon);
|
||||
|
||||
const createComponent = ({ props = {} } = {}) => {
|
||||
wrapper = shallowMountExtended(CiResourceHeader, {
|
||||
|
|
@ -102,42 +102,32 @@ describe('CiResourceHeader', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when the project has a release', () => {
|
||||
const pipelineStatus = {
|
||||
detailsPath: 'path/to/pipeline',
|
||||
icon: 'status_success',
|
||||
text: 'passed',
|
||||
group: 'success',
|
||||
};
|
||||
describe('verification badge', () => {
|
||||
describe('when the resource is not verified', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('does not render the verification badge', () => {
|
||||
expect(findVerificationBadge().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each`
|
||||
hasPipelineBadge | describeText | testText | status
|
||||
${true} | ${'is'} | ${'renders'} | ${pipelineStatus}
|
||||
${false} | ${'is not'} | ${'does not render'} | ${{}}
|
||||
`('and there $describeText a pipeline', ({ hasPipelineBadge, testText, status }) => {
|
||||
verificationLevel | describeText
|
||||
${'GITLAB'} | ${'GitLab'}
|
||||
${'PARTNER'} | ${'partner'}
|
||||
`('when the resource is $describeText maintained', ({ verificationLevel }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
props: {
|
||||
pipelineStatus: status,
|
||||
latestVersion: { name: '1.0.0', path: 'path/to/release' },
|
||||
},
|
||||
});
|
||||
createComponent({ props: { resource: { ...resource, verificationLevel } } });
|
||||
});
|
||||
|
||||
it('renders the version badge', () => {
|
||||
expect(findVersionBadge().exists()).toBe(true);
|
||||
it('renders the verification badge', () => {
|
||||
expect(findVerificationBadge().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it(`${testText} the pipeline status badge`, () => {
|
||||
expect(findPipelineStatusBadge().exists()).toBe(hasPipelineBadge);
|
||||
if (hasPipelineBadge) {
|
||||
expect(findPipelineStatusBadge().props()).toEqual({
|
||||
showStatusText: true,
|
||||
status: pipelineStatus,
|
||||
showTooltip: true,
|
||||
useLink: true,
|
||||
});
|
||||
}
|
||||
it('displays the correct badge', () => {
|
||||
expect(findVerificationBadge().props('verificationLevel')).toBe(verificationLevel);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
|
|||
import { createRouter } from '~/ci/catalog/router/index';
|
||||
import CiResourcesListItem from '~/ci/catalog/components/list/ci_resources_list_item.vue';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import CiVerificationBadge from '~/ci/catalog/components/shared/ci_verification_badge.vue';
|
||||
import { catalogSinglePageResponse } from '../../mock';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
|
@ -48,6 +49,7 @@ describe('CiResourcesListItem', () => {
|
|||
const findResourceName = () => wrapper.findByTestId('ci-resource-link');
|
||||
const findResourceDescription = () => wrapper.findByText(defaultProps.resource.description);
|
||||
const findUserLink = () => wrapper.findByTestId('user-link');
|
||||
const findVerificationBadge = () => wrapper.findComponent(CiVerificationBadge);
|
||||
const findTimeAgoMessage = () => wrapper.findComponent(GlSprintf);
|
||||
const findFavorites = () => wrapper.findByTestId('stats-favorites');
|
||||
|
||||
|
|
@ -139,6 +141,36 @@ describe('CiResourcesListItem', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('verification badge', () => {
|
||||
describe('when the resource is not verified', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('does not render the verification badge', () => {
|
||||
expect(findVerificationBadge().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each`
|
||||
verificationLevel | describeText
|
||||
${'GITLAB'} | ${'GitLab'}
|
||||
${'PARTNER'} | ${'partner'}
|
||||
`('when the resource is $describeText maintained', ({ verificationLevel }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({ props: { resource: { ...resource, verificationLevel } } });
|
||||
});
|
||||
|
||||
it('renders the verification badge', () => {
|
||||
expect(findVerificationBadge().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('displays the correct badge', () => {
|
||||
expect(findVerificationBadge().props('verificationLevel')).toBe(verificationLevel);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('release time', () => {
|
||||
describe('when there is no release data', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -164,8 +164,6 @@ describe('CiResourceDetailsPage', () => {
|
|||
isLoadingSharedData: false,
|
||||
openIssuesCount: defaultAdditionalData.openIssuesCount,
|
||||
openMergeRequestsCount: defaultAdditionalData.openMergeRequestsCount,
|
||||
pipelineStatus:
|
||||
defaultAdditionalData.versions.nodes[0].commit.pipelines.nodes[0].detailedStatus,
|
||||
resource: defaultSharedData,
|
||||
});
|
||||
});
|
||||
|
|
@ -179,7 +177,7 @@ describe('CiResourceDetailsPage', () => {
|
|||
it('passes expected props', () => {
|
||||
expect(findDetailsComponent().props()).toEqual({
|
||||
resourcePath: cleanLeadingSeparator(defaultSharedData.webPath),
|
||||
version: defaultAdditionalData.versions.nodes[0].name,
|
||||
version: defaultSharedData.versions.nodes[0].name,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
import { GlIcon, GlLink } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import CiVerificationBadge from '~/ci/catalog/components/shared/ci_verification_badge.vue';
|
||||
import { VerificationLevel } from '~/ci/catalog/constants';
|
||||
|
||||
describe('Catalog Verification Badge', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
resourceId: 'gid://gitlab/Ci::Catalog::Resource/36',
|
||||
showText: true,
|
||||
verificationLevel: 'GITLAB',
|
||||
};
|
||||
|
||||
const findVerificationIcon = () => wrapper.findComponent(GlIcon);
|
||||
const findLink = () => wrapper.findComponent(GlLink);
|
||||
const findVerificationText = () => wrapper.findByTestId('verification-badge-text');
|
||||
|
||||
const createComponent = (props = defaultProps) => {
|
||||
wrapper = extendedWrapper(
|
||||
shallowMount(CiVerificationBadge, {
|
||||
propsData: {
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
describe('when the badge is rendered', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders an icon', () => {
|
||||
expect(findVerificationIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders a link', () => {
|
||||
expect(findLink().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('badge text', () => {
|
||||
describe('when showText is true', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders badge text', () => {
|
||||
expect(findVerificationText().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when showText is false', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ ...defaultProps, showText: false });
|
||||
});
|
||||
|
||||
it('does not render badge text', () => {
|
||||
expect(findVerificationText().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe.each`
|
||||
verificationLevel | describeText
|
||||
${'GITLAB'} | ${'GitLab'}
|
||||
${'PARTNER'} | ${'partner'}
|
||||
`('when the resource is $describeText maintained', ({ verificationLevel }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({ ...defaultProps, verificationLevel });
|
||||
});
|
||||
|
||||
it('renders the correct icon', () => {
|
||||
expect(findVerificationIcon().props('name')).toBe(VerificationLevel[verificationLevel].icon);
|
||||
});
|
||||
|
||||
it('displays the correct badge text', () => {
|
||||
expect(findVerificationText().text()).toContain(
|
||||
VerificationLevel[verificationLevel].badgeText,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -96,6 +96,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-42/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -110,6 +111,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-41/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -124,6 +126,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-40/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -138,6 +141,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-39/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -152,6 +156,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-38/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -166,6 +171,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-37/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -180,6 +186,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-36/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -194,6 +201,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-35/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -208,6 +216,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-34/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -222,6 +231,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-33/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -236,6 +246,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-32/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -250,6 +261,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-31/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -264,6 +276,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-30/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -278,6 +291,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-29/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -292,6 +306,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-28/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -306,6 +321,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-27/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -320,6 +336,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-26/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -334,6 +351,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-25/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -348,6 +366,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-24/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -362,6 +381,7 @@ export const catalogResponseBody = {
|
|||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
starrersPath: '/frontend-fixtures/project-23/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -394,6 +414,7 @@ export const catalogSinglePageResponse = {
|
|||
name: 'Project-45 Name',
|
||||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
nodes: [
|
||||
|
|
@ -424,6 +445,7 @@ export const catalogSinglePageResponse = {
|
|||
name: 'Project-44 Name',
|
||||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -437,6 +459,7 @@ export const catalogSinglePageResponse = {
|
|||
name: 'Project-43 Name',
|
||||
description: 'A simple component',
|
||||
starCount: 0,
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
nodes: [],
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
|
|
@ -460,6 +483,7 @@ export const catalogSharedDataMock = {
|
|||
name: 'Ruby',
|
||||
starCount: 1,
|
||||
starrersPath: '/path/to/project/-/starrers',
|
||||
verificationLevel: 'UNVERIFIED',
|
||||
versions: {
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
nodes: [
|
||||
|
|
@ -494,40 +518,6 @@ export const catalogAdditionalDetailsMock = {
|
|||
openIssuesCount: 4,
|
||||
openMergeRequestsCount: 10,
|
||||
readmeHtml: '<h1>Hello world</h1>',
|
||||
versions: {
|
||||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
nodes: [
|
||||
{
|
||||
__typename: 'CiCatalogResourceVersion',
|
||||
id: 'gid://gitlab/Release/3',
|
||||
commit: {
|
||||
__typename: 'Commit',
|
||||
id: 'gid://gitlab/CommitPresenter/afa936495f20e08c26ed4a67130ee2166f94fa6e',
|
||||
pipelines: {
|
||||
__typename: 'PipelineConnection',
|
||||
nodes: [
|
||||
{
|
||||
__typename: 'Pipeline',
|
||||
id: 'gid://gitlab/Ci::Pipeline/583',
|
||||
detailedStatus: {
|
||||
__typename: 'DetailedStatus',
|
||||
id: 'success-583-583',
|
||||
detailsPath: '/root/cicd-circular/-/pipelines/583',
|
||||
icon: 'status_success',
|
||||
text: 'passed',
|
||||
group: 'success',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
name: '1.0.2',
|
||||
path: '/path/to/release',
|
||||
author: { __typename: 'UserCore', id: 1, webUrl: 'profile/1', name: 'username' },
|
||||
createdAt: '2022-08-23T17:19:09Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlAlert } from '@gitlab/ui';
|
||||
import DetailsFeedback from '~/deployments/components/details_feedback.vue';
|
||||
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
|
||||
|
||||
jest.mock('~/sentry/sentry_browser_wrapper');
|
||||
jest.mock('~/graphql_shared/utils');
|
||||
|
||||
describe('~/deployments/components/details_feedback.vue', () => {
|
||||
let wrapper;
|
||||
let dismiss;
|
||||
let dismisserComponent;
|
||||
|
||||
const createComponent = ({ shouldShowCallout = true } = {}) => {
|
||||
dismiss = jest.fn();
|
||||
dismisserComponent = makeMockUserCalloutDismisser({
|
||||
dismiss,
|
||||
shouldShowCallout,
|
||||
});
|
||||
wrapper = shallowMount(DetailsFeedback, {
|
||||
stubs: {
|
||||
UserCalloutDismisser: dismisserComponent,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
|
||||
it('shows an alert', () => {
|
||||
createComponent();
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('calls dismiss when the alert is dismissed', () => {
|
||||
createComponent();
|
||||
findAlert().vm.$emit('dismiss');
|
||||
expect(dismiss).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('links to the feedback issue', () => {
|
||||
createComponent();
|
||||
expect(findAlert().props()).toMatchObject({
|
||||
title: 'What would you like to see here?',
|
||||
primaryButtonText: 'Leave feedback',
|
||||
primaryButtonLink: 'https://gitlab.com/gitlab-org/gitlab/-/issues/450700',
|
||||
});
|
||||
});
|
||||
|
||||
it('hides the alert if already dismissed', () => {
|
||||
createComponent({ shouldShowCallout: false });
|
||||
expect(findAlert().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -9,6 +9,7 @@ import { toggleQueryPollingByVisibility } from '~/graphql_shared/utils';
|
|||
import ShowDeployment from '~/deployments/components/show_deployment.vue';
|
||||
import DeploymentHeader from '~/deployments/components/deployment_header.vue';
|
||||
import DeploymentDeployBlock from '~/deployments/components/deployment_deploy_block.vue';
|
||||
import DetailsFeedback from '~/deployments/components/details_feedback.vue';
|
||||
import deploymentQuery from '~/deployments/graphql/queries/deployment.query.graphql';
|
||||
import environmentQuery from '~/deployments/graphql/queries/environment.query.graphql';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
|
@ -106,6 +107,10 @@ describe('~/deployments/components/show_deployment.vue', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('shows an alert asking for feedback', () => {
|
||||
expect(wrapper.findComponent(DetailsFeedback).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('shows the deployment block if the deployment job is manual', () => {
|
||||
expect(wrapper.findComponent(DeploymentDeployBlock).props()).toEqual({
|
||||
deployment: mockDeploymentFixture.data.project.deployment,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import HomePanelActions from '~/pages/projects/home_panel/components/home_panel_actions.vue';
|
||||
import ForksButton from '~/forks/components/forks_button.vue';
|
||||
import MoreActionsDropdown from '~/groups_projects/components/more_actions_dropdown.vue';
|
||||
import NotificationsDropdown from '~/notifications/components/notifications_dropdown.vue';
|
||||
import StarCount from '~/stars/components/star_count.vue';
|
||||
|
||||
describe('HomePanelActions', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = ({ isLoggedIn = false, provide = {} } = {}) => {
|
||||
if (isLoggedIn) {
|
||||
window.gon.current_user_id = 1;
|
||||
}
|
||||
|
||||
wrapper = shallowMountExtended(HomePanelActions, {
|
||||
provide: {
|
||||
...provide,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findAdminButton = () => wrapper.find('[data-testid="admin-button"]');
|
||||
const findForksButton = () => wrapper.findComponent(ForksButton);
|
||||
const findMoreActionsDropdown = () => wrapper.findComponent(MoreActionsDropdown);
|
||||
const findNotificationsDropdown = () => wrapper.findComponent(NotificationsDropdown);
|
||||
const findStarCount = () => wrapper.findComponent(StarCount);
|
||||
|
||||
describe.each`
|
||||
isLoggedIn | canReadProject | isProjectEmpty | adminPath | isForkButtonVisible | isMoreActionsDropdownVisible | isNotificationDropdownVisible | isStarCountVisible | isAdminButtonVisible
|
||||
${true} | ${true} | ${true} | ${undefined} | ${false} | ${true} | ${true} | ${true} | ${false}
|
||||
${true} | ${true} | ${true} | ${null} | ${false} | ${true} | ${true} | ${true} | ${false}
|
||||
${true} | ${true} | ${true} | ${''} | ${false} | ${true} | ${true} | ${true} | ${false}
|
||||
${true} | ${true} | ${false} | ${''} | ${true} | ${true} | ${true} | ${true} | ${false}
|
||||
${true} | ${false} | ${true} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false}
|
||||
${true} | ${false} | ${false} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false}
|
||||
${true} | ${true} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${true} | ${true} | ${true}
|
||||
${true} | ${true} | ${false} | ${'project/admin/path'} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${true} | ${false} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true}
|
||||
${true} | ${false} | ${false} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true}
|
||||
${false} | ${true} | ${true} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false}
|
||||
${false} | ${true} | ${false} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false}
|
||||
${false} | ${false} | ${true} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false}
|
||||
${false} | ${false} | ${false} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false}
|
||||
${false} | ${true} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true}
|
||||
${false} | ${true} | ${false} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true}
|
||||
${false} | ${false} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true}
|
||||
${false} | ${false} | ${false} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true}
|
||||
`(
|
||||
'renders components',
|
||||
({
|
||||
isLoggedIn,
|
||||
canReadProject,
|
||||
isProjectEmpty,
|
||||
adminPath,
|
||||
isForkButtonVisible,
|
||||
isMoreActionsDropdownVisible,
|
||||
isNotificationDropdownVisible,
|
||||
isStarCountVisible,
|
||||
isAdminButtonVisible,
|
||||
}) => {
|
||||
it('as expected', () => {
|
||||
createComponent({
|
||||
isLoggedIn,
|
||||
provide: {
|
||||
adminPath,
|
||||
canReadProject,
|
||||
isProjectEmpty,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findForksButton().exists()).toBe(isForkButtonVisible);
|
||||
expect(findMoreActionsDropdown().exists()).toBe(isMoreActionsDropdownVisible);
|
||||
expect(findNotificationsDropdown().exists()).toBe(isNotificationDropdownVisible);
|
||||
expect(findStarCount().exists()).toBe(isStarCountVisible);
|
||||
expect(findAdminButton().exists()).toBe(isAdminButtonVisible);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
@ -1,80 +1,19 @@
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import HomePanel from '~/pages/projects/home_panel/components/home_panel.vue';
|
||||
import ForksButton from '~/forks/components/forks_button.vue';
|
||||
import MoreActionsDropdown from '~/groups_projects/components/more_actions_dropdown.vue';
|
||||
import NotificationsDropdown from '~/notifications/components/notifications_dropdown.vue';
|
||||
import StarCount from '~/stars/components/star_count.vue';
|
||||
import HomePanelActions from '~/pages/projects/home_panel/components/home_panel_actions.vue';
|
||||
|
||||
describe('HomePanel', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = ({ isLoggedIn = false, provide = {} } = {}) => {
|
||||
if (isLoggedIn) {
|
||||
window.gon.current_user_id = 1;
|
||||
}
|
||||
|
||||
wrapper = shallowMountExtended(HomePanel, {
|
||||
provide: {
|
||||
...provide,
|
||||
},
|
||||
});
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMountExtended(HomePanel);
|
||||
};
|
||||
|
||||
const findAdminButton = () => wrapper.find('[data-testid="admin-button"]');
|
||||
const findForksButton = () => wrapper.findComponent(ForksButton);
|
||||
const findMoreActionsDropdown = () => wrapper.findComponent(MoreActionsDropdown);
|
||||
const findNotificationsDropdown = () => wrapper.findComponent(NotificationsDropdown);
|
||||
const findStarCount = () => wrapper.findComponent(StarCount);
|
||||
const findHomePanelActions = () => wrapper.findComponent(HomePanelActions);
|
||||
|
||||
describe.each`
|
||||
isLoggedIn | canReadProject | isProjectEmpty | adminPath | isForkButtonVisible | isMoreActionsDropdownVisible | isNotificationDropdownVisible | isStarCountVisible | isAdminButtonVisible
|
||||
${true} | ${true} | ${true} | ${undefined} | ${false} | ${true} | ${true} | ${true} | ${false}
|
||||
${true} | ${true} | ${true} | ${null} | ${false} | ${true} | ${true} | ${true} | ${false}
|
||||
${true} | ${true} | ${true} | ${''} | ${false} | ${true} | ${true} | ${true} | ${false}
|
||||
${true} | ${true} | ${false} | ${''} | ${true} | ${true} | ${true} | ${true} | ${false}
|
||||
${true} | ${false} | ${true} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false}
|
||||
${true} | ${false} | ${false} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false}
|
||||
${true} | ${true} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${true} | ${true} | ${true}
|
||||
${true} | ${true} | ${false} | ${'project/admin/path'} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${true} | ${false} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true}
|
||||
${true} | ${false} | ${false} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true}
|
||||
${false} | ${true} | ${true} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false}
|
||||
${false} | ${true} | ${false} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false}
|
||||
${false} | ${false} | ${true} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false}
|
||||
${false} | ${false} | ${false} | ${''} | ${false} | ${true} | ${false} | ${true} | ${false}
|
||||
${false} | ${true} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true}
|
||||
${false} | ${true} | ${false} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true}
|
||||
${false} | ${false} | ${true} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true}
|
||||
${false} | ${false} | ${false} | ${'project/admin/path'} | ${false} | ${true} | ${false} | ${true} | ${true}
|
||||
`(
|
||||
'renders components',
|
||||
({
|
||||
isLoggedIn,
|
||||
canReadProject,
|
||||
isProjectEmpty,
|
||||
adminPath,
|
||||
isForkButtonVisible,
|
||||
isMoreActionsDropdownVisible,
|
||||
isNotificationDropdownVisible,
|
||||
isStarCountVisible,
|
||||
isAdminButtonVisible,
|
||||
}) => {
|
||||
it('as expected', () => {
|
||||
createComponent({
|
||||
isLoggedIn,
|
||||
provide: {
|
||||
adminPath,
|
||||
canReadProject,
|
||||
isProjectEmpty,
|
||||
},
|
||||
});
|
||||
it('renders components as expected', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findForksButton().exists()).toBe(isForkButtonVisible);
|
||||
expect(findMoreActionsDropdown().exists()).toBe(isMoreActionsDropdownVisible);
|
||||
expect(findNotificationsDropdown().exists()).toBe(isNotificationDropdownVisible);
|
||||
expect(findStarCount().exists()).toBe(isStarCountVisible);
|
||||
expect(findAdminButton().exists()).toBe(isAdminButtonVisible);
|
||||
});
|
||||
},
|
||||
);
|
||||
expect(findHomePanelActions().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2242,52 +2242,6 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
|
|||
end
|
||||
end
|
||||
|
||||
describe '#set_full_path' do
|
||||
let(:full_path) { 'some/path' }
|
||||
|
||||
before do
|
||||
repository.set_full_path(full_path: full_path)
|
||||
end
|
||||
|
||||
it 'writes full_path to gitaly' do
|
||||
repository.set_full_path(full_path: "not-the/real-path.git")
|
||||
|
||||
expect(repository.full_path).to eq('not-the/real-path.git')
|
||||
end
|
||||
|
||||
context 'it is given an empty path' do
|
||||
it 'does not write it to disk' do
|
||||
repository.set_full_path(full_path: "")
|
||||
|
||||
expect(repository.full_path).to eq(full_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'repository does not exist' do
|
||||
it 'raises NoRepository and does not call SetFullPath' do
|
||||
repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '', 'group/project')
|
||||
|
||||
expect(repository.gitaly_repository_client).not_to receive(:set_full_path)
|
||||
|
||||
expect do
|
||||
repository.set_full_path(full_path: 'foo/bar.git')
|
||||
end.to raise_error(Gitlab::Git::Repository::NoRepository)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#full_path' do
|
||||
let(:full_path) { 'some/path' }
|
||||
|
||||
before do
|
||||
repository.set_full_path(full_path: full_path)
|
||||
end
|
||||
|
||||
it 'returns the full path' do
|
||||
expect(repository.full_path).to eq(full_path)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#merge_to_ref' do
|
||||
let(:repository) { mutable_repository }
|
||||
let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
|
||||
|
|
|
|||
|
|
@ -451,31 +451,6 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService, feature_category: :gital
|
|||
end
|
||||
end
|
||||
|
||||
describe '#set_full_path' do
|
||||
let(:path) { 'repo/path' }
|
||||
|
||||
it 'sends a set_full_path message' do
|
||||
expect_any_instance_of(Gitaly::RepositoryService::Stub)
|
||||
.to receive(:set_full_path)
|
||||
.with(gitaly_request_with_params(path: path), kind_of(Hash))
|
||||
.and_return(double)
|
||||
|
||||
client.set_full_path(path)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#full_path' do
|
||||
let(:path) { 'repo/path' }
|
||||
|
||||
it 'sends a full_path message' do
|
||||
expect_any_instance_of(Gitaly::RepositoryService::Stub)
|
||||
.to receive(:full_path)
|
||||
.and_return(double(path: path))
|
||||
|
||||
expect(client.full_path).to eq(path)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#find_license" do
|
||||
it 'sends a find_license request with medium timeout' do
|
||||
expect_any_instance_of(Gitaly::RepositoryService::Stub)
|
||||
|
|
|
|||
|
|
@ -6022,7 +6022,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
|
|||
expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:repository_size, :wiki_size])
|
||||
expect(DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id)
|
||||
expect(AuthorizedProjectUpdate::ProjectRecalculateWorker).to receive(:perform_async).with(project.id)
|
||||
expect(project).to receive(:set_full_path)
|
||||
|
||||
project.after_import
|
||||
end
|
||||
|
|
@ -6175,30 +6174,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
|
|||
end
|
||||
end
|
||||
|
||||
describe '#set_full_path' do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
let(:repository) { project.repository.raw }
|
||||
|
||||
it 'writes full path in .git/config when key is missing' do
|
||||
project.set_full_path
|
||||
|
||||
expect(repository.full_path).to eq project.full_path
|
||||
end
|
||||
|
||||
it 'updates full path in .git/config when key is present' do
|
||||
project.set_full_path(gl_full_path: 'old/path')
|
||||
|
||||
expect { project.set_full_path }.to change { repository.full_path }.from('old/path').to(project.full_path)
|
||||
end
|
||||
|
||||
it 'does not raise an error with an empty repository' do
|
||||
project = create(:project_empty_repo)
|
||||
|
||||
expect { project.set_full_path }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe '#default_branch' do
|
||||
context 'with default_branch_name' do
|
||||
let_it_be_with_refind(:root_group) { create(:group) }
|
||||
|
|
|
|||
|
|
@ -461,10 +461,6 @@ RSpec.describe Projects::CreateService, '#execute', feature_category: :groups_an
|
|||
let(:imported_project) { create_project(user, { name: 'test', import_url: 'http://import-url', import_data: import_data }) }
|
||||
|
||||
it 'does not write repository config' do
|
||||
expect_next_instance_of(Project) do |project|
|
||||
expect(project).not_to receive(:set_full_path)
|
||||
end
|
||||
|
||||
imported_project
|
||||
expect(imported_project.project_namespace).to be_in_sync_with_project(imported_project)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -449,7 +449,6 @@
|
|||
- spec/features/profiles/list_users_comment_template_spec.rb
|
||||
- spec/features/profiles/oauth_applications_spec.rb
|
||||
- spec/features/profiles/password_spec.rb
|
||||
- spec/features/profiles/personal_access_tokens_spec.rb
|
||||
- spec/features/profiles/two_factor_auths_spec.rb
|
||||
- spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
|
||||
- spec/features/profiles/user_creates_comment_template_spec.rb
|
||||
|
|
|
|||
|
|
@ -0,0 +1,150 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'gitlab/dangerfiles/spec_helper'
|
||||
require 'fast_spec_helper'
|
||||
|
||||
require_relative '../../../tooling/danger/master_pipeline_status'
|
||||
|
||||
RSpec.describe Tooling::Danger::MasterPipelineStatus, feature_category: :tooling do
|
||||
include_context 'with dangerfile'
|
||||
|
||||
let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
|
||||
let(:statuses) do
|
||||
[
|
||||
{
|
||||
'name' => 'rubocop',
|
||||
'stage' => 'linter',
|
||||
'status' => 'failed',
|
||||
'last_finished_at' => '2016-08-12 15:23:28 UTC',
|
||||
'last_failed' => {
|
||||
'web_url' => "rubocop_failed_web_url",
|
||||
'finished_at' => "2024-03-25T18:49:52.259Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
'name' => 'rspec',
|
||||
'stage' => 'test',
|
||||
'status' => 'failed',
|
||||
'last_finished_at' => '2016-08-12 15:23:28 UTC',
|
||||
'last_failed' => {
|
||||
'web_url' => "rubocop_failed_web_url",
|
||||
'finished_at' => "2024-03-25T18:49:52.259Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
'name' => 'eslint',
|
||||
'stage' => 'test',
|
||||
'status' => 'success',
|
||||
'last_finished_at' => '2016-08-12 15:23:28 UTC'
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
let(:jobs) do
|
||||
[
|
||||
{ 'name' => 'rubocop', 'stage' => 'linter', 'allow_failure' => false },
|
||||
{ 'name' => 'rspec', 'stage' => 'test', 'allow_failure' => false },
|
||||
{ 'name' => 'eslint', 'stage' => 'linter', 'allow_failure' => false }
|
||||
]
|
||||
end
|
||||
|
||||
let(:file_contents_response) { JSON.pretty_generate(statuses) } # rubocop:disable Gitlab/Json -- JSON is sufficient
|
||||
let(:pipeline_jobs_response) { double('PipelineJobsResponse', auto_paginate: jobs) } # rubocop:disable RSpec/VerifiedDoubles -- type is not relevant
|
||||
|
||||
subject(:master_pipeline_status) { fake_danger.new(helper: fake_helper) }
|
||||
|
||||
before do
|
||||
allow(master_pipeline_status).to receive_message_chain(:gitlab, :api, :pipeline_jobs) do
|
||||
pipeline_jobs_response
|
||||
end
|
||||
|
||||
allow(master_pipeline_status).to receive_message_chain(:gitlab, :api, :file_contents) do
|
||||
file_contents_response
|
||||
end
|
||||
|
||||
allow(fake_helper).to receive(:ci?).and_return(ci_env)
|
||||
end
|
||||
|
||||
describe 'check!' do
|
||||
context 'when not in ci environment' do
|
||||
let(:ci_env) { false }
|
||||
|
||||
it 'does not add the warnings' do
|
||||
expect(master_pipeline_status).not_to receive(:warn)
|
||||
|
||||
master_pipeline_status.check!
|
||||
end
|
||||
end
|
||||
|
||||
context 'when in ci environment' do
|
||||
let(:ci_env) { true }
|
||||
|
||||
context 'when all tests are reported as passed in status page' do
|
||||
let(:statuses) do
|
||||
[
|
||||
{
|
||||
'name' => 'rubocop',
|
||||
'stage' => 'linter',
|
||||
'status' => 'success',
|
||||
'last_finished_at' => '2024-03-25T18:49:52.259Z'
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
it 'does not raise any warning' do
|
||||
expect(master_pipeline_status).not_to receive(:warn)
|
||||
|
||||
master_pipeline_status.check!
|
||||
end
|
||||
end
|
||||
|
||||
context 'when rubocop is reproted to have failed in the pipeline status page' do
|
||||
it 'raises warnings for rubocop' do
|
||||
expect(master_pipeline_status).to receive(:warn).with(
|
||||
<<~MSG
|
||||
The [master pipeline status page](#{described_class::STATUS_PAGE_URL}) reported failures in
|
||||
|
||||
* [rubocop](rubocop_failed_web_url)
|
||||
* [rspec](rubocop_failed_web_url)
|
||||
|
||||
If these jobs fail in your merge request with the same errors, then they are not caused by your changes.
|
||||
Please check for any on-going incidents in the [incident issue tracker](#{described_class::INCIDENT_TRACKER_URL}) or in the `#master-broken` Slack channel.
|
||||
MSG
|
||||
)
|
||||
|
||||
master_pipeline_status.check!
|
||||
end
|
||||
end
|
||||
|
||||
context 'when api returns error when fetching pipeline status' do
|
||||
let(:file_contents_response) { raise StandardError, 'Failed to fetch file_contents' }
|
||||
|
||||
it 'does not raise any warning' do
|
||||
expect(master_pipeline_status).not_to receive(:warn)
|
||||
|
||||
master_pipeline_status.check!
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status file does not contain a valid JSON' do
|
||||
let(:file_contents_response) { '{' }
|
||||
|
||||
it 'does not raise any warning' do
|
||||
expect(master_pipeline_status).not_to receive(:warn)
|
||||
|
||||
master_pipeline_status.check!
|
||||
end
|
||||
end
|
||||
|
||||
context 'when api returns error when fetching pipeline jobs' do
|
||||
let(:pipeline_jobs_response) { raise StandardError, 'Failed to fetch pipeline jobs' }
|
||||
|
||||
it 'does not raise any warning' do
|
||||
expect(master_pipeline_status).not_to receive(:warn)
|
||||
|
||||
master_pipeline_status.check!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'gitlab'
|
||||
|
||||
module Tooling
|
||||
module Danger
|
||||
module MasterPipelineStatus
|
||||
MASTER_PIPELINE_STATUS_PROJECT_ID = '40549124' # https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents
|
||||
MASTER_PIPELINE_STATUS_BRANCH = 'master-pipeline-status'
|
||||
MASTER_PIPELINE_STATUS_FILE_NAME = 'canonical-gitlab-master-pipeline-status.json'
|
||||
STATUS_PAGE_URL = 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/raw/master-pipeline-status/canonical-gitlab-master-pipeline-status.json'
|
||||
INCIDENT_TRACKER_URL = 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues'
|
||||
|
||||
def check!
|
||||
return unless helper.ci?
|
||||
|
||||
failed_master_pipeline_jobs = master_pipeline_jobs_statuses.select { |job| job['status'] == 'failed' }
|
||||
return if failed_master_pipeline_jobs.empty?
|
||||
|
||||
pipeline_jobs = pipeline_jobs(ENV['CI_PROJECT_ID'], ENV['CI_PIPELINE_ID'])
|
||||
return if pipeline_jobs.empty?
|
||||
|
||||
job_statuses_to_warn = impacted_job_statuses(pipeline_jobs, failed_master_pipeline_jobs)
|
||||
warn(construct_message(job_statuses_to_warn)) if job_statuses_to_warn.any?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def master_pipeline_jobs_statuses
|
||||
status_file_content = begin
|
||||
gitlab.api.file_contents(
|
||||
MASTER_PIPELINE_STATUS_PROJECT_ID,
|
||||
MASTER_PIPELINE_STATUS_FILE_NAME,
|
||||
MASTER_PIPELINE_STATUS_BRANCH
|
||||
)
|
||||
rescue StandardError
|
||||
'[]'
|
||||
end
|
||||
|
||||
JSON.parse(status_file_content)
|
||||
rescue JSON::ParserError
|
||||
[]
|
||||
end
|
||||
|
||||
def pipeline_jobs(project_id, pipeline_id)
|
||||
gitlab.api.pipeline_jobs(project_id, pipeline_id).auto_paginate
|
||||
rescue StandardError
|
||||
[]
|
||||
end
|
||||
|
||||
def impacted_job_statuses(pipeline_jobs, failed_jobs_statuses)
|
||||
failed_jobs_statuses.select do |failed_job_status|
|
||||
pipeline_jobs.any? do |job|
|
||||
failed_job_status['name'] == job['name'] &&
|
||||
failed_job_status['stage'] == job['stage'] &&
|
||||
!job['allow_failure']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def construct_message(job_statuses_to_warn)
|
||||
job_list = job_statuses_to_warn.map do |job|
|
||||
"* [#{job['name']}](#{job.dig('last_failed', 'web_url')})"
|
||||
end.join("\n")
|
||||
|
||||
<<~MSG
|
||||
The [master pipeline status page](#{STATUS_PAGE_URL}) reported failures in
|
||||
|
||||
#{job_list}
|
||||
|
||||
If these jobs fail in your merge request with the same errors, then they are not caused by your changes.
|
||||
Please check for any on-going incidents in the [incident issue tracker](#{INCIDENT_TRACKER_URL}) or in the `#master-broken` Slack channel.
|
||||
MSG
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -8,6 +8,7 @@ module Tooling
|
|||
ci_templates
|
||||
datateam
|
||||
feature_flag
|
||||
master_pipeline_status
|
||||
roulette
|
||||
sidekiq_queues
|
||||
specialization_labels
|
||||
|
|
|
|||
Loading…
Reference in New Issue