Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-05-06 15:09:05 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-06 15:09:05 +0300
commit669ad9e431c7647f01bda681aab2c0ad2cb58826 (patch)
tree13fac2c7a8f4c4f8f715ed0dd351470dee3918a1
parent3c21cbd6a81d7b962a5d9a72ba8e03f3bf449af8 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/access_tokens/components/projects_token_selector.vue2
-rw-r--r--app/assets/javascripts/admin/statistics_panel/components/app.vue2
-rw-r--r--app/assets/javascripts/blob/components/blob_content.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_card_inner.vue2
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue2
-rw-r--r--app/assets/javascripts/clusters/agents/components/activity_events_list.vue2
-rw-r--r--app/assets/javascripts/clusters/agents/components/show.vue2
-rw-r--r--app/assets/javascripts/clusters_list/components/agents.vue2
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters.vue2
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_view_all.vue2
-rw-r--r--app/assets/javascripts/content_editor/components/loading_indicator.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/jobs/failed_jobs_app.vue73
-rw-r--r--app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue111
-rw-r--r--app/assets/javascripts/pipelines/components/jobs/utils.js33
-rw-r--r--app/assets/javascripts/pipelines/constants.js24
-rw-r--r--app/assets/javascripts/pipelines/graphql/mutations/retry_failed_job.mutation.graphql12
-rw-r--r--app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql41
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js14
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_failed_jobs.js36
-rw-r--r--app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql2
-rw-r--r--app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql2
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue2
-rw-r--r--app/controllers/projects/pipelines_controller.rb1
-rw-r--r--app/controllers/projects/services_controller.rb14
-rw-r--r--app/controllers/projects/settings/operations_controller.rb8
-rw-r--r--app/graphql/types/ci/runner_type.rb6
-rw-r--r--app/helpers/ci/builds_helper.rb10
-rw-r--r--app/views/admin/application_settings/general.html.haml2
-rw-r--r--app/views/groups/milestones/index.html.haml2
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml72
-rw-r--r--app/views/projects/settings/operations/_prometheus.html.haml17
-rw-r--r--app/views/projects/settings/operations/show.html.haml1
-rw-r--r--app/views/shared/_milestones_sort_dropdown.html.haml2
-rw-r--r--config/feature_flags/development/failed_jobs_tab_vue.yml (renamed from config/feature_flags/development/settings_operations_prometheus_service.yml)10
-rw-r--r--config/feature_flags/development/omniauth_initializer_fullhost_proc.yml4
-rw-r--r--config/initializers/0_marginalia.rb4
-rw-r--r--doc/administration/monitoring/performance/request_profiling.md12
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/api/linked_epics.md6
-rw-r--r--doc/user/admin_area/license_file.md3
-rw-r--r--doc/user/group/epics/linked_epics.md8
-rw-r--r--lefthook.yml2
-rw-r--r--locale/gitlab.pot32
-rw-r--r--qa/qa.rb5
-rw-r--r--qa/qa/scenario/template.rb5
-rw-r--r--qa/qa/specs/knapsack_runner.rb1
-rw-r--r--qa/qa/support/knapsack_report.rb2
-rw-r--r--qa/tasks/knapsack.rake3
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb13
-rw-r--r--spec/controllers/projects/services_controller_spec.rb82
-rw-r--r--spec/controllers/projects/settings/operations_controller_spec.rb31
-rw-r--r--spec/features/projects/integrations/user_activates_prometheus_spec.rb5
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb39
-rw-r--r--spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js87
-rw-r--r--spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js117
-rw-r--r--spec/frontend/pipelines/components/jobs/utils_spec.js14
-rw-r--r--spec/frontend/pipelines/mock_data.js215
-rw-r--r--spec/graphql/types/ci/runner_type_spec.rb2
-rw-r--r--spec/helpers/ci/builds_helper_spec.rb14
-rw-r--r--spec/lib/gitlab/background_migration/extract_project_topics_into_separate_table_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/branch_check_spec.rb6
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb28
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb4
-rw-r--r--spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb2
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb6
-rw-r--r--spec/lib/gitlab/popen_spec.rb2
-rw-r--r--spec/lib/gitlab/tracking/event_definition_spec.rb2
-rw-r--r--spec/migrations/finalize_project_namespaces_backfill_spec.rb2
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb2
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb2
-rw-r--r--spec/models/environment_spec.rb2
-rw-r--r--spec/models/integration_spec.rb2
-rw-r--r--spec/models/packages/package_spec.rb2
-rw-r--r--spec/models/project_spec.rb2
-rw-r--r--spec/models/protected_branch/push_access_level_spec.rb2
-rw-r--r--spec/models/release_spec.rb5
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb25
-rw-r--r--spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb2
-rw-r--r--spec/services/clusters/agents/delete_service_spec.rb2
-rw-r--r--spec/support/rspec.rb2
-rw-r--r--spec/support/shared_contexts/email_shared_context.rb2
-rw-r--r--spec/support_specs/helpers/active_record/query_recorder_spec.rb10
-rw-r--r--spec/views/admin/application_settings/general.html.haml_spec.rb22
-rw-r--r--spec/views/projects/settings/operations/show.html.haml_spec.rb24
-rw-r--r--spec/workers/clusters/applications/activate_service_worker_spec.rb2
-rw-r--r--spec/workers/delete_diff_files_worker_spec.rb6
89 files changed, 1049 insertions, 352 deletions
diff --git a/app/assets/javascripts/access_tokens/components/projects_token_selector.vue b/app/assets/javascripts/access_tokens/components/projects_token_selector.vue
index a746f62b3a1..4843c52fcbb 100644
--- a/app/assets/javascripts/access_tokens/components/projects_token_selector.vue
+++ b/app/assets/javascripts/access_tokens/components/projects_token_selector.vue
@@ -148,7 +148,7 @@ export default {
</template>
<template #dropdown-footer>
<gl-intersection-observer v-if="projects.pageInfo.hasNextPage" @appear="loadMoreProjects">
- <gl-loading-icon v-if="isLoadingMoreProjects" size="md" />
+ <gl-loading-icon v-if="isLoadingMoreProjects" class="gl-mb-3" size="sm" />
</gl-intersection-observer>
</template>
</gl-token-selector>
diff --git a/app/assets/javascripts/admin/statistics_panel/components/app.vue b/app/assets/javascripts/admin/statistics_panel/components/app.vue
index 1f0db422807..f250bdae4f5 100644
--- a/app/assets/javascripts/admin/statistics_panel/components/app.vue
+++ b/app/assets/javascripts/admin/statistics_panel/components/app.vue
@@ -29,7 +29,7 @@ export default {
<div class="gl-card">
<div class="gl-card-body">
<h4>{{ __('Statistics') }}</h4>
- <gl-loading-icon v-if="isLoading" size="md" class="my-3" />
+ <gl-loading-icon v-if="isLoading" size="lg" class="my-3" />
<template v-else>
<p
v-for="statistic in getStatistics(statisticsLabels)"
diff --git a/app/assets/javascripts/blob/components/blob_content.vue b/app/assets/javascripts/blob/components/blob_content.vue
index 9832ebbea5c..f032e2e7fb8 100644
--- a/app/assets/javascripts/blob/components/blob_content.vue
+++ b/app/assets/javascripts/blob/components/blob_content.vue
@@ -66,7 +66,7 @@ export default {
</script>
<template>
<div class="blob-viewer" :data-type="activeViewer.type" :data-loaded="!loading">
- <gl-loading-icon v-if="loading" size="md" color="dark" class="my-4 mx-auto" />
+ <gl-loading-icon v-if="loading" size="lg" color="dark" class="my-4 mx-auto" />
<template v-else>
<blob-content-error
diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue
index af1d0bf0807..98ce1ac7f97 100644
--- a/app/assets/javascripts/boards/components/board_card_inner.vue
+++ b/app/assets/javascripts/boards/components/board_card_inner.vue
@@ -253,7 +253,7 @@ export default {
<div
class="gl-display-flex align-items-start flex-wrap-reverse board-card-number-container gl-overflow-hidden"
>
- <gl-loading-icon v-if="item.isLoading" size="md" class="mt-3" />
+ <gl-loading-icon v-if="item.isLoading" size="lg" class="mt-3" />
<span
v-if="item.referencePath"
class="board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3"
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index f1cc7ae7d75..247910301e7 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -140,7 +140,7 @@ export default {
<span class="gl-text-gray-500">{{ $options.i18n.emptySearchResult }}</span>
</gl-dropdown-text>
<gl-intersection-observer v-if="hasNextPage" @appear="loadMoreProjects">
- <gl-loading-icon v-if="groupProjectsFlags.isLoadingMore" size="md" />
+ <gl-loading-icon v-if="groupProjectsFlags.isLoadingMore" size="lg" />
</gl-intersection-observer>
</gl-dropdown>
</div>
diff --git a/app/assets/javascripts/clusters/agents/components/activity_events_list.vue b/app/assets/javascripts/clusters/agents/components/activity_events_list.vue
index d996da259f5..18c6503bfb2 100644
--- a/app/assets/javascripts/clusters/agents/components/activity_events_list.vue
+++ b/app/assets/javascripts/clusters/agents/components/activity_events_list.vue
@@ -124,7 +124,7 @@ export default {
<template>
<div>
- <gl-loading-icon v-if="isLoading" size="md" />
+ <gl-loading-icon v-if="isLoading" size="lg" />
<div v-else-if="hasEvents">
<div
diff --git a/app/assets/javascripts/clusters/agents/components/show.vue b/app/assets/javascripts/clusters/agents/components/show.vue
index 5df3e0811a5..e3de8339325 100644
--- a/app/assets/javascripts/clusters/agents/components/show.vue
+++ b/app/assets/javascripts/clusters/agents/components/show.vue
@@ -140,7 +140,7 @@ export default {
</span>
</template>
- <gl-loading-icon v-if="isLoading" size="md" class="gl-m-3" />
+ <gl-loading-icon v-if="isLoading" size="lg" class="gl-m-3" />
<div v-else>
<token-table :tokens="tokens" :cluster-agent-id="clusterAgent.id" :cursor="cursor" />
diff --git a/app/assets/javascripts/clusters_list/components/agents.vue b/app/assets/javascripts/clusters_list/components/agents.vue
index 89b18ed6d06..8a4a81d3e96 100644
--- a/app/assets/javascripts/clusters_list/components/agents.vue
+++ b/app/assets/javascripts/clusters_list/components/agents.vue
@@ -175,7 +175,7 @@ export default {
</script>
<template>
- <gl-loading-icon v-if="isLoading" size="md" />
+ <gl-loading-icon v-if="isLoading" size="lg" />
<section v-else-if="agentList">
<div v-if="agentList.length">
diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue
index 59cfdde731d..02eac5a6268 100644
--- a/app/assets/javascripts/clusters_list/components/clusters.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters.vue
@@ -224,7 +224,7 @@ export default {
</script>
<template>
- <gl-loading-icon v-if="loadingClusters" size="md" />
+ <gl-loading-icon v-if="loadingClusters" size="lg" />
<section v-else>
<ancestor-notice />
diff --git a/app/assets/javascripts/clusters_list/components/clusters_view_all.vue b/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
index 73ca804e111..d831d79b994 100644
--- a/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
@@ -89,7 +89,7 @@ export default {
</script>
<template>
<div>
- <gl-loading-icon v-if="isLoading" size="md" />
+ <gl-loading-icon v-if="isLoading" size="lg" />
<div v-show="!isLoading" data-testid="clusters-cards-container">
<gl-card
header-class="gl-bg-white gl-display-flex gl-align-items-center gl-justify-content-space-between gl-py-4"
diff --git a/app/assets/javascripts/content_editor/components/loading_indicator.vue b/app/assets/javascripts/content_editor/components/loading_indicator.vue
index 620324adb06..7bc953e0dc3 100644
--- a/app/assets/javascripts/content_editor/components/loading_indicator.vue
+++ b/app/assets/javascripts/content_editor/components/loading_indicator.vue
@@ -34,7 +34,7 @@ export default {
class="gl-w-full gl-display-flex gl-justify-content-center gl-align-items-center gl-absolute gl-top-0 gl-bottom-0"
>
<div class="gl-bg-white gl-absolute gl-w-full gl-h-full gl-opacity-3"></div>
- <gl-loading-icon size="md" />
+ <gl-loading-icon size="lg" />
</div>
</editor-state-observer>
</template>
diff --git a/app/assets/javascripts/pipelines/components/jobs/failed_jobs_app.vue b/app/assets/javascripts/pipelines/components/jobs/failed_jobs_app.vue
new file mode 100644
index 00000000000..9e886fd7a48
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/jobs/failed_jobs_app.vue
@@ -0,0 +1,73 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import createFlash from '~/flash';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import GetFailedJobsQuery from '../../graphql/queries/get_failed_jobs.query.graphql';
+import { prepareFailedJobs } from './utils';
+import FailedJobsTable from './failed_jobs_table.vue';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ FailedJobsTable,
+ },
+ inject: {
+ fullPath: {
+ default: '',
+ },
+ pipelineIid: {
+ default: '',
+ },
+ },
+ props: {
+ failedJobsSummary: {
+ type: Array,
+ required: true,
+ },
+ },
+ apollo: {
+ failedJobs: {
+ query: GetFailedJobsQuery,
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ pipelineIid: this.pipelineIid,
+ };
+ },
+ update({ project }) {
+ if (project?.pipeline?.jobs?.nodes) {
+ return project.pipeline.jobs.nodes.map((job) => {
+ return { normalizedId: getIdFromGraphQLId(job.id), ...job };
+ });
+ }
+ return [];
+ },
+ result() {
+ this.preparedFailedJobs = prepareFailedJobs(this.failedJobs, this.failedJobsSummary);
+ },
+ error() {
+ createFlash({ message: s__('Jobs|There was a problem fetching the failed jobs.') });
+ },
+ },
+ },
+ data() {
+ return {
+ failedJobs: [],
+ preparedFailedJobs: [],
+ };
+ },
+ computed: {
+ loading() {
+ return this.$apollo.queries.failedJobs.loading;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-loading-icon v-if="loading" size="lg" class="gl-mt-4" />
+ <failed-jobs-table v-else :failed-jobs="preparedFailedJobs" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue b/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue
new file mode 100644
index 00000000000..1c646bdf3d6
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue
@@ -0,0 +1,111 @@
+<script>
+import { GlButton, GlLink, GlSafeHtmlDirective, GlTableLite } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import createFlash from '~/flash';
+import { redirectTo } from '~/lib/utils/url_utility';
+import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
+import RetryFailedJobMutation from '../../graphql/mutations/retry_failed_job.mutation.graphql';
+import { DEFAULT_FIELDS } from '../../constants';
+
+export default {
+ fields: DEFAULT_FIELDS,
+ retry: __('Retry'),
+ components: {
+ CiBadge,
+ GlButton,
+ GlLink,
+ GlTableLite,
+ },
+ directives: {
+ SafeHtml: GlSafeHtmlDirective,
+ },
+ props: {
+ failedJobs: {
+ type: Array,
+ required: true,
+ },
+ },
+ methods: {
+ async retryJob(id) {
+ try {
+ const {
+ data: {
+ jobRetry: { errors, job },
+ },
+ } = await this.$apollo.mutate({
+ mutation: RetryFailedJobMutation,
+ variables: { id },
+ });
+ if (errors.length > 0) {
+ this.showErrorMessage();
+ } else {
+ redirectTo(job.detailedStatus.detailsPath);
+ }
+ } catch {
+ this.showErrorMessage();
+ }
+ },
+ canRetryJob(job) {
+ return job.retryable && job.userPermissions.updateBuild;
+ },
+ showErrorMessage() {
+ createFlash({ message: s__('Job|There was a problem retrying the failed job.') });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-table-lite :items="failedJobs" :fields="$options.fields" stacked="lg" fixed>
+ <template #table-colgroup="{ fields }">
+ <col v-for="field in fields" :key="field.key" :class="field.columnClass" />
+ </template>
+
+ <template #cell(name)="{ item }">
+ <div
+ class="gl-display-flex gl-align-items-center gl-lg-justify-content-start gl-justify-content-end"
+ >
+ <ci-badge :status="item.detailedStatus" :show-text="false" class="gl-mr-3" />
+ <div class="gl-text-truncate">
+ <gl-link
+ :href="item.detailedStatus.detailsPath"
+ class="gl-font-weight-bold gl-text-gray-900!"
+ >
+ {{ item.name }}
+ </gl-link>
+ </div>
+ </div>
+ </template>
+
+ <template #cell(stage)="{ item }">
+ <div class="gl-text-truncate">
+ <span>{{ item.stage.name }}</span>
+ </div>
+ </template>
+
+ <template #cell(failure)="{ item }">
+ <span>{{ item.failure }}</span>
+ </template>
+
+ <template #cell(actions)="{ item }">
+ <gl-button
+ v-if="canRetryJob(item)"
+ icon="repeat"
+ :title="$options.retry"
+ :aria-label="$options.retry"
+ @click="retryJob(item.id)"
+ />
+ </template>
+
+ <template #row-details="{ item }">
+ <pre
+ v-if="item.userPermissions.readBuild"
+ class="gl-w-full gl-text-left gl-border-none"
+ data-testid="job-log"
+ >
+ <code v-safe-html="item.failureSummary" class="gl-reset-bg gl-p-0" >
+ </code>
+ </pre>
+ </template>
+ </gl-table-lite>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/jobs/utils.js b/app/assets/javascripts/pipelines/components/jobs/utils.js
new file mode 100644
index 00000000000..c8414d44d14
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/jobs/utils.js
@@ -0,0 +1,33 @@
+/*
+ We get the failure and failure summary from Rails which has
+ a summary failure log. Here we combine that data with the data
+ from GraphQL to display the log.
+
+ failedJobs is from GraphQL
+ failedJobsSummary is from Rails
+ */
+
+export const prepareFailedJobs = (failedJobs = [], failedJobsSummary = []) => {
+ const combinedJobs = [];
+
+ if (failedJobs.length > 0 && failedJobsSummary.length > 0) {
+ failedJobs.forEach((failedJob) => {
+ const foundJob = failedJobsSummary.find(
+ (failedJobSummary) => failedJob.normalizedId === failedJobSummary.id,
+ );
+
+ if (foundJob) {
+ combinedJobs.push({
+ ...failedJob,
+ failure: foundJob?.failure,
+ failureSummary: foundJob?.failure_summary,
+ // this field is needed for the slot row-details
+ // on the failed_jobs_table.vue component
+ _showDetails: true,
+ });
+ }
+ });
+ }
+
+ return combinedJobs;
+};
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index 8da6bf6d4c4..0510992e962 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -85,3 +85,27 @@ export const TOAST_MESSAGE = s__('Pipeline|Creating pipeline.');
export const BUTTON_TOOLTIP_RETRY = __('Retry failed jobs');
export const BUTTON_TOOLTIP_CANCEL = __('Cancel');
+
+export const DEFAULT_FIELDS = [
+ {
+ key: 'name',
+ label: __('Name'),
+ columnClass: 'gl-w-20p',
+ },
+ {
+ key: 'stage',
+ label: __('Stage'),
+ columnClass: 'gl-w-20p',
+ },
+ {
+ key: 'failure',
+ label: __('Failure'),
+ columnClass: 'gl-w-40p',
+ },
+ {
+ key: 'actions',
+ label: '',
+ tdClass: 'gl-text-right',
+ columnClass: 'gl-w-20p',
+ },
+];
diff --git a/app/assets/javascripts/pipelines/graphql/mutations/retry_failed_job.mutation.graphql b/app/assets/javascripts/pipelines/graphql/mutations/retry_failed_job.mutation.graphql
new file mode 100644
index 00000000000..1955cc9b0ac
--- /dev/null
+++ b/app/assets/javascripts/pipelines/graphql/mutations/retry_failed_job.mutation.graphql
@@ -0,0 +1,12 @@
+mutation retryFailedJob($id: CiBuildID!) {
+ jobRetry(input: { id: $id }) {
+ job {
+ id
+ detailedStatus {
+ id
+ detailsPath
+ }
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql
new file mode 100644
index 00000000000..14e9a838f4b
--- /dev/null
+++ b/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql
@@ -0,0 +1,41 @@
+query getFailedJobs($fullPath: ID!, $pipelineIid: ID!) {
+ project(fullPath: $fullPath) {
+ id
+ pipeline(iid: $pipelineIid) {
+ id
+ jobs(statuses: FAILED) {
+ nodes {
+ status
+ detailedStatus {
+ id
+ detailsPath
+ group
+ icon
+ label
+ text
+ tooltip
+ action {
+ id
+ buttonTitle
+ icon
+ method
+ path
+ title
+ }
+ }
+ id
+ stage {
+ id
+ name
+ }
+ name
+ retryable
+ userPermissions {
+ readBuild
+ updateBuild
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index 338de65e795..fd869014570 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -1,10 +1,11 @@
import createFlash from '~/flash';
-import { __ } from '~/locale';
+import { __, s__ } from '~/locale';
import createDagApp from './pipeline_details_dag';
import { createPipelinesDetailApp } from './pipeline_details_graph';
import { createPipelineHeaderApp } from './pipeline_details_header';
import { createPipelineNotificationApp } from './pipeline_details_notification';
import { createPipelineJobsApp } from './pipeline_details_jobs';
+import { createPipelineFailedJobsApp } from './pipeline_details_failed_jobs';
import { apolloProvider } from './pipeline_shared_client';
import { createTestDetails } from './pipeline_test_details';
@@ -16,6 +17,7 @@ const SELECTORS = {
PIPELINE_TABS: '#js-pipeline-tabs',
PIPELINE_TESTS: '#js-pipeline-tests-detail',
PIPELINE_JOBS: '#js-pipeline-jobs-vue',
+ PIPELINE_FAILED_JOBS: '#js-pipeline-failed-jobs-vue',
};
export default async function initPipelineDetailsBundle() {
@@ -79,5 +81,15 @@ export default async function initPipelineDetailsBundle() {
message: __('An error occurred while loading the Jobs tab.'),
});
}
+
+ if (gon.features?.failedJobsTabVue) {
+ try {
+ createPipelineFailedJobsApp(SELECTORS.PIPELINE_FAILED_JOBS);
+ } catch {
+ createFlash({
+ message: s__('Jobs|An error occurred while loading the Failed Jobs tab.'),
+ });
+ }
+ }
}
}
diff --git a/app/assets/javascripts/pipelines/pipeline_details_failed_jobs.js b/app/assets/javascripts/pipelines/pipeline_details_failed_jobs.js
new file mode 100644
index 00000000000..7bf3b64bf47
--- /dev/null
+++ b/app/assets/javascripts/pipelines/pipeline_details_failed_jobs.js
@@ -0,0 +1,36 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import FailedJobsApp from './components/jobs/failed_jobs_app.vue';
+
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
+
+export const createPipelineFailedJobsApp = (selector) => {
+ const containerEl = document.querySelector(selector);
+
+ if (!containerEl) {
+ return false;
+ }
+
+ const { fullPath, pipelineIid, failedJobsSummaryData } = containerEl.dataset;
+
+ return new Vue({
+ el: containerEl,
+ apolloProvider,
+ provide: {
+ fullPath,
+ pipelineIid,
+ },
+ render(createElement) {
+ return createElement(FailedJobsApp, {
+ props: {
+ failedJobsSummary: JSON.parse(failedJobsSummaryData),
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql b/app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql
index 1a7a9629b07..d9b9c04fd63 100644
--- a/app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql
+++ b/app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql
@@ -1,4 +1,4 @@
-mutation mergeRequestRemoveAttentionRequest($projectPath: ID!, $iid: String!, $userId: ID!) {
+mutation mergeRequestRemoveAttentionRequest($projectPath: ID!, $iid: String!, $userId: UserID!) {
mergeRequestRemoveAttentionRequest(
input: { projectPath: $projectPath, iid: $iid, userId: $userId }
) {
diff --git a/app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql b/app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql
index 99f9beea17b..99a86e4fe5c 100644
--- a/app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql
+++ b/app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql
@@ -1,4 +1,4 @@
-mutation mergeRequestRequestAttention($projectPath: ID!, $iid: String!, $userId: ID!) {
+mutation mergeRequestRequestAttention($projectPath: ID!, $iid: String!, $userId: UserID!) {
mergeRequestRequestAttention(input: { projectPath: $projectPath, iid: $iid, userId: $userId }) {
errors
}
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index ebbc1bfb037..388353bc35b 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -172,7 +172,9 @@ export default {
:img-src="author.avatar_url"
:img-alt="userImageAltDescription"
:tooltip-text="author.username"
+ :img-size="16"
class="avatar-image-container text-decoration-none"
+ img-css-classes="gl-mr-3"
/>
<tooltip-on-truncate :title="title" class="flex-truncate-child">
<gl-link :href="commitUrl" class="commit-row-message cgray">{{ title }}</gl-link>
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index f4e7755c838..a958f10e778 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -23,6 +23,7 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:pipeline_tabs_vue, @project, default_enabled: :yaml)
push_frontend_feature_flag(:downstream_retry_action, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:failed_jobs_tab_vue, @project, default_enabled: :yaml)
end
# Will be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/225596
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 1321111faaf..8f83e34411b 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -10,8 +10,6 @@ class Projects::ServicesController < Projects::ApplicationController
before_action :integration
before_action :default_integration, only: [:edit, :update]
before_action :web_hook_logs, only: [:edit, :update]
- before_action :set_deprecation_notice_for_prometheus_integration, only: [:edit, :update]
- before_action :redirect_deprecated_prometheus_integration, only: [:update]
respond_to :html
@@ -118,18 +116,6 @@ class Projects::ServicesController < Projects::ApplicationController
.merge(errors: integration.errors.as_json)
end
- def redirect_deprecated_prometheus_integration
- redirect_to edit_project_integration_path(project, integration) if integration.is_a?(::Integrations::Prometheus) && Feature.enabled?(:settings_operations_prometheus_service, project)
- end
-
- def set_deprecation_notice_for_prometheus_integration
- return if !integration.is_a?(::Integrations::Prometheus) || !Feature.enabled?(:settings_operations_prometheus_service, project)
-
- operations_link_start = "<a href=\"#{project_settings_operations_path(project)}\">"
- message = s_('PrometheusService|You can now manage your Prometheus settings on the %{operations_link_start}Operations%{operations_link_end} page. Fields on this page have been deprecated.') % { operations_link_start: operations_link_start, operations_link_end: "</a>" }
- flash.now[:alert] = message.html_safe
- end
-
def use_inherited_settings?(attributes)
default_integration && attributes[:inherit_from_id] == default_integration.id.to_s
end
diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb
index 22b5d217af9..d4126cbd708 100644
--- a/app/controllers/projects/settings/operations_controller.rb
+++ b/app/controllers/projects/settings/operations_controller.rb
@@ -134,7 +134,7 @@ module Projects
# overridden in EE
def permitted_project_params
- project_params = {
+ {
incident_management_setting_attributes: ::Gitlab::Tracking::IncidentManagement.tracking_keys.keys,
metrics_setting_attributes: [:external_dashboard_url, :dashboard_timezone],
@@ -150,12 +150,6 @@ module Projects
grafana_integration_attributes: [:token, :grafana_url, :enabled],
tracing_setting_attributes: [:external_url]
}
-
- if Feature.enabled?(:settings_operations_prometheus_service, project)
- project_params[:prometheus_integration_attributes] = [:manual_configuration, :api_url]
- end
-
- project_params
end
end
end
diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb
index bdc5bfa62eb..9c604209409 100644
--- a/app/graphql/types/ci/runner_type.rb
+++ b/app/graphql/types/ci/runner_type.rb
@@ -35,6 +35,12 @@ module Types
field :executor_name, GraphQL::Types::String, null: true,
description: 'Executor last advertised by the runner.',
method: :executor_name
+ field :platform_name, GraphQL::Types::String, null: true,
+ description: 'Platform provided by the runner.',
+ method: :platform
+ field :architecture_name, GraphQL::Types::String, null: true,
+ description: 'Architecture provided by the the runner.',
+ method: :architecture
field :groups, ::Types::GroupType.connection_type, null: true,
description: 'Groups the runner is associated with. For group runners only.'
field :id, ::Types::GlobalIDType[::Ci::Runner], null: false,
diff --git a/app/helpers/ci/builds_helper.rb b/app/helpers/ci/builds_helper.rb
index bfdb830f2c3..b4a2cf7bb1e 100644
--- a/app/helpers/ci/builds_helper.rb
+++ b/app/helpers/ci/builds_helper.rb
@@ -36,5 +36,15 @@ module Ci
description: project_job_url(@project, @build)
}
end
+
+ def prepare_failed_jobs_summary_data(failed_builds)
+ failed_builds.map do |build|
+ {
+ id: build.id,
+ failure: build.present.callout_failure_message,
+ failure_summary: build_summary(build)
+ }
+ end.to_json
+ end
end
end
diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml
index bc2fedec69c..66b8b783ac2 100644
--- a/app/views/admin/application_settings/general.html.haml
+++ b/app/views/admin/application_settings/general.html.haml
@@ -115,4 +115,4 @@
= render 'admin/application_settings/snowplow'
= render 'admin/application_settings/eks'
= render 'admin/application_settings/floc'
-= render_if_exists 'admin/application_settings/license_file'
+= render_if_exists 'admin/application_settings/add_license'
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 5c0487db0fc..50a1b474504 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -9,7 +9,7 @@
= render 'shared/milestones/search_form'
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @group)
- = link_to _('New milestone'), new_group_milestone_path(@group), class: "btn gl-button btn-confirm", data: { qa_selector: "new_group_milestone_link" }
+ = link_to _('New milestone'), new_group_milestone_path(@group), class: "btn gl-button btn-confirm gl-ml-3", data: { qa_selector: "new_group_milestone_link" }
- if @milestones.blank?
= render 'shared/empty_states/milestones_tab', learn_more_path: help_page_path('user/project/milestones/index') do
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 154a92e6ec8..326a7c4027f 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -9,7 +9,7 @@
= render 'shared/milestones/search_form'
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project)
- = link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
+ = link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm gl-ml-3', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
= _('New milestone')
- if @milestones.blank?
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 03fc7548c84..4ef2bdd35bf 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -32,41 +32,45 @@
#js-pipeline-jobs-vue{ data: { full_path: @project.full_path, pipeline_iid: @pipeline.iid } }
- if @pipeline.failed_builds.present?
- #js-tab-failures.build-failures.tab-pane.build-page
- %table.table.gl-table.responsive-table.ci-table.responsive-table-sm-rounded
- %thead
- %th
- %th= _('Name')
- %th= _('Stage')
- %th= _('Failure')
- %th
+ #js-tab-failures.tab-pane
+ - if Feature.enabled?(:failed_jobs_tab_vue, @project, default_enabled: :yaml)
+ #js-pipeline-failed-jobs-vue{ data: { full_path: @project.full_path, pipeline_iid: @pipeline.iid, failed_jobs_summary_data: prepare_failed_jobs_summary_data(@pipeline.failed_builds) } }
+ - else
+ .build-failures.build-page
+ %table.table.gl-table.responsive-table.ci-table.responsive-table-sm-rounded
+ %thead
+ %th
+ %th= _('Name')
+ %th= _('Stage')
+ %th= _('Failure')
+ %th
- %tbody
- - @pipeline.failed_builds.each_with_index do |build, index|
- - job = build.present(current_user: current_user)
- %tr.build-state.responsive-table-border-start
- %td.responsive-table-cell.ci-status-icon-failed{ data: { column: _('Status')} }
- .d-none.d-md-block.build-icon
- = sprite_icon("status_#{build.status}")
- .d-md-none.build-badge
- = render "ci/status/badge", link: false, status: job.detailed_status(current_user)
- %td.responsive-table-cell.build-name{ data: { column: _('Name')} }
- = link_to build.name, pipeline_job_url(pipeline, build)
- %td.responsive-table-cell.build-stage{ data: { column: _('Stage')} }
- = build.stage.titleize
- %td.responsive-table-cell.build-failure{ data: { column: _('Failure')} }
- = build.present.callout_failure_message
- %td.responsive-table-cell.build-actions
- - if can?(current_user, :update_build, job) && job.retryable?
- = link_to retry_project_job_path(build.project, build, return_to: request.original_url), method: :post, title: _('Retry'), class: 'gl-button btn btn-default btn-icon' do
- = sprite_icon('repeat', css_class: 'gl-icon')
- - if can?(current_user, :read_build, job)
- %tr.build-log-row.responsive-table-border-end
- %td
- %td.responsive-table-cell.build-log-container{ colspan: 4 }
- %pre.build-log.build-log-rounded
- %code.bash.js-build-output
- = build_summary(build)
+ %tbody
+ - @pipeline.failed_builds.each_with_index do |build, index|
+ - job = build.present(current_user: current_user)
+ %tr.build-state.responsive-table-border-start
+ %td.responsive-table-cell.ci-status-icon-failed{ data: { column: _('Status')} }
+ .d-none.d-md-block.build-icon
+ = sprite_icon("status_#{build.status}")
+ .d-md-none.build-badge
+ = render "ci/status/badge", link: false, status: job.detailed_status(current_user)
+ %td.responsive-table-cell.build-name{ data: { column: _('Name')} }
+ = link_to build.name, pipeline_job_url(pipeline, build)
+ %td.responsive-table-cell.build-stage{ data: { column: _('Stage')} }
+ = build.stage.titleize
+ %td.responsive-table-cell.build-failure{ data: { column: _('Failure')} }
+ = build.present.callout_failure_message
+ %td.responsive-table-cell.build-actions
+ - if can?(current_user, :update_build, job) && job.retryable?
+ = link_to retry_project_job_path(build.project, build, return_to: request.original_url), method: :post, title: _('Retry'), class: 'gl-button btn btn-default btn-icon' do
+ = sprite_icon('repeat', css_class: 'gl-icon')
+ - if can?(current_user, :read_build, job)
+ %tr.build-log-row.responsive-table-border-end
+ %td
+ %td.responsive-table-cell.build-log-container{ colspan: 4 }
+ %pre.build-log.build-log-rounded
+ %code.bash.js-build-output
+ = build_summary(build)
#js-tab-dag.tab-pane
#js-pipeline-dag-vue{ data: { pipeline_project_path: @project.full_path, pipeline_iid: @pipeline.iid, empty_svg_path: image_path('illustrations/empty-state/empty-dag-md.svg'), about_dag_doc_path: help_page_path('ci/directed_acyclic_graph/index.md'), dag_doc_path: help_page_path('ci/yaml/index.md', anchor: 'needs')} }
diff --git a/app/views/projects/settings/operations/_prometheus.html.haml b/app/views/projects/settings/operations/_prometheus.html.haml
deleted file mode 100644
index 93281cc225b..00000000000
--- a/app/views/projects/settings/operations/_prometheus.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-%section.settings.no-animate.js-prometheus-settings
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
- = _('Prometheus')
- %button.gl-button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = _('Expand')
- %p
- = _('Link Prometheus monitoring to GitLab.')
- = link_to _('More information'), help_page_path('user/project/integrations/prometheus'), target: '_blank', rel: 'noopener noreferrer'
- .settings-content
- - if @project
- = render 'shared/prometheus_configuration_banner', project: @project, integration: service, header_tag: :b, info_well_classes: 'gl-p-3 gl-mt-3'
-
- %b.gl-mb-3
- = s_('PrometheusService|Manual configuration')
- %p
- = s_('PrometheusService|Auto configuration settings are used unless you override their values here.')
diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml
index 9a31666c316..63eb2814309 100644
--- a/app/views/projects/settings/operations/show.html.haml
+++ b/app/views/projects/settings/operations/show.html.haml
@@ -24,4 +24,3 @@
= render 'projects/settings/operations/incidents'
= render 'projects/settings/operations/grafana_integration'
= render_if_exists 'projects/settings/operations/status_page'
-= render 'projects/settings/operations/prometheus', service: prometheus_integration if Feature.enabled?(:settings_operations_prometheus_service)
diff --git a/app/views/shared/_milestones_sort_dropdown.html.haml b/app/views/shared/_milestones_sort_dropdown.html.haml
index 1c6eb7aa96b..b68022bfeda 100644
--- a/app/views/shared/_milestones_sort_dropdown.html.haml
+++ b/app/views/shared/_milestones_sort_dropdown.html.haml
@@ -1,4 +1,4 @@
- milestones_sort_options = milestones_sort_options_hash.map { |value, text| { value: value, text: text, href: page_filter_path(sort: value) } }
%div{ data: {testid: 'milestone_sort_by_dropdown'} }
- = gl_redirect_listbox_tag milestones_sort_options, @sort, class: 'gl-ml-3'
+ = gl_redirect_listbox_tag milestones_sort_options, @sort
diff --git a/config/feature_flags/development/settings_operations_prometheus_service.yml b/config/feature_flags/development/failed_jobs_tab_vue.yml
index 93afe504b21..1c70bd5b418 100644
--- a/config/feature_flags/development/settings_operations_prometheus_service.yml
+++ b/config/feature_flags/development/failed_jobs_tab_vue.yml
@@ -1,8 +1,8 @@
---
-name: settings_operations_prometheus_service
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24296
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258560
-milestone: '12.8'
+name: failed_jobs_tab_vue
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86151
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/360849
+milestone: '15.0'
type: development
-group: group::respond
+group: group::pipeline execution
default_enabled: false
diff --git a/config/feature_flags/development/omniauth_initializer_fullhost_proc.yml b/config/feature_flags/development/omniauth_initializer_fullhost_proc.yml
index 75ed8e642c6..52743818b5a 100644
--- a/config/feature_flags/development/omniauth_initializer_fullhost_proc.yml
+++ b/config/feature_flags/development/omniauth_initializer_fullhost_proc.yml
@@ -1,8 +1,8 @@
---
name: omniauth_initializer_fullhost_proc
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82401
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82703
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/355579
milestone: '14.10'
type: development
group: group::geo
-default_enabled: false
+default_enabled: true
diff --git a/config/initializers/0_marginalia.rb b/config/initializers/0_marginalia.rb
index 344b24252e5..e88599fd93c 100644
--- a/config/initializers/0_marginalia.rb
+++ b/config/initializers/0_marginalia.rb
@@ -17,9 +17,9 @@ Marginalia::Comment.components = [:application, :correlation_id, :jid, :endpoint
# As mentioned in https://github.com/basecamp/marginalia/pull/93/files,
# adding :line has some overhead because a regexp on the backtrace has
-# to be run on every SQL query. Only enable this in development because
+# to be run on every SQL query. Only enable this in development and test because
# we've seen it slow things down.
-if Rails.env.development?
+if Gitlab.dev_or_test_env?
Marginalia::Comment.components << :line
Marginalia::Comment.lines_to_ignore = Regexp.union(
Gitlab::BacktraceCleaner::IGNORE_BACKTRACES + %w[
diff --git a/doc/administration/monitoring/performance/request_profiling.md b/doc/administration/monitoring/performance/request_profiling.md
new file mode 100644
index 00000000000..d0d05c16ea0
--- /dev/null
+++ b/doc/administration/monitoring/performance/request_profiling.md
@@ -0,0 +1,12 @@
+---
+stage: Monitor
+group: Respond
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+remove_date: '2022-07-26'
+redirect_to: 'index.md'
+---
+
+# Request profiling (removed) **(FREE SELF)**
+
+This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/352488) in GitLab 14.8
+and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/350152) in 15.0.
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 25364361cc1..d07b84cbe69 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -9483,6 +9483,7 @@ Represents the total number of issues and their weights for a particular day.
| <a id="cirunneraccesslevel"></a>`accessLevel` | [`CiRunnerAccessLevel!`](#cirunneraccesslevel) | Access level of the runner. |
| <a id="cirunneractive"></a>`active` **{warning-solid}** | [`Boolean!`](#boolean) | **Deprecated** in 14.8. Use paused. |
| <a id="cirunneradminurl"></a>`adminUrl` | [`String`](#string) | Admin URL of the runner. Only available for administrators. |
+| <a id="cirunnerarchitecturename"></a>`architectureName` | [`String`](#string) | Architecture provided by the the runner. |
| <a id="cirunnercontactedat"></a>`contactedAt` | [`Time`](#time) | Timestamp of last contact from this runner. |
| <a id="cirunnercreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of creation of this runner. |
| <a id="cirunnerdescription"></a>`description` | [`String`](#string) | Description of the runner. |
@@ -9495,6 +9496,7 @@ Represents the total number of issues and their weights for a particular day.
| <a id="cirunnerlocked"></a>`locked` | [`Boolean`](#boolean) | Indicates the runner is locked. |
| <a id="cirunnermaximumtimeout"></a>`maximumTimeout` | [`Int`](#int) | Maximum timeout (in seconds) for jobs processed by the runner. |
| <a id="cirunnerpaused"></a>`paused` | [`Boolean!`](#boolean) | Indicates the runner is paused and not available to run jobs. |
+| <a id="cirunnerplatformname"></a>`platformName` | [`String`](#string) | Platform provided by the runner. |
| <a id="cirunnerprivateprojectsminutescostfactor"></a>`privateProjectsMinutesCostFactor` | [`Float`](#float) | Private projects' "minutes cost factor" associated with the runner (GitLab.com only). |
| <a id="cirunnerprojectcount"></a>`projectCount` | [`Int`](#int) | Number of projects that the runner is associated with. |
| <a id="cirunnerprojects"></a>`projects` | [`ProjectConnection`](#projectconnection) | Projects the runner is associated with. For project runners only. (see [Connections](#connections)) |
diff --git a/doc/api/linked_epics.md b/doc/api/linked_epics.md
index df302be0555..0d2176dfc61 100644
--- a/doc/api/linked_epics.md
+++ b/doc/api/linked_epics.md
@@ -6,10 +6,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Linked epics API **(ULTIMATE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/352493) in GitLab 14.9 [with a flag](../administration/feature_flags.md) named `related_epics_widget`. Enabled by default.
-
-FLAG:
-On self-managed GitLab, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](../administration/feature_flags.md) named `related_epics_widget`. On GitLab.com, this feature is available.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/352493) in GitLab 14.9 [with a flag](../administration/feature_flags.md) named `related_epics_widget`. Enabled by default.
+> - [Feature flag `related_epics_widget`](https://gitlab.com/gitlab-org/gitlab/-/issues/357089) removed in GitLab 15.0.
If the Related Epics feature is not available in your GitLab plan, a `403` status code is returned.
diff --git a/doc/user/admin_area/license_file.md b/doc/user/admin_area/license_file.md
index 5999e774d26..ff9e87680f9 100644
--- a/doc/user/admin_area/license_file.md
+++ b/doc/user/admin_area/license_file.md
@@ -18,8 +18,7 @@ Otherwise, to add your license:
1. Sign in to GitLab as an administrator.
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Settings > General**.
-1. In the **License file** area, select **Add a license**.
-1. Add a license by either uploading the file or pasting the key.
+1. In the **Add License** area, add a license by either uploading the file or entering the key.
1. Select the **Terms of Service** checkbox.
1. Select **Add license**.
diff --git a/doc/user/group/epics/linked_epics.md b/doc/user/group/epics/linked_epics.md
index 1fdf2ee3e52..89699661efa 100644
--- a/doc/user/group/epics/linked_epics.md
+++ b/doc/user/group/epics/linked_epics.md
@@ -6,12 +6,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Linked epics **(ULTIMATE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353473) in GitLab 14.9 [with a flag](../../../administration/feature_flags.md) named `related_epics_widget`. Enabled by default.
-
-FLAG:
-On self-managed GitLab, by default this feature is available. To hide the feature,
-ask an administrator to [disable the feature flag](../../../administration/feature_flags.md)
-named `related_epics_widget`. On GitLab.com, this feature is available.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353473) in GitLab 14.9 [with a flag](../../../administration/feature_flags.md) named `related_epics_widget`. Enabled by default.
+> - [Feature flag `related_epics_widget`](https://gitlab.com/gitlab-org/gitlab/-/issues/357089) removed in GitLab 15.0.
Linked epics are a bi-directional relationship between any two epics and appear in a block below
the epic description. You can link epics in different groups.
diff --git a/lefthook.yml b/lefthook.yml
index fb5e39c27c3..8bfa5d015c7 100644
--- a/lefthook.yml
+++ b/lefthook.yml
@@ -12,7 +12,7 @@ pre-push:
tags: view haml style
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
glob: '*.html.haml'
- run: bundle exec haml-lint --config .haml-lint.yml {files}
+ run: REVEAL_RUBOCOP_TODO=0 bundle exec haml-lint --config .haml-lint.yml {files}
markdownlint:
tags: documentation style
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 50de84a560e..9fb2a9c8c1c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -225,11 +225,6 @@ msgid_plural "%d errors"
msgstr[0] ""
msgstr[1] ""
-msgid "%d error found:"
-msgid_plural "%d errors found:"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d exporter"
msgid_plural "%d exporters"
msgstr[0] ""
@@ -769,9 +764,6 @@ msgstr ""
msgid "%{level_name} is not allowed since the fork source project has lower visibility."
msgstr ""
-msgid "%{link_start}Add a license%{link_end} that you have received from GitLab Inc."
-msgstr ""
-
msgid "%{link_start}Remove the %{draft_snippet} prefix%{link_end} from the title to allow this merge request to be merged when it's ready."
msgstr ""
@@ -21684,6 +21676,9 @@ msgstr ""
msgid "Jobs|All"
msgstr ""
+msgid "Jobs|An error occurred while loading the Failed Jobs tab."
+msgstr ""
+
msgid "Jobs|Are you sure you want to proceed?"
msgstr ""
@@ -21714,6 +21709,9 @@ msgstr ""
msgid "Jobs|Status"
msgstr ""
+msgid "Jobs|There was a problem fetching the failed jobs."
+msgstr ""
+
msgid "Jobs|Use jobs to automate your tasks"
msgstr ""
@@ -21816,6 +21814,9 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|There was a problem retrying the failed job."
+msgstr ""
+
msgid "Job|These artifacts are the latest. They will not be deleted (even if expired) until newer artifacts are available."
msgstr ""
@@ -22484,9 +22485,6 @@ msgstr ""
msgid "License compliance"
msgstr ""
-msgid "License file"
-msgstr ""
-
msgid "License key"
msgstr ""
@@ -22711,9 +22709,6 @@ msgstr ""
msgid "Link (optional)"
msgstr ""
-msgid "Link Prometheus monitoring to GitLab."
-msgstr ""
-
msgid "Link Sentry to GitLab to discover and view the errors your application generates."
msgstr ""
@@ -30090,9 +30085,6 @@ msgstr ""
msgid "ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}"
msgstr ""
-msgid "Prometheus"
-msgstr ""
-
msgid "PrometheusAlerts|exceeded"
msgstr ""
@@ -30189,9 +30181,6 @@ msgstr ""
msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
msgstr ""
-msgid "PrometheusService|You can now manage your Prometheus settings on the %{operations_link_start}Operations%{operations_link_end} page. Fields on this page have been deprecated."
-msgstr ""
-
msgid "PrometheusService|You have a cluster with the Prometheus integration enabled."
msgstr ""
@@ -37574,9 +37563,6 @@ msgstr[1] ""
msgid "The fork relationship has been removed."
msgstr ""
-msgid "The form contains the following errors:"
-msgstr ""
-
msgid "The form contains the following warning:"
msgstr ""
diff --git a/qa/qa.rb b/qa/qa.rb
index 01bf31f42f2..8d358aa2efe 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -64,5 +64,10 @@ module QA
"registry_with_cdn" => "RegistryWithCDN"
)
+ # Configure knapsack at the very begining of the setup
+ loader.on_setup do
+ QA::Support::KnapsackReport.configure!
+ end
+
loader.setup
end
diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb
index 829bdbbc6ee..99906ca44d9 100644
--- a/qa/qa/scenario/template.rb
+++ b/qa/qa/scenario/template.rb
@@ -30,11 +30,6 @@ module QA
Runtime::Scenario.define(:large_setup?, args.include?('can_use_large_setup'))
##
- # Setup knapsack and download latest report
- #
- Support::KnapsackReport.configure!
-
- ##
# Configure browser
#
Runtime::Browser.configure!
diff --git a/qa/qa/specs/knapsack_runner.rb b/qa/qa/specs/knapsack_runner.rb
index 123ecc5e1c3..4908553e43d 100644
--- a/qa/qa/specs/knapsack_runner.rb
+++ b/qa/qa/specs/knapsack_runner.rb
@@ -6,6 +6,7 @@ module QA
def self.run(args)
allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator
+ Knapsack.logger.info '==== Knapsack specs to execute ====='
Knapsack.logger.info 'Report specs:'
Knapsack.logger.info allocator.report_node_tests
Knapsack.logger.info 'Leftover specs:'
diff --git a/qa/qa/support/knapsack_report.rb b/qa/qa/support/knapsack_report.rb
index afe60664bec..b8cf6743746 100644
--- a/qa/qa/support/knapsack_report.rb
+++ b/qa/qa/support/knapsack_report.rb
@@ -108,7 +108,7 @@ module QA
#
# @return [Logger]
def logger
- @logger ||= Logger.new($stdout)
+ @logger ||= Knapsack.logger
end
# GCS client
diff --git a/qa/tasks/knapsack.rake b/qa/tasks/knapsack.rake
index 03c295f1aff..61c153291b7 100644
--- a/qa/tasks/knapsack.rake
+++ b/qa/tasks/knapsack.rake
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# rubocop:disable Rails/RakeEnvironment
namespace :knapsack do
desc "Run tests with knapsack runner"
task :rspec, [:rspec_args] do |_, args|
@@ -12,7 +13,6 @@ namespace :knapsack do
exit RSpec::Core::Runner.run([*rspec_args, "qa/specs/features"])
end
- QA::Support::KnapsackReport.configure!
exit QA::Specs::KnapsackRunner.run(rspec_args)
end
@@ -31,3 +31,4 @@ namespace :knapsack do
QA::Support::LongRunningSpecReporter.execute
end
end
+# rubocop:enable Rails/RakeEnvironment
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index e9f1232b5e7..162c36f5069 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -929,13 +929,13 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'when continue url is present' do
let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) }
+ before do
+ post_cancel(continue: { to: url })
+ end
+
context 'when continue to is a safe url' do
let(:url) { '/test' }
- before do
- post_cancel(continue: { to: url })
- end
-
it 'redirects to the continue url' do
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(url)
@@ -949,8 +949,9 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'when continue to is not a safe url' do
let(:url) { 'http://example.com' }
- it 'raises an error' do
- expect { cancel_with_redirect(url) }.to raise_error
+ it 'redirects to the builds page' do
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response).to redirect_to(builds_namespace_project_pipeline_path(id: pipeline.id))
end
end
end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 7e96c59fbb1..6802ebeb63e 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -326,56 +326,6 @@ RSpec.describe Projects::ServicesController do
end
end
end
-
- context 'with Prometheus integration' do
- let_it_be(:prometheus_integration) { create(:prometheus_integration, project: project) }
-
- let(:integration) { prometheus_integration }
- let(:integration_params) { { manual_configuration: '1', api_url: 'http://example.com' } }
-
- context 'when feature flag :settings_operations_prometheus_service is enabled' do
- before do
- stub_feature_flags(settings_operations_prometheus_service: true)
- end
-
- it 'redirects user back to edit page with alert' do
- put :update, params: project_params.merge(service: integration_params)
-
- expect(response).to redirect_to(edit_project_integration_path(project, integration))
- expected_alert = [
- "You can now manage your Prometheus settings on the",
- %(<a href="#{project_settings_operations_path(project)}">Operations</a> page.),
- "Fields on this page have been deprecated."
- ].join(' ')
-
- expect(controller).to set_flash.now[:alert].to(expected_alert)
- end
-
- it 'does not modify integration' do
- expect { put :update, params: project_params.merge(service: integration_params) }
- .not_to change { prometheus_integration_as_data }
- end
-
- def prometheus_integration_as_data
- pi = project.prometheus_integration.reload
- attrs = pi.attributes.except('encrypted_properties',
- 'encrypted_properties_iv')
-
- [attrs, pi.properties]
- end
- end
-
- context 'when feature flag :settings_operations_prometheus_service is disabled' do
- before do
- stub_feature_flags(settings_operations_prometheus_service: false)
- end
-
- it 'modifies integration' do
- expect { put :update, params: project_params.merge(service: integration_params) }
- .to change { project.prometheus_integration.reload.attributes }
- end
- end
- end
end
describe 'GET #edit' do
@@ -392,38 +342,6 @@ RSpec.describe Projects::ServicesController do
end
end
end
-
- context 'with Prometheus service' do
- let(:integration_param) { 'prometheus' }
-
- context 'when feature flag :settings_operations_prometheus_service is enabled' do
- before do
- stub_feature_flags(settings_operations_prometheus_service: true)
- get :edit, params: project_params(id: integration_param)
- end
-
- it 'renders deprecation warning notice' do
- expected_alert = [
- "You can now manage your Prometheus settings on the",
- %(<a href="#{project_settings_operations_path(project)}">Operations</a> page.),
- "Fields on this page have been deprecated."
- ].join(' ')
-
- expect(controller).to set_flash.now[:alert].to(expected_alert)
- end
- end
-
- context 'when feature flag :settings_operations_prometheus_service is disabled' do
- before do
- stub_feature_flags(settings_operations_prometheus_service: false)
- get :edit, params: project_params(id: integration_param)
- end
-
- it 'does not render deprecation warning notice' do
- expect(controller).not_to set_flash.now[:alert]
- end
- end
- end
end
private
diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb
index 7ef5371f2b5..c1fa91e9f8b 100644
--- a/spec/controllers/projects/settings/operations_controller_spec.rb
+++ b/spec/controllers/projects/settings/operations_controller_spec.rb
@@ -354,37 +354,6 @@ RSpec.describe Projects::Settings::OperationsController do
end
context 'prometheus integration' do
- describe 'PATCH #update' do
- let(:params) do
- {
- prometheus_integration_attributes: {
- manual_configuration: '0',
- api_url: 'https://gitlab.prometheus.rocks'
- }
- }
- end
-
- context 'feature flag :settings_operations_prometheus_service is enabled' do
- before do
- stub_feature_flags(settings_operations_prometheus_service: true)
- end
-
- it_behaves_like 'PATCHable'
- end
-
- context 'feature flag :settings_operations_prometheus_service is disabled' do
- before do
- stub_feature_flags(settings_operations_prometheus_service: false)
- end
-
- it_behaves_like 'PATCHable' do
- let(:permitted_params) do
- ActionController::Parameters.new(params.except(:prometheus_integration_attributes)).permit!
- end
- end
- end
- end
-
describe 'POST #reset_alerting_token' do
context 'with existing alerting setting' do
let!(:alerting_setting) do
diff --git a/spec/features/projects/integrations/user_activates_prometheus_spec.rb b/spec/features/projects/integrations/user_activates_prometheus_spec.rb
index 80629af6fce..56b895919b8 100644
--- a/spec/features/projects/integrations/user_activates_prometheus_spec.rb
+++ b/spec/features/projects/integrations/user_activates_prometheus_spec.rb
@@ -9,14 +9,13 @@ RSpec.describe 'User activates Prometheus' do
stub_request(:get, /.*prometheus.example.com.*/)
end
- it 'does not activate integration and informs about deprecation', :js do
+ it 'saves and activates integration', :js do
visit_project_integration('Prometheus')
check('Active')
fill_in('API URL', with: 'http://prometheus.example.com')
click_button('Save changes')
- expect(page).not_to have_content('Prometheus settings saved and active.')
- expect(page).to have_content('Fields on this page have been deprecated.')
+ expect(page).to have_content('Prometheus settings saved and active.')
end
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 8581584aea4..c6a2ccb3e8c 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -1188,12 +1188,43 @@ RSpec.describe 'Pipeline', :js do
expect(page).to have_content('There is an unknown failure, please try again')
end
+ context 'when failed_jobs_tab_vue feature flag is disabled' do
+ before do
+ stub_feature_flags(failed_jobs_tab_vue: false)
+ end
+
+ context 'when user does not have permission to retry build' do
+ it 'shows retry button for failed build' do
+ subject
+
+ page.within(find('.build-failures', match: :first)) do
+ expect(page).not_to have_link('Retry')
+ end
+ end
+ end
+
+ context 'when user does have permission to retry build' do
+ before do
+ create(:protected_branch, :developers_can_merge,
+ name: pipeline.ref, project: project)
+ end
+
+ it 'shows retry button for failed build' do
+ subject
+
+ page.within(find('.build-failures', match: :first)) do
+ expect(page).to have_link('Retry')
+ end
+ end
+ end
+ end
+
context 'when user does not have permission to retry build' do
it 'shows retry button for failed build' do
subject
- page.within(find('.build-failures', match: :first)) do
- expect(page).not_to have_link('Retry')
+ page.within(find('#js-tab-failures', match: :first)) do
+ expect(page).not_to have_button('Retry')
end
end
end
@@ -1207,8 +1238,8 @@ RSpec.describe 'Pipeline', :js do
it 'shows retry button for failed build' do
subject
- page.within(find('.build-failures', match: :first)) do
- expect(page).to have_link('Retry')
+ page.within(find('#js-tab-failures', match: :first)) do
+ expect(page).to have_button('Retry')
end
end
end
diff --git a/spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js b/spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js
new file mode 100644
index 00000000000..3b5632a8a4e
--- /dev/null
+++ b/spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js
@@ -0,0 +1,87 @@
+import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import createFlash from '~/flash';
+import FailedJobsApp from '~/pipelines/components/jobs/failed_jobs_app.vue';
+import FailedJobsTable from '~/pipelines/components/jobs/failed_jobs_table.vue';
+import GetFailedJobsQuery from '~/pipelines/graphql/queries/get_failed_jobs.query.graphql';
+import { mockFailedJobsQueryResponse, mockFailedJobsSummaryData } from '../../mock_data';
+
+Vue.use(VueApollo);
+
+jest.mock('~/flash');
+
+describe('Failed Jobs App', () => {
+ let wrapper;
+ let resolverSpy;
+
+ const findLoadingSpinner = () => wrapper.findComponent(GlLoadingIcon);
+ const findJobsTable = () => wrapper.findComponent(FailedJobsTable);
+
+ const createMockApolloProvider = (resolver) => {
+ const requestHandlers = [[GetFailedJobsQuery, resolver]];
+
+ return createMockApollo(requestHandlers);
+ };
+
+ const createComponent = (resolver, failedJobsSummaryData = mockFailedJobsSummaryData) => {
+ wrapper = shallowMount(FailedJobsApp, {
+ provide: {
+ fullPath: 'root/ci-project',
+ pipelineIid: 1,
+ },
+ propsData: {
+ failedJobsSummary: failedJobsSummaryData,
+ },
+ apolloProvider: createMockApolloProvider(resolver),
+ });
+ };
+
+ beforeEach(() => {
+ resolverSpy = jest.fn().mockResolvedValue(mockFailedJobsQueryResponse);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('loading spinner', () => {
+ beforeEach(() => {
+ createComponent(resolverSpy);
+ });
+
+ it('displays loading spinner when fetching failed jobs', () => {
+ expect(findLoadingSpinner().exists()).toBe(true);
+ });
+
+ it('hides loading spinner after the failed jobs have been fetched', async () => {
+ await waitForPromises();
+
+ expect(findLoadingSpinner().exists()).toBe(false);
+ });
+ });
+
+ it('displays the failed jobs table', async () => {
+ createComponent(resolverSpy);
+
+ await waitForPromises();
+
+ expect(findJobsTable().exists()).toBe(true);
+ expect(createFlash).not.toHaveBeenCalled();
+ });
+
+ it('handles query fetch error correctly', async () => {
+ resolverSpy = jest.fn().mockRejectedValue(new Error('GraphQL error'));
+
+ createComponent(resolverSpy);
+
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalledWith({
+ message: 'There was a problem fetching the failed jobs.',
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js b/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js
new file mode 100644
index 00000000000..b597a3bf4b0
--- /dev/null
+++ b/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js
@@ -0,0 +1,117 @@
+import { GlButton, GlLink, GlTableLite } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import createFlash from '~/flash';
+import { redirectTo } from '~/lib/utils/url_utility';
+import FailedJobsTable from '~/pipelines/components/jobs/failed_jobs_table.vue';
+import RetryFailedJobMutation from '~/pipelines/graphql/mutations/retry_failed_job.mutation.graphql';
+import {
+ successRetryMutationResponse,
+ failedRetryMutationResponse,
+ mockPreparedFailedJobsData,
+ mockPreparedFailedJobsDataNoPermission,
+} from '../../mock_data';
+
+jest.mock('~/flash');
+jest.mock('~/lib/utils/url_utility');
+
+Vue.use(VueApollo);
+
+describe('Failed Jobs Table', () => {
+ let wrapper;
+
+ const successRetryMutationHandler = jest.fn().mockResolvedValue(successRetryMutationResponse);
+ const failedRetryMutationHandler = jest.fn().mockResolvedValue(failedRetryMutationResponse);
+
+ const findJobsTable = () => wrapper.findComponent(GlTableLite);
+ const findRetryButton = () => wrapper.findComponent(GlButton);
+ const findJobLink = () => wrapper.findComponent(GlLink);
+ const findJobLog = () => wrapper.findByTestId('job-log');
+
+ const createMockApolloProvider = (resolver) => {
+ const requestHandlers = [[RetryFailedJobMutation, resolver]];
+ return createMockApollo(requestHandlers);
+ };
+
+ const createComponent = (resolver, failedJobsData = mockPreparedFailedJobsData) => {
+ wrapper = mountExtended(FailedJobsTable, {
+ propsData: {
+ failedJobs: failedJobsData,
+ },
+ apolloProvider: createMockApolloProvider(resolver),
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('displays the failed jobs table', () => {
+ createComponent();
+
+ expect(findJobsTable().exists()).toBe(true);
+ });
+
+ it('calls the retry failed job mutation correctly', () => {
+ createComponent(successRetryMutationHandler);
+
+ findRetryButton().trigger('click');
+
+ expect(successRetryMutationHandler).toHaveBeenCalledWith({
+ id: mockPreparedFailedJobsData[0].id,
+ });
+ });
+
+ it('redirects to the new job after the mutation', async () => {
+ const {
+ data: {
+ jobRetry: { job },
+ },
+ } = successRetryMutationResponse;
+
+ createComponent(successRetryMutationHandler);
+
+ findRetryButton().trigger('click');
+
+ await waitForPromises();
+
+ expect(redirectTo).toHaveBeenCalledWith(job.detailedStatus.detailsPath);
+ });
+
+ it('shows error message if the retry failed job mutation fails', async () => {
+ createComponent(failedRetryMutationHandler);
+
+ findRetryButton().trigger('click');
+
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalledWith({
+ message: 'There was a problem retrying the failed job.',
+ });
+ });
+
+ it('hides the job log and retry button if a user does not have permission', () => {
+ createComponent([[]], mockPreparedFailedJobsDataNoPermission);
+
+ expect(findJobLog().exists()).toBe(false);
+ expect(findRetryButton().exists()).toBe(false);
+ });
+
+ it('displays the job log and retry button if a user has permission', () => {
+ createComponent();
+
+ expect(findJobLog().exists()).toBe(true);
+ expect(findRetryButton().exists()).toBe(true);
+ });
+
+ it('job name links to the correct job', () => {
+ createComponent();
+
+ expect(findJobLink().attributes('href')).toBe(
+ mockPreparedFailedJobsData[0].detailedStatus.detailsPath,
+ );
+ });
+});
diff --git a/spec/frontend/pipelines/components/jobs/utils_spec.js b/spec/frontend/pipelines/components/jobs/utils_spec.js
new file mode 100644
index 00000000000..720446cfda3
--- /dev/null
+++ b/spec/frontend/pipelines/components/jobs/utils_spec.js
@@ -0,0 +1,14 @@
+import { prepareFailedJobs } from '~/pipelines/components/jobs/utils';
+import {
+ mockFailedJobsData,
+ mockFailedJobsSummaryData,
+ mockPreparedFailedJobsData,
+} from '../../mock_data';
+
+describe('Utils', () => {
+ it('prepares failed jobs data correctly', () => {
+ expect(prepareFailedJobs(mockFailedJobsData, mockFailedJobsSummaryData)).toEqual(
+ mockPreparedFailedJobsData,
+ );
+ });
+});
diff --git a/spec/frontend/pipelines/mock_data.js b/spec/frontend/pipelines/mock_data.js
index 59d4e808b32..57d1511d859 100644
--- a/spec/frontend/pipelines/mock_data.js
+++ b/spec/frontend/pipelines/mock_data.js
@@ -1141,3 +1141,218 @@ export const mockPipelineBranch = () => {
viewType: 'root',
};
};
+
+export const mockFailedJobsQueryResponse = {
+ data: {
+ project: {
+ __typename: 'Project',
+ id: 'gid://gitlab/Project/20',
+ pipeline: {
+ __typename: 'Pipeline',
+ id: 'gid://gitlab/Ci::Pipeline/300',
+ jobs: {
+ __typename: 'CiJobConnection',
+ nodes: [
+ {
+ __typename: 'CiJob',
+ status: 'FAILED',
+ detailedStatus: {
+ __typename: 'DetailedStatus',
+ id: 'failed-1848-1848',
+ detailsPath: '/root/ci-project/-/jobs/1848',
+ group: 'failed',
+ icon: 'status_failed',
+ label: 'failed',
+ text: 'failed',
+ tooltip: 'failed - (script failure)',
+ action: {
+ __typename: 'StatusAction',
+ id: 'Ci::Build-failed-1848',
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ method: 'post',
+ path: '/root/ci-project/-/jobs/1848/retry',
+ title: 'Retry',
+ },
+ },
+ id: 'gid://gitlab/Ci::Build/1848',
+ stage: {
+ __typename: 'CiStage',
+ id: 'gid://gitlab/Ci::Stage/358',
+ name: 'build',
+ },
+ name: 'wait_job',
+ retryable: true,
+ userPermissions: {
+ __typename: 'JobPermissions',
+ readBuild: true,
+ updateBuild: true,
+ },
+ },
+ {
+ __typename: 'CiJob',
+ status: 'FAILED',
+ detailedStatus: {
+ __typename: 'DetailedStatus',
+ id: 'failed-1710-1710',
+ detailsPath: '/root/ci-project/-/jobs/1710',
+ group: 'failed',
+ icon: 'status_failed',
+ label: 'failed',
+ text: 'failed',
+ tooltip: 'failed - (script failure) (retried)',
+ action: null,
+ },
+ id: 'gid://gitlab/Ci::Build/1710',
+ stage: {
+ __typename: 'CiStage',
+ id: 'gid://gitlab/Ci::Stage/358',
+ name: 'build',
+ },
+ name: 'wait_job',
+ retryable: false,
+ userPermissions: {
+ __typename: 'JobPermissions',
+ readBuild: true,
+ updateBuild: true,
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+};
+
+export const mockFailedJobsSummaryData = [
+ {
+ id: 1848,
+ failure: null,
+ failure_summary:
+ '<span>Pulling docker image node:latest ...<br/></span><span>Using docker image sha256:738d733448be00c72cb6618b7a06a1424806c6d239d8885e92f9b1e8727092b5 for node:latest with digest node@sha256:e5b7b349d517159246070bf14242027a9e220ffa8bd98a67ba1495d969c06c01 ...<br/></span><div class="section-start" data-timestamp="1651175313" data-section="prepare-script" role="button"></div><span class="term-fg-l-cyan term-bold section section-header js-s-prepare-script">Preparing environment</span><span class="section section-header js-s-prepare-script"><br/></span><span class="section line js-s-prepare-script">Running on runner-kvkqh24-project-20-concurrent-0 via 0706719b1b8d...<br/></span><div class="section-end" data-section="prepare-script"></div><div class="section-start" data-timestamp="1651175313" data-section="get-sources" role="button"></div><span class="term-fg-l-cyan term-bold section section-header js-s-get-sources">Getting source from Git repository</span><span class="section section-header js-s-get-sources"><br/></span><span class="term-fg-l-green term-bold section line js-s-get-sources">Fetching changes with git depth set to 50...</span><span class="section line js-s-get-sources"><br/>Reinitialized existing Git repository in /builds/root/ci-project/.git/<br/>fatal: couldn\'t find remote ref refs/heads/test<br/></span><div class="section-end" data-section="get-sources"></div><span class="term-fg-l-red term-bold">ERROR: Job failed: exit code 1<br/></span>',
+ },
+];
+
+export const mockFailedJobsData = [
+ {
+ normalizedId: 1848,
+ __typename: 'CiJob',
+ status: 'FAILED',
+ detailedStatus: {
+ __typename: 'DetailedStatus',
+ id: 'failed-1848-1848',
+ detailsPath: '/root/ci-project/-/jobs/1848',
+ group: 'failed',
+ icon: 'status_failed',
+ label: 'failed',
+ text: 'failed',
+ tooltip: 'failed - (script failure)',
+ action: {
+ __typename: 'StatusAction',
+ id: 'Ci::Build-failed-1848',
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ method: 'post',
+ path: '/root/ci-project/-/jobs/1848/retry',
+ title: 'Retry',
+ },
+ },
+ id: 'gid://gitlab/Ci::Build/1848',
+ stage: { __typename: 'CiStage', id: 'gid://gitlab/Ci::Stage/358', name: 'build' },
+ name: 'wait_job',
+ retryable: true,
+ userPermissions: { __typename: 'JobPermissions', readBuild: true, updateBuild: true },
+ },
+ {
+ normalizedId: 1710,
+ __typename: 'CiJob',
+ status: 'FAILED',
+ detailedStatus: {
+ __typename: 'DetailedStatus',
+ id: 'failed-1710-1710',
+ detailsPath: '/root/ci-project/-/jobs/1710',
+ group: 'failed',
+ icon: 'status_failed',
+ label: 'failed',
+ text: 'failed',
+ tooltip: 'failed - (script failure) (retried)',
+ action: null,
+ },
+ id: 'gid://gitlab/Ci::Build/1710',
+ stage: { __typename: 'CiStage', id: 'gid://gitlab/Ci::Stage/358', name: 'build' },
+ name: 'wait_job',
+ retryable: false,
+ userPermissions: { __typename: 'JobPermissions', readBuild: true, updateBuild: true },
+ },
+];
+
+export const mockPreparedFailedJobsData = [
+ {
+ __typename: 'CiJob',
+ _showDetails: true,
+ detailedStatus: {
+ __typename: 'DetailedStatus',
+ action: {
+ __typename: 'StatusAction',
+ buttonTitle: 'Retry this job',
+ icon: 'retry',
+ id: 'Ci::Build-failed-1848',
+ method: 'post',
+ path: '/root/ci-project/-/jobs/1848/retry',
+ title: 'Retry',
+ },
+ detailsPath: '/root/ci-project/-/jobs/1848',
+ group: 'failed',
+ icon: 'status_failed',
+ id: 'failed-1848-1848',
+ label: 'failed',
+ text: 'failed',
+ tooltip: 'failed - (script failure)',
+ },
+ failure: null,
+ failureSummary:
+ '<span>Pulling docker image node:latest ...<br/></span><span>Using docker image sha256:738d733448be00c72cb6618b7a06a1424806c6d239d8885e92f9b1e8727092b5 for node:latest with digest node@sha256:e5b7b349d517159246070bf14242027a9e220ffa8bd98a67ba1495d969c06c01 ...<br/></span><div class="section-start" data-timestamp="1651175313" data-section="prepare-script" role="button"></div><span class="term-fg-l-cyan term-bold section section-header js-s-prepare-script">Preparing environment</span><span class="section section-header js-s-prepare-script"><br/></span><span class="section line js-s-prepare-script">Running on runner-kvkqh24-project-20-concurrent-0 via 0706719b1b8d...<br/></span><div class="section-end" data-section="prepare-script"></div><div class="section-start" data-timestamp="1651175313" data-section="get-sources" role="button"></div><span class="term-fg-l-cyan term-bold section section-header js-s-get-sources">Getting source from Git repository</span><span class="section section-header js-s-get-sources"><br/></span><span class="term-fg-l-green term-bold section line js-s-get-sources">Fetching changes with git depth set to 50...</span><span class="section line js-s-get-sources"><br/>Reinitialized existing Git repository in /builds/root/ci-project/.git/<br/>fatal: couldn\'t find remote ref refs/heads/test<br/></span><div class="section-end" data-section="get-sources"></div><span class="term-fg-l-red term-bold">ERROR: Job failed: exit code 1<br/></span>',
+ id: 'gid://gitlab/Ci::Build/1848',
+ name: 'wait_job',
+ normalizedId: 1848,
+ retryable: true,
+ stage: { __typename: 'CiStage', id: 'gid://gitlab/Ci::Stage/358', name: 'build' },
+ status: 'FAILED',
+ userPermissions: { __typename: 'JobPermissions', readBuild: true, updateBuild: true },
+ },
+];
+
+export const mockPreparedFailedJobsDataNoPermission = [
+ {
+ ...mockPreparedFailedJobsData[0],
+ userPermissions: { __typename: 'JobPermissions', readBuild: false, updateBuild: false },
+ },
+];
+
+export const successRetryMutationResponse = {
+ data: {
+ jobRetry: {
+ job: {
+ __typename: 'CiJob',
+ id: '"gid://gitlab/Ci::Build/1985"',
+ detailedStatus: {
+ detailsPath: '/root/project/-/jobs/1985',
+ id: 'pending-1985-1985',
+ __typename: 'DetailedStatus',
+ },
+ },
+ errors: [],
+ __typename: 'JobRetryPayload',
+ },
+ },
+};
+
+export const failedRetryMutationResponse = {
+ data: {
+ jobRetry: {
+ job: {},
+ errors: ['New Error'],
+ __typename: 'JobRetryPayload',
+ },
+ },
+};
diff --git a/spec/graphql/types/ci/runner_type_spec.rb b/spec/graphql/types/ci/runner_type_spec.rb
index 7697cd0ef79..b2fbfda898e 100644
--- a/spec/graphql/types/ci/runner_type_spec.rb
+++ b/spec/graphql/types/ci/runner_type_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe GitlabSchema.types['CiRunner'] do
expected_fields = %w[
id description created_at contacted_at maximum_timeout access_level active paused status
version short_sha revision locked run_untagged ip_address runner_type tag_list
- project_count job_count admin_url edit_admin_url user_permissions executor_name
+ project_count job_count admin_url edit_admin_url user_permissions executor_name architecture_name platform_name
groups projects jobs token_expires_at
]
diff --git a/spec/helpers/ci/builds_helper_spec.rb b/spec/helpers/ci/builds_helper_spec.rb
index 143d96cf632..ea3b5aac4ea 100644
--- a/spec/helpers/ci/builds_helper_spec.rb
+++ b/spec/helpers/ci/builds_helper_spec.rb
@@ -97,6 +97,20 @@ RSpec.describe Ci::BuildsHelper do
end
end
+ describe '#prepare_failed_jobs_summary_data' do
+ let(:failed_build) { create(:ci_build, :failed, :trace_live) }
+
+ subject { helper.prepare_failed_jobs_summary_data([failed_build]) }
+
+ it 'returns array of failed jobs with id, failure and failure summary' do
+ expect(subject).to eq([{
+ id: failed_build.id,
+ failure: failed_build.present.callout_failure_message,
+ failure_summary: helper.build_summary(failed_build)
+ }].to_json)
+ end
+ end
+
def assign_project
build(:project).tap do |project|
assign(:project, project)
diff --git a/spec/lib/gitlab/background_migration/extract_project_topics_into_separate_table_spec.rb b/spec/lib/gitlab/background_migration/extract_project_topics_into_separate_table_spec.rb
index a111007a984..65d55f85a98 100644
--- a/spec/lib/gitlab/background_migration/extract_project_topics_into_separate_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/extract_project_topics_into_separate_table_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe Gitlab::BackgroundMigration::ExtractProjectTopicsIntoSeparateTabl
# Tagging records
expect { tagging_1.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect { tagging_2.reload }.to raise_error(ActiveRecord::RecordNotFound)
- expect { other_tagging.reload }.not_to raise_error(ActiveRecord::RecordNotFound)
+ expect { other_tagging.reload }.not_to raise_error
expect { tagging_3.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect { tagging_4.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect { tagging_5.reload }.to raise_error(ActiveRecord::RecordNotFound)
diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb
index c06d26d1441..d6280d3c28c 100644
--- a/spec/lib/gitlab/checks/branch_check_spec.rb
+++ b/spec/lib/gitlab/checks/branch_check_spec.rb
@@ -103,7 +103,7 @@ RSpec.describe Gitlab::Checks::BranchCheck do
it 'prevents force push' do
expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
- expect { subject.validate! }.to raise_error
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError)
end
end
end
@@ -126,7 +126,7 @@ RSpec.describe Gitlab::Checks::BranchCheck do
it 'prevents force push' do
expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
- expect { subject.validate! }.to raise_error
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError)
end
end
@@ -141,7 +141,7 @@ RSpec.describe Gitlab::Checks::BranchCheck do
it 'prevents force push' do
expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
- expect { subject.validate! }.to raise_error
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError)
end
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 28fd0011673..fd80cf3b396 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -3180,15 +3180,15 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
context 'without proper permissions' do
before do
- allow(model).to receive(:execute).with(/CREATE EXTENSION IF NOT EXISTS #{extension}/).and_raise(ActiveRecord::StatementInvalid, 'InsufficientPrivilege: permission denied')
+ allow(model).to receive(:execute)
+ .with(/CREATE EXTENSION IF NOT EXISTS #{extension}/)
+ .and_raise(ActiveRecord::StatementInvalid, 'InsufficientPrivilege: permission denied')
end
- it 'raises the exception' do
- expect { subject }.to raise_error(ActiveRecord::StatementInvalid, /InsufficientPrivilege/)
- end
-
- it 'prints an error message' do
- expect { subject }.to output(/user is not allowed/).to_stderr.and raise_error
+ it 'raises an exception and prints an error message' do
+ expect { subject }
+ .to output(/user is not allowed/).to_stderr
+ .and raise_error(ActiveRecord::StatementInvalid, /InsufficientPrivilege/)
end
end
end
@@ -3206,15 +3206,15 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
context 'without proper permissions' do
before do
- allow(model).to receive(:execute).with(/DROP EXTENSION IF EXISTS #{extension}/).and_raise(ActiveRecord::StatementInvalid, 'InsufficientPrivilege: permission denied')
- end
-
- it 'raises the exception' do
- expect { subject }.to raise_error(ActiveRecord::StatementInvalid, /InsufficientPrivilege/)
+ allow(model).to receive(:execute)
+ .with(/DROP EXTENSION IF EXISTS #{extension}/)
+ .and_raise(ActiveRecord::StatementInvalid, 'InsufficientPrivilege: permission denied')
end
- it 'prints an error message' do
- expect { subject }.to output(/user is not allowed/).to_stderr.and raise_error
+ it 'raises an exception and prints an error message' do
+ expect { subject }
+ .to output(/user is not allowed/).to_stderr
+ .and raise_error(ActiveRecord::StatementInvalid, /InsufficientPrivilege/)
end
end
end
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
index 59b87c5d8e7..9ff395070ea 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -62,7 +62,7 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
end
it 'does not raise a UserNotFoundError' do
- expect { receiver.execute }.not_to raise_error(Gitlab::Email::UserNotFoundError)
+ expect { receiver.execute }.not_to raise_error
end
end
end
@@ -71,7 +71,7 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
let(:original_recipient) { User.support_bot }
it 'does not raise a UserNotFoundError' do
- expect { receiver.execute }.not_to raise_error(Gitlab::Email::UserNotFoundError)
+ expect { receiver.execute }.not_to raise_error
end
end
end
diff --git a/spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb
index 8bd873cf008..dfa18c27d5e 100644
--- a/spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb
+++ b/spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Base do
let(:series) { 0 }
it 'does not raise error' do
- expect { subject }.not_to raise_error(ArgumentError)
+ expect { subject }.not_to raise_error
end
end
end
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index 4c47912e218..2c931a999f1 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -177,9 +177,9 @@ EOT
expect(diff_two.diff).to include(Gitlab::EncodingHelper::UNICODE_REPLACEMENT_CHARACTER)
expect(diff_three.diff).to include(Gitlab::EncodingHelper::UNICODE_REPLACEMENT_CHARACTER)
- expect { Oj.dump(diff) }.not_to raise_error(EncodingError)
- expect { Oj.dump(diff_two) }.not_to raise_error(EncodingError)
- expect { Oj.dump(diff_three) }.not_to raise_error(EncodingError)
+ expect { Oj.dump(diff) }.not_to raise_error
+ expect { Oj.dump(diff_two) }.not_to raise_error
+ expect { Oj.dump(diff_three) }.not_to raise_error
end
context 'when the diff is binary' do
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index 8211806a809..0a186b07d19 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -103,7 +103,7 @@ RSpec.describe Gitlab::Popen do
it 'raises error' do
expect do
@klass.new.popen(%w[foobar])
- end.to raise_error
+ end.to raise_error(Errno::ENOENT)
end
end
end
diff --git a/spec/lib/gitlab/tracking/event_definition_spec.rb b/spec/lib/gitlab/tracking/event_definition_spec.rb
index 51c62840819..623009e9a30 100644
--- a/spec/lib/gitlab/tracking/event_definition_spec.rb
+++ b/spec/lib/gitlab/tracking/event_definition_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe Gitlab::Tracking::EventDefinition do
end
it 'has all definitions valid' do
- expect { described_class.definitions }.not_to raise_error(Gitlab::Tracking::InvalidEventError)
+ expect { described_class.definitions }.not_to raise_error
end
describe '#validate' do
diff --git a/spec/migrations/finalize_project_namespaces_backfill_spec.rb b/spec/migrations/finalize_project_namespaces_backfill_spec.rb
index 3d0b0ec13fe..f70f1612f9d 100644
--- a/spec/migrations/finalize_project_namespaces_backfill_spec.rb
+++ b/spec/migrations/finalize_project_namespaces_backfill_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe FinalizeProjectNamespacesBackfill, :migration do
context 'when project namespace backfilling migration finished successfully' do
it 'does not raise exception' do
- expect { migrate! }.not_to raise_error(/Expected batched background migration for the given configuration to be marked as 'finished'/)
+ expect { migrate! }.not_to raise_error
end
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 8c0f07047e4..a0ede9fb0d9 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -494,7 +494,7 @@ RSpec.describe Clusters::Platforms::Kubernetes do
end
it 'does not raise kubeclient http error' do
- expect { subject }.not_to raise_error(Kubeclient::HttpError)
+ expect { subject }.not_to raise_error
end
end
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index 4f3b95e43cd..5468699f9dd 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -237,7 +237,7 @@ RSpec.describe ReactiveCaching, :use_clean_rails_memory_store_caching do
end
it 'does not raise the exception' do
- expect { go! }.not_to raise_exception(ReactiveCaching::ExceededReactiveCacheLimit)
+ expect { go! }.not_to raise_exception
end
end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 3cab2b67ec5..2769e94cef7 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -621,7 +621,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
expect(close_action.processed).to be_falsey
# it encounters the StaleObjectError at first, but reloads the object and runs `build.play`
- expect { subject }.not_to raise_error(ActiveRecord::StaleObjectError)
+ expect { subject }.not_to raise_error
# Now the build should be processed.
expect(close_action.reload.processed).to be_truthy
diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb
index b7f75bfe8bf..7d4a50788f3 100644
--- a/spec/models/integration_spec.rb
+++ b/spec/models/integration_spec.rb
@@ -681,7 +681,7 @@ RSpec.describe Integration do
integration.properties = { foo: 1, bar: 2 }
- expect { integration.properties[:foo] = 3 }.to raise_error
+ expect { integration.properties[:foo] = 3 }.to raise_error(FrozenError)
end
end
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index 6c86db1197f..a9ed811e77d 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -1182,7 +1182,7 @@ RSpec.describe Packages::Package, type: :model do
it "plan_limits includes column #{plan_limit_name}" do
expect { package.project.actual_limits.send(plan_limit_name) }
- .not_to raise_error(NoMethodError)
+ .not_to raise_error
end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 42fe9f03141..d315b62b23e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2030,7 +2030,7 @@ RSpec.describe Project, factory_default: :keep do
it 'returns nil if the path detection throws an error' do
expect(Rails.application.routes).to receive(:recognize_path).with(url) { raise ActionController::RoutingError, 'test' }
- expect { subject }.not_to raise_error(ActionController::RoutingError)
+ expect { subject }.not_to raise_error
expect(subject).to be_nil
end
end
diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb
index 13d33b95b16..008ae6275f0 100644
--- a/spec/models/protected_branch/push_access_level_spec.rb
+++ b/spec/models/protected_branch/push_access_level_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe ProtectedBranch::PushAccessLevel do
it 'checks that a deploy key is enabled for the same project as the protected branch\'s' do
level = build(:protected_branch_push_access_level, deploy_key: create(:deploy_key))
- expect { level.save! }.to raise_error
+ expect { level.save! }.to raise_error(ActiveRecord::RecordInvalid)
expect(level.errors.full_messages).to contain_exactly('Deploy key is not enabled for this project')
end
end
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index 125fec61d72..4ae1927dcca 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -53,7 +53,10 @@ RSpec.describe Release do
context 'when a release is tied to a milestone for another project' do
it 'creates a validation error' do
milestone = build(:milestone, project: create(:project))
- expect { release.milestones << milestone }.to raise_error
+
+ expect { release.milestones << milestone }
+ .to raise_error(ActiveRecord::RecordInvalid,
+ 'Validation failed: Release does not have the same project as the milestone')
end
end
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index 6102e15012b..542876438cc 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -62,6 +62,8 @@ RSpec.describe 'Query.runner(id)' do
'ipAddress' => runner.ip_address,
'runnerType' => runner.instance_type? ? 'INSTANCE_TYPE' : 'PROJECT_TYPE',
'executorName' => runner.executor_type&.dasherize,
+ 'architectureName' => runner.architecture,
+ 'platformName' => runner.platform,
'jobCount' => 0,
'jobs' => a_hash_including("count" => 0, "nodes" => [], "pageInfo" => anything),
'projectCount' => nil,
diff --git a/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb b/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb
index 5f6822223ca..4891e64aab8 100644
--- a/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe 'Delete a cluster agent' do
'or you don\'t have permission to perform this action']
it 'does not delete cluster agent' do
- expect { cluster_agent.reload }.not_to raise_error(ActiveRecord::RecordNotFound)
+ expect { cluster_agent.reload }.not_to raise_error
end
end
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index c7f121c48ab..d69f542cd18 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -506,10 +506,12 @@ RSpec.describe 'getting merge request listings nested in a project' do
end
context 'when only the count is requested' do
+ let_it_be(:merged_at) { Time.new(2020, 1, 3) }
+
context 'when merged at filter is present' do
let_it_be(:merge_request) do
create(:merge_request, :unique_branches, source_project: project).tap do |mr|
- mr.metrics.update!(merged_at: Time.new(2020, 1, 3))
+ mr.metrics.update!(merged_at: merged_at, created_at: merged_at - 2.days)
end
end
@@ -526,12 +528,18 @@ RSpec.describe 'getting merge request listings nested in a project' do
it 'does not query the merge requests table for the count' do
query_recorder = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) }
- queries = query_recorder.data.each_value.first[:occurrences]
+ queries = query_recorder.log
expect(queries).not_to include(match(/SELECT COUNT\(\*\) FROM "merge_requests"/))
expect(queries).to include(match(/SELECT COUNT\(\*\) FROM "merge_request_metrics"/))
end
context 'when total_time_to_merge and count is queried' do
+ let_it_be(:merge_request_2) do
+ create(:merge_request, :unique_branches, source_project: project).tap do |mr|
+ mr.metrics.update!(merged_at: merged_at, created_at: merged_at - 1.day)
+ end
+ end
+
let(:query) do
graphql_query_for(:project, { full_path: project.full_path }, <<~QUERY)
mergeRequests(mergedAfter: "2020-01-01", mergedBefore: "2020-01-05", first: 0) {
@@ -541,11 +549,18 @@ RSpec.describe 'getting merge request listings nested in a project' do
QUERY
end
- it 'does not query the merge requests table for the total_time_to_merge' do
+ it 'uses the merge_request_metrics table for total_time_to_merge' do
query_recorder = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) }
- queries = query_recorder.data.each_value.first[:occurrences]
- expect(queries).to include(match(/SELECT.+SUM.+FROM "merge_request_metrics" WHERE/))
+ expect(query_recorder.log).to include(match(/SELECT.+SUM.+FROM "merge_request_metrics" WHERE/))
+ end
+
+ it 'returns the correct total time to merge' do
+ post_graphql(query, current_user: current_user)
+
+ sum = graphql_data_at(:project, :merge_requests, :total_time_to_merge)
+
+ expect(sum).to eq(3.days.to_f)
end
end
diff --git a/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb b/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb
index b48ea70aa4c..98b01e2b303 100644
--- a/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb
+++ b/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe ::Ci::PipelineArtifacts::CoverageReportService do
context 'when pipeline artifact has already been created' do
it 'do not raise an error and do not persist the same artifact twice' do
- expect { 2.times { described_class.new.execute(pipeline) } }.not_to raise_error(ActiveRecord::RecordNotUnique)
+ expect { 2.times { described_class.new.execute(pipeline) } }.not_to raise_error
expect(Ci::PipelineArtifact.count).to eq(1)
end
diff --git a/spec/services/clusters/agents/delete_service_spec.rb b/spec/services/clusters/agents/delete_service_spec.rb
index 1d6bc9618dd..abe1bdaab27 100644
--- a/spec/services/clusters/agents/delete_service_spec.rb
+++ b/spec/services/clusters/agents/delete_service_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe Clusters::Agents::DeleteService do
expect(response.status).to eq(:error)
expect(response.message).to eq('You have insufficient permissions to delete this cluster agent')
- expect { cluster_agent.reload }.not_to raise_error(ActiveRecord::RecordNotFound)
+ expect { cluster_agent.reload }.not_to raise_error
end
end
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index b4a25fd121d..30e48b3baf1 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -14,6 +14,8 @@ require_relative "helpers/fast_rails_root"
require 'rubocop'
require 'rubocop/rspec/support'
+RSpec::Expectations.configuration.on_potential_false_positives = :raise
+
RSpec.configure do |config|
config.mock_with :rspec do |mocks|
mocks.verify_doubled_constant_names = true
diff --git a/spec/support/shared_contexts/email_shared_context.rb b/spec/support/shared_contexts/email_shared_context.rb
index 0dc66eeb2ee..086cdf50e9d 100644
--- a/spec/support/shared_contexts/email_shared_context.rb
+++ b/spec/support/shared_contexts/email_shared_context.rb
@@ -148,7 +148,7 @@ RSpec.shared_examples :note_handler_shared_examples do |forwardable|
end
it 'allows email to only have quoted text', if: forwardable do
- expect { receiver.execute }.not_to raise_error(Gitlab::Email::EmptyEmailError)
+ expect { receiver.execute }.not_to raise_error
end
end
diff --git a/spec/support_specs/helpers/active_record/query_recorder_spec.rb b/spec/support_specs/helpers/active_record/query_recorder_spec.rb
index f1af9ceffb9..d6c52b22449 100644
--- a/spec/support_specs/helpers/active_record/query_recorder_spec.rb
+++ b/spec/support_specs/helpers/active_record/query_recorder_spec.rb
@@ -78,12 +78,14 @@ RSpec.describe ActiveRecord::QueryRecorder do
end
describe 'detecting the right number of calls and their origin' do
- it 'detects two separate queries' do
- control = ActiveRecord::QueryRecorder.new query_recorder_debug: true do
+ let(:control) do
+ ActiveRecord::QueryRecorder.new query_recorder_debug: true do
2.times { TestQueries.count }
TestQueries.first
end
+ end
+ it 'detects two separate queries' do
# Check #find_query
expect(control.find_query(/.*/, 0).size)
.to eq(control.data.keys.size)
@@ -98,8 +100,8 @@ RSpec.describe ActiveRecord::QueryRecorder do
expect(control.log.size).to eq(3)
# Ensure memoization value match the raw value above
expect(control.count).to eq(control.log.size)
- # Ensure we have only two sources of queries
- expect(control.data.keys.size).to eq(1)
+ # Ensure we have two sources of queries
+ expect(control.data.keys.size).to eq(2)
end
end
end
diff --git a/spec/views/admin/application_settings/general.html.haml_spec.rb b/spec/views/admin/application_settings/general.html.haml_spec.rb
index 7d28175d134..503e41eabc9 100644
--- a/spec/views/admin/application_settings/general.html.haml_spec.rb
+++ b/spec/views/admin/application_settings/general.html.haml_spec.rb
@@ -6,13 +6,16 @@ RSpec.describe 'admin/application_settings/general.html.haml' do
let(:app_settings) { build(:application_setting) }
let(:user) { create(:admin) }
+ before do
+ assign(:application_setting, app_settings)
+ allow(view).to receive(:current_user).and_return(user)
+ end
+
describe 'sourcegraph integration' do
let(:sourcegraph_flag) { true }
before do
- assign(:application_setting, app_settings)
allow(Gitlab::Sourcegraph).to receive(:feature_available?).and_return(sourcegraph_flag)
- allow(view).to receive(:current_user).and_return(user)
end
context 'when sourcegraph feature is enabled' do
@@ -35,11 +38,6 @@ RSpec.describe 'admin/application_settings/general.html.haml' do
end
describe 'prompt user about registration features' do
- before do
- assign(:application_setting, app_settings)
- allow(view).to receive(:current_user).and_return(user)
- end
-
context 'when service ping is enabled' do
before do
stub_application_setting(usage_ping_enabled: true)
@@ -60,4 +58,14 @@ RSpec.describe 'admin/application_settings/general.html.haml' do
it_behaves_like 'renders registration features prompt', :application_setting_disabled_repository_size_limit
end
end
+
+ describe 'add license' do
+ before do
+ render
+ end
+
+ it 'does not show the Add License section' do
+ expect(rendered).not_to have_css('#js-add-license-toggle')
+ end
+ end
end
diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb
index c0ec86a41a7..8853b34074a 100644
--- a/spec/views/projects/settings/operations/show.html.haml_spec.rb
+++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb
@@ -52,30 +52,6 @@ RSpec.describe 'projects/settings/operations/show' do
end
end
- describe 'Operations > Prometheus' do
- context 'when settings_operations_prometheus_service flag is enabled' do
- it 'renders the Operations Settings page' do
- render
-
- expect(rendered).to have_content _('Prometheus')
- expect(rendered).to have_content _('Link Prometheus monitoring to GitLab.')
- expect(rendered).to have_content _('To use a Prometheus installed on a cluster, deactivate the manual configuration.')
- end
- end
-
- context 'when settings_operations_prometheus_service is disabled' do
- before do
- stub_feature_flags(settings_operations_prometheus_service: false)
- end
-
- it 'renders the Operations Settings page' do
- render
-
- expect(rendered).not_to have_content _('Auto configuration settings are used unless you override their values here.')
- end
- end
- end
-
describe 'Operations > Tracing' do
context 'Settings page ' do
it 'renders the Tracing Settings page' do
diff --git a/spec/workers/clusters/applications/activate_service_worker_spec.rb b/spec/workers/clusters/applications/activate_service_worker_spec.rb
index 019bfe7a750..d13ff76613c 100644
--- a/spec/workers/clusters/applications/activate_service_worker_spec.rb
+++ b/spec/workers/clusters/applications/activate_service_worker_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe Clusters::Applications::ActivateServiceWorker, '#perform' do
context 'cluster does not exist' do
it 'does not raise Record Not Found error' do
- expect { described_class.new.perform(0, 'ignored in this context') }.not_to raise_error(ActiveRecord::RecordNotFound)
+ expect { described_class.new.perform(0, 'ignored in this context') }.not_to raise_error
end
end
end
diff --git a/spec/workers/delete_diff_files_worker_spec.rb b/spec/workers/delete_diff_files_worker_spec.rb
index cf26dbabb97..c124847ca45 100644
--- a/spec/workers/delete_diff_files_worker_spec.rb
+++ b/spec/workers/delete_diff_files_worker_spec.rb
@@ -34,11 +34,13 @@ RSpec.describe DeleteDiffFilesWorker do
end
it 'rollsback if something goes wrong' do
+ error = RuntimeError.new('something went wrong')
+
expect(MergeRequestDiffFile).to receive_message_chain(:where, :delete_all)
- .and_raise
+ .and_raise(error)
expect { described_class.new.perform(merge_request_diff.id) }
- .to raise_error
+ .to raise_error(error)
merge_request_diff.reload