diff options
Diffstat (limited to 'app/assets/javascripts/artifacts/components/job_artifacts_table.vue')
-rw-r--r-- | app/assets/javascripts/artifacts/components/job_artifacts_table.vue | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/app/assets/javascripts/artifacts/components/job_artifacts_table.vue b/app/assets/javascripts/artifacts/components/job_artifacts_table.vue new file mode 100644 index 00000000000..34e443f4e58 --- /dev/null +++ b/app/assets/javascripts/artifacts/components/job_artifacts_table.vue @@ -0,0 +1,337 @@ +<script> +import { + GlLoadingIcon, + GlTable, + GlLink, + GlButtonGroup, + GlButton, + GlBadge, + GlIcon, + GlPagination, +} from '@gitlab/ui'; +import { createAlert } from '~/flash'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import getJobArtifactsQuery from '../graphql/queries/get_job_artifacts.query.graphql'; +import { totalArtifactsSizeForJob, mapArchivesToJobNodes, mapBooleansToJobNodes } from '../utils'; +import { + STATUS_BADGE_VARIANTS, + I18N_DOWNLOAD, + I18N_BROWSE, + I18N_DELETE, + I18N_EXPIRED, + I18N_DESTROY_ERROR, + I18N_FETCH_ERROR, + I18N_ARTIFACTS, + I18N_JOB, + I18N_SIZE, + I18N_CREATED, + I18N_ARTIFACTS_COUNT, + INITIAL_CURRENT_PAGE, + INITIAL_PREVIOUS_PAGE_CURSOR, + INITIAL_NEXT_PAGE_CURSOR, + JOBS_PER_PAGE, + INITIAL_LAST_PAGE_SIZE, +} from '../constants'; +import ArtifactsTableRowDetails from './artifacts_table_row_details.vue'; + +const INITIAL_PAGINATION_STATE = { + currentPage: INITIAL_CURRENT_PAGE, + prevPageCursor: INITIAL_PREVIOUS_PAGE_CURSOR, + nextPageCursor: INITIAL_NEXT_PAGE_CURSOR, + firstPageSize: JOBS_PER_PAGE, + lastPageSize: INITIAL_LAST_PAGE_SIZE, +}; + +export default { + name: 'JobArtifactsTable', + components: { + GlLoadingIcon, + GlTable, + GlLink, + GlButtonGroup, + GlButton, + GlBadge, + GlIcon, + GlPagination, + CiIcon, + TimeAgo, + ArtifactsTableRowDetails, + }, + inject: ['projectPath'], + apollo: { + jobArtifacts: { + query: getJobArtifactsQuery, + variables() { + return this.queryVariables; + }, + update({ project: { jobs: { nodes = [], pageInfo = {}, count = 0 } = {} } }) { + this.pageInfo = pageInfo; + this.count = count; + return nodes + .map(mapArchivesToJobNodes) + .map(mapBooleansToJobNodes) + .map((jobNode) => { + return { + ...jobNode, + // GlTable uses an item's _showDetails attribute to determine whether + // it should show the <template #row-details /> for its table row + _showDetails: this.expandedJobs.includes(jobNode.id), + }; + }); + }, + error() { + createAlert({ + message: I18N_FETCH_ERROR, + }); + }, + }, + }, + data() { + return { + jobArtifacts: [], + count: 0, + pageInfo: {}, + expandedJobs: [], + pagination: INITIAL_PAGINATION_STATE, + }; + }, + computed: { + queryVariables() { + return { + projectPath: this.projectPath, + firstPageSize: this.pagination.firstPageSize, + lastPageSize: this.pagination.lastPageSize, + prevPageCursor: this.pagination.prevPageCursor, + nextPageCursor: this.pagination.nextPageCursor, + }; + }, + showPagination() { + return this.count > JOBS_PER_PAGE; + }, + prevPage() { + return Number(this.pageInfo.hasPreviousPage); + }, + nextPage() { + return Number(this.pageInfo.hasNextPage); + }, + }, + methods: { + refetchArtifacts() { + this.$apollo.queries.jobArtifacts.refetch(); + }, + artifactsSize(item) { + return totalArtifactsSizeForJob(item); + }, + pipelineId(item) { + const id = getIdFromGraphQLId(item.pipeline.id); + return `#${id}`; + }, + handlePageChange(page) { + const { startCursor, endCursor } = this.pageInfo; + + if (page > this.pagination.currentPage) { + this.pagination = { + ...INITIAL_PAGINATION_STATE, + nextPageCursor: endCursor, + currentPage: page, + }; + } else { + this.pagination = { + lastPageSize: JOBS_PER_PAGE, + firstPageSize: null, + prevPageCursor: startCursor, + currentPage: page, + }; + } + }, + handleRowToggle(toggleDetails, hasArtifacts, id, detailsShowing) { + if (!hasArtifacts) return; + toggleDetails(); + + if (!detailsShowing) { + this.expandedJobs.push(id); + } else { + this.expandedJobs.splice(this.expandedJobs.indexOf(id), 1); + } + }, + downloadPath(job) { + return job.archive?.downloadPath; + }, + downloadButtonDisabled(job) { + return !job.archive?.downloadPath; + }, + browseButtonDisabled(job) { + return !job.browseArtifactsPath; + }, + }, + fields: [ + { + key: 'artifacts', + label: I18N_ARTIFACTS, + thClass: 'gl-w-quarter', + }, + { + key: 'job', + label: I18N_JOB, + thClass: 'gl-w-35p', + }, + { + key: 'size', + label: I18N_SIZE, + thClass: 'gl-w-15p gl-text-right', + tdClass: 'gl-text-right', + }, + { + key: 'created', + label: I18N_CREATED, + thClass: 'gl-w-eighth gl-text-center', + tdClass: 'gl-text-center', + }, + { + key: 'actions', + label: '', + thClass: 'gl-w-eighth', + tdClass: 'gl-text-right', + }, + ], + STATUS_BADGE_VARIANTS, + i18n: { + download: I18N_DOWNLOAD, + browse: I18N_BROWSE, + delete: I18N_DELETE, + expired: I18N_EXPIRED, + destroyArtifactError: I18N_DESTROY_ERROR, + fetchArtifactsError: I18N_FETCH_ERROR, + artifactsLabel: I18N_ARTIFACTS, + jobLabel: I18N_JOB, + sizeLabel: I18N_SIZE, + createdLabel: I18N_CREATED, + artifactsCount: I18N_ARTIFACTS_COUNT, + }, +}; +</script> +<template> + <div> + <gl-table + :items="jobArtifacts" + :fields="$options.fields" + :busy="$apollo.queries.jobArtifacts.loading" + stacked="sm" + details-td-class="gl-bg-gray-10! gl-p-0! gl-overflow-auto" + > + <template #table-busy> + <gl-loading-icon size="lg" /> + </template> + <template + #cell(artifacts)="{ item: { id, artifacts, hasArtifacts }, toggleDetails, detailsShowing }" + > + <span + :class="{ 'gl-cursor-pointer': hasArtifacts }" + data-testid="job-artifacts-count" + @click="handleRowToggle(toggleDetails, hasArtifacts, id, detailsShowing)" + > + <gl-icon + v-if="hasArtifacts" + :name="detailsShowing ? 'chevron-down' : 'chevron-right'" + class="gl-mr-2" + /> + <strong> + {{ $options.i18n.artifactsCount(artifacts.nodes.length) }} + </strong> + </span> + </template> + <template #cell(job)="{ item }"> + <span class="gl-display-inline-flex gl-align-items-center gl-w-full gl-mb-4"> + <span data-testid="job-artifacts-job-status"> + <ci-icon v-if="item.succeeded" :status="item.detailedStatus" class="gl-mr-3" /> + <gl-badge + v-else + :icon="item.detailedStatus.icon" + :variant="$options.STATUS_BADGE_VARIANTS[item.detailedStatus.group]" + class="gl-mr-3" + > + {{ item.detailedStatus.label }} + </gl-badge> + </span> + <gl-link :href="item.webPath" class="gl-font-weight-bold"> + {{ item.name }} + </gl-link> + </span> + <span class="gl-display-inline-flex"> + <gl-icon name="pipeline" class="gl-mr-2" /> + <gl-link + :href="item.pipeline.path" + class="gl-text-black-normal gl-text-decoration-underline gl-mr-4" + > + {{ pipelineId(item) }} + </gl-link> + <gl-icon name="branch" class="gl-mr-2" /> + <gl-link + :href="item.refPath" + class="gl-text-black-normal gl-text-decoration-underline gl-mr-4" + > + {{ item.refName }} + </gl-link> + <gl-icon name="commit" class="gl-mr-2" /> + <gl-link + :href="item.commitPath" + class="gl-text-black-normal gl-text-decoration-underline gl-mr-4" + > + {{ item.shortSha }} + </gl-link> + </span> + </template> + <template #cell(size)="{ item }"> + <span data-testid="job-artifacts-size">{{ artifactsSize(item) }}</span> + </template> + <template #cell(created)="{ item }"> + <time-ago data-testid="job-artifacts-created" :time="item.finishedAt" /> + </template> + <template #cell(actions)="{ item }"> + <gl-button-group> + <gl-button + icon="download" + :disabled="downloadButtonDisabled(item)" + :href="downloadPath(item)" + :title="$options.i18n.download" + :aria-label="$options.i18n.download" + data-testid="job-artifacts-download-button" + /> + <gl-button + icon="folder-open" + :disabled="browseButtonDisabled(item)" + :href="item.browseArtifactsPath" + :title="$options.i18n.browse" + :aria-label="$options.i18n.browse" + data-testid="job-artifacts-browse-button" + /> + <gl-button + icon="remove" + :title="$options.i18n.delete" + :aria-label="$options.i18n.delete" + data-testid="job-artifacts-delete-button" + disabled + /> + </gl-button-group> + </template> + <template #row-details="{ item: { artifacts } }"> + <artifacts-table-row-details + :artifacts="artifacts" + :query-variables="queryVariables" + @refetch="refetchArtifacts" + /> + </template> + </gl-table> + <gl-pagination + v-if="showPagination" + :value="pagination.currentPage" + :prev-page="prevPage" + :next-page="nextPage" + align="center" + class="gl-mt-3" + @input="handlePageChange" + /> + </div> +</template> |