diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-06 15:09:05 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-06 15:09:05 +0300 |
commit | 669ad9e431c7647f01bda681aab2c0ad2cb58826 (patch) | |
tree | 13fac2c7a8f4c4f8f715ed0dd351470dee3918a1 /app/assets/javascripts | |
parent | 3c21cbd6a81d7b962a5d9a72ba8e03f3bf449af8 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
22 files changed, 358 insertions, 14 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> |