diff options
Diffstat (limited to 'app/assets/javascripts/jobs')
23 files changed, 188 insertions, 544 deletions
diff --git a/app/assets/javascripts/jobs/bridge/app.vue b/app/assets/javascripts/jobs/bridge/app.vue deleted file mode 100644 index c639e49083b..00000000000 --- a/app/assets/javascripts/jobs/bridge/app.vue +++ /dev/null @@ -1,118 +0,0 @@ -<script> -import { GlLoadingIcon } from '@gitlab/ui'; -import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import { __, sprintf } from '~/locale'; -import CiHeader from '~/vue_shared/components/header_ci_component.vue'; -import getPipelineQuery from './graphql/queries/pipeline.query.graphql'; -import BridgeEmptyState from './components/empty_state.vue'; -import BridgeSidebar from './components/sidebar.vue'; -import { SIDEBAR_COLLAPSE_BREAKPOINTS } from './components/constants'; - -export default { - name: 'BridgePageApp', - components: { - BridgeEmptyState, - BridgeSidebar, - CiHeader, - GlLoadingIcon, - }, - inject: ['buildId', 'projectFullPath', 'pipelineIid'], - apollo: { - pipeline: { - query: getPipelineQuery, - variables() { - return { - fullPath: this.projectFullPath, - iid: this.pipelineIid, - }; - }, - update(data) { - if (!data?.project?.pipeline) { - return null; - } - - const { pipeline } = data.project; - const stages = pipeline?.stages.edges.map((edge) => edge.node) || []; - const jobs = stages.map((stage) => stage.jobs.nodes).flat(); - - return { - ...pipeline, - commit: { - ...pipeline.commit, - commit_path: pipeline.commit.webPath, - short_id: pipeline.commit.shortId, - }, - id: getIdFromGraphQLId(pipeline.id), - jobs, - stages, - }; - }, - }, - }, - data() { - return { - isSidebarExpanded: true, - pipeline: {}, - }; - }, - computed: { - bridgeJob() { - return ( - this.pipeline.jobs?.filter( - (job) => getIdFromGraphQLId(job.id) === Number(this.buildId), - )[0] || {} - ); - }, - bridgeName() { - return sprintf(__('Job %{jobName}'), { jobName: this.bridgeJob.name }); - }, - isPipelineLoading() { - return this.$apollo.queries.pipeline.loading; - }, - }, - created() { - window.addEventListener('resize', this.onResize); - }, - mounted() { - this.onResize(); - }, - methods: { - toggleSidebar() { - this.isSidebarExpanded = !this.isSidebarExpanded; - }, - onResize() { - const breakpoint = bp.getBreakpointSize(); - if (SIDEBAR_COLLAPSE_BREAKPOINTS.includes(breakpoint)) { - this.isSidebarExpanded = false; - } else if (!this.isSidebarExpanded) { - this.isSidebarExpanded = true; - } - }, - }, -}; -</script> -<template> - <div> - <gl-loading-icon v-if="isPipelineLoading" size="lg" class="gl-mt-4" /> - <div v-else> - <ci-header - class="gl-border-b-1 gl-border-b-solid gl-border-b-gray-100" - :status="bridgeJob.detailedStatus" - :time="bridgeJob.createdAt" - :user="pipeline.user" - :has-sidebar-button="true" - :item-name="bridgeName" - @clickedSidebarButton="toggleSidebar" - /> - <bridge-empty-state :downstream-pipeline-path="bridgeJob.downstreamPipeline.path" /> - <bridge-sidebar - v-if="isSidebarExpanded" - :bridge-job="bridgeJob" - :commit="pipeline.commit" - :is-sidebar-expanded="isSidebarExpanded" - @toggleSidebar="toggleSidebar" - /> - </div> - </div> -</template> diff --git a/app/assets/javascripts/jobs/bridge/components/constants.js b/app/assets/javascripts/jobs/bridge/components/constants.js deleted file mode 100644 index 33310b3157a..00000000000 --- a/app/assets/javascripts/jobs/bridge/components/constants.js +++ /dev/null @@ -1 +0,0 @@ -export const SIDEBAR_COLLAPSE_BREAKPOINTS = ['xs', 'sm']; diff --git a/app/assets/javascripts/jobs/bridge/components/empty_state.vue b/app/assets/javascripts/jobs/bridge/components/empty_state.vue deleted file mode 100644 index bd07d863719..00000000000 --- a/app/assets/javascripts/jobs/bridge/components/empty_state.vue +++ /dev/null @@ -1,45 +0,0 @@ -<script> -import { GlButton } from '@gitlab/ui'; -import { __ } from '~/locale'; - -export default { - name: 'BridgeEmptyState', - i18n: { - title: __('This job triggers a downstream pipeline'), - linkBtnText: __('View downstream pipeline'), - }, - components: { - GlButton, - }, - inject: { - emptyStateIllustrationPath: { - type: String, - require: true, - }, - }, - props: { - downstreamPipelinePath: { - type: String, - required: false, - default: undefined, - }, - }, -}; -</script> - -<template> - <div class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-mt-11"> - <img :src="emptyStateIllustrationPath" /> - <h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1> - <gl-button - v-if="downstreamPipelinePath" - class="gl-mt-3" - category="secondary" - variant="confirm" - size="medium" - :href="downstreamPipelinePath" - > - {{ $options.i18n.linkBtnText }} - </gl-button> - </div> -</template> diff --git a/app/assets/javascripts/jobs/bridge/components/sidebar.vue b/app/assets/javascripts/jobs/bridge/components/sidebar.vue deleted file mode 100644 index 3ba07cf55d1..00000000000 --- a/app/assets/javascripts/jobs/bridge/components/sidebar.vue +++ /dev/null @@ -1,105 +0,0 @@ -<script> -import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import { __ } from '~/locale'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; -import { JOB_SIDEBAR } from '../../constants'; -import CommitBlock from '../../components/commit_block.vue'; - -export default { - styles: { - width: '290px', - }, - name: 'BridgeSidebar', - i18n: { - ...JOB_SIDEBAR, - retryButton: __('Retry'), - retryTriggerJob: __('Retry the trigger job'), - retryDownstreamPipeline: __('Retry the downstream pipeline'), - }, - sectionClass: ['gl-border-t-solid', 'gl-border-t-1', 'gl-border-t-gray-100', 'gl-py-5'], - components: { - CommitBlock, - GlButton, - GlDropdown, - GlDropdownItem, - TooltipOnTruncate, - }, - mixins: [glFeatureFlagsMixin()], - props: { - bridgeJob: { - type: Object, - required: true, - }, - commit: { - type: Object, - required: true, - }, - }, - data() { - return { - topPosition: 0, - }; - }, - computed: { - rootStyle() { - return { ...this.$options.styles, top: `${this.topPosition}px` }; - }, - }, - mounted() { - this.setTopPosition(); - }, - methods: { - onSidebarButtonClick() { - this.$emit('toggleSidebar'); - }, - setTopPosition() { - const navbarEl = document.querySelector('.js-navbar'); - - if (navbarEl) { - this.topPosition = navbarEl.getBoundingClientRect().bottom; - } - }, - }, -}; -</script> -<template> - <aside - class="gl-fixed gl-right-0 gl-px-5 gl-bg-gray-10 gl-h-full gl-border-l-solid gl-border-1 gl-border-gray-100 gl-z-index-200 gl-overflow-hidden" - :style="rootStyle" - > - <div class="gl-py-5 gl-display-flex gl-align-items-center"> - <tooltip-on-truncate :title="bridgeJob.name" truncate-target="child" - ><h4 class="gl-mb-0 gl-mr-2 gl-text-truncate"> - {{ bridgeJob.name }} - </h4> - </tooltip-on-truncate> - <!-- TODO: implement retry actions --> - <div - v-if="glFeatures.triggerJobRetryAction" - class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right" - > - <gl-dropdown - :text="$options.i18n.retryButton" - category="primary" - variant="confirm" - right - size="medium" - > - <gl-dropdown-item>{{ $options.i18n.retryTriggerJob }}</gl-dropdown-item> - <gl-dropdown-item>{{ $options.i18n.retryDownstreamPipeline }}</gl-dropdown-item> - </gl-dropdown> - </div> - <gl-button - :aria-label="$options.i18n.toggleSidebar" - data-testid="sidebar-expansion-toggle" - category="tertiary" - class="gl-md-display-none gl-ml-2" - icon="chevron-double-lg-right" - @click="onSidebarButtonClick" - /> - </div> - <commit-block :commit="commit" :class="$options.sectionClass" /> - <!-- TODO: show stage dropdown, jobs list --> - </aside> -</template> diff --git a/app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql b/app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql deleted file mode 100644 index 338ca9f16c7..00000000000 --- a/app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql +++ /dev/null @@ -1,70 +0,0 @@ -query getPipelineData($fullPath: ID!, $iid: ID!) { - project(fullPath: $fullPath) { - id - pipeline(iid: $iid) { - id - iid - path - sha - ref - refPath - commit { - id - shortId - title - webPath - } - detailedStatus { - id - icon - group - } - stages { - edges { - node { - id - name - jobs { - nodes { - id - createdAt - name - scheduledAt - startedAt - status - triggered - detailedStatus { - id - detailsPath - icon - group - text - tooltip - } - downstreamPipeline { - id - path - } - stage { - id - name - } - } - } - } - } - } - user { - id - avatarUrl - name - username - webPath - webUrl - status { - message - } - } - } - } -} diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index 396b015ad83..f9e6c64aad1 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -68,6 +68,11 @@ export default { default: null, }, }, + data() { + return { + searchResults: [], + }; + }, computed: { ...mapState([ 'isLoading', @@ -184,6 +189,9 @@ export default { this.throttled(); }, + setSearchResults(searchResults) { + this.searchResults = searchResults; + }, }, }; </script> @@ -279,10 +287,12 @@ export default { :is-scroll-top-disabled="isScrollTopDisabled" :is-job-log-size-visible="isJobLogSizeVisible" :is-scrolling-down="isScrollingDown" + :job-log="jobLog" @scrollJobLogTop="scrollTop" @scrollJobLogBottom="scrollBottom" + @searchResults="setSearchResults" /> - <log :job-log="jobLog" :is-complete="isJobLogComplete" /> + <log :job-log="jobLog" :is-complete="isJobLogComplete" :search-results="searchResults" /> </div> <!-- EO job log --> diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue index eb6a284dfaf..5e89dd5acc2 100644 --- a/app/assets/javascripts/jobs/components/job_log_controllers.vue +++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue @@ -1,21 +1,34 @@ <script> -import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui'; +import { GlTooltipDirective, GlLink, GlButton, GlSearchBoxByClick } from '@gitlab/ui'; +import { scrollToElement } from '~/lib/utils/common_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils'; import { __, s__, sprintf } from '~/locale'; +import HelpPopover from '~/vue_shared/components/help_popover.vue'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { i18n: { scrollToBottomButtonLabel: s__('Job|Scroll to bottom'), scrollToTopButtonLabel: s__('Job|Scroll to top'), showRawButtonLabel: s__('Job|Show complete raw'), + searchPlaceholder: s__('Job|Search job log'), + noResults: s__('Job|No search results found'), + searchPopoverTitle: s__('Job|Job log search'), + searchPopoverDescription: s__( + 'Job|Search for substrings in your job log output. Currently search is only supported for the visible job log output, not for any log output that is truncated due to size.', + ), + logLineNumberNotFound: s__('Job|We could not find this element'), }, components: { GlLink, GlButton, + GlSearchBoxByClick, + HelpPopover, }, directives: { GlTooltip: GlTooltipDirective, }, + mixins: [glFeatureFlagMixin()], props: { size: { type: Number, @@ -42,6 +55,16 @@ export default { type: Boolean, required: true, }, + jobLog: { + type: Array, + required: true, + }, + }, + data() { + return { + searchTerm: '', + searchResults: [], + }; }, computed: { jobLogSize() { @@ -49,6 +72,9 @@ export default { size: numberToHumanSize(this.size), }); }, + showJobLogSearch() { + return this.glFeatures.jobLogSearch; + }, }, methods: { handleScrollToTop() { @@ -57,6 +83,54 @@ export default { handleScrollToBottom() { this.$emit('scrollJobLogBottom'); }, + searchJobLog() { + this.searchResults = []; + + if (!this.searchTerm) return; + + const compactedLog = []; + + this.jobLog.forEach((obj) => { + if (obj.lines && obj.lines.length > 0) { + compactedLog.push(...obj.lines); + } + + if (!obj.lines && obj.content.length > 0) { + compactedLog.push(obj); + } + }); + + compactedLog.forEach((line) => { + const lineText = line.content[0].text; + + if (lineText.toLocaleLowerCase().includes(this.searchTerm.toLocaleLowerCase())) { + this.searchResults.push(line); + } + }); + + if (this.searchResults.length > 0) { + this.$emit('searchResults', this.searchResults); + + // BE returns zero based index, we need to add one to match the line numbers in the DOM + const firstSearchResult = `#L${this.searchResults[0].lineNumber + 1}`; + const logLine = document.querySelector(`.js-line ${firstSearchResult}`); + + if (logLine) { + setTimeout(() => scrollToElement(logLine)); + + const message = sprintf(s__('Job|%{searchLength} results found for %{searchTerm}'), { + searchLength: this.searchResults.length, + searchTerm: this.searchTerm, + }); + + this.$toast.show(message); + } else { + this.$toast.show(this.$options.i18n.logLineNumberNotFound); + } + } else { + this.$toast.show(this.$options.i18n.noResults); + } + }, }, }; </script> @@ -81,6 +155,25 @@ export default { <!-- eo truncate information --> <div class="controllers gl-float-right"> + <template v-if="showJobLogSearch"> + <gl-search-box-by-click + v-model="searchTerm" + class="gl-mr-3" + :placeholder="$options.i18n.searchPlaceholder" + data-testid="job-log-search-box" + @clear="$emit('searchResults', [])" + @submit="searchJobLog" + /> + + <help-popover class="gl-mr-3"> + <template #title>{{ $options.i18n.searchPopoverTitle }}</template> + + <p class="gl-mb-0"> + {{ $options.i18n.searchPopoverDescription }} + </p> + </help-popover> + </template> + <!-- links --> <gl-button v-if="rawPath" diff --git a/app/assets/javascripts/jobs/components/log/collapsible_section.vue b/app/assets/javascripts/jobs/components/log/collapsible_section.vue index 757b2e458e9..13716b4d391 100644 --- a/app/assets/javascripts/jobs/components/log/collapsible_section.vue +++ b/app/assets/javascripts/jobs/components/log/collapsible_section.vue @@ -1,6 +1,4 @@ <script> -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import { INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF } from '../../constants'; import LogLine from './line.vue'; import LogLineHeader from './line_header.vue'; @@ -9,9 +7,7 @@ export default { components: { LogLine, LogLineHeader, - CollapsibleLogSection: () => import('./collapsible_section.vue'), }, - mixins: [glFeatureFlagsMixin()], props: { section: { type: Object, @@ -21,14 +17,16 @@ export default { type: String, required: true, }, + searchResults: { + type: Array, + required: false, + default: () => [], + }, }, computed: { badgeDuration() { return this.section.line && this.section.line.section_duration; }, - infinitelyCollapsibleSectionsFlag() { - return this.glFeatures?.[INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF]; - }, }, methods: { handleOnClickCollapsibleLine(section) { @@ -47,26 +45,13 @@ export default { @toggleLine="handleOnClickCollapsibleLine(section)" /> <template v-if="!section.isClosed"> - <template v-if="infinitelyCollapsibleSectionsFlag"> - <template v-for="line in section.lines"> - <collapsible-log-section - v-if="line.isHeader" - :key="line.line.offset" - :section="line" - :job-log-endpoint="jobLogEndpoint" - @onClickCollapsibleLine="handleOnClickCollapsibleLine" - /> - <log-line v-else :key="line.offset" :line="line" :path="jobLogEndpoint" /> - </template> - </template> - <template v-else> - <log-line - v-for="line in section.lines" - :key="line.offset" - :line="line" - :path="jobLogEndpoint" - /> - </template> + <log-line + v-for="line in section.lines" + :key="line.offset" + :line="line" + :path="jobLogEndpoint" + :search-results="searchResults" + /> </template> </div> </template> diff --git a/app/assets/javascripts/jobs/components/log/line.vue b/app/assets/javascripts/jobs/components/log/line.vue index 2d9714cd06b..36b350f4d64 100644 --- a/app/assets/javascripts/jobs/components/log/line.vue +++ b/app/assets/javascripts/jobs/components/log/line.vue @@ -14,9 +14,14 @@ export default { type: String, required: true, }, + searchResults: { + type: Array, + required: false, + default: () => [], + }, }, render(h, { props }) { - const { line, path } = props; + const { line, path, searchResults } = props; const chars = line.content.map((content) => { return h( @@ -46,15 +51,33 @@ export default { ); }); - return h('div', { class: 'js-line log-line' }, [ - h(LineNumber, { - props: { - lineNumber: line.lineNumber, - path, - }, - }), - ...chars, - ]); + let applyHighlight = false; + + if (searchResults.length > 0) { + const linesToHighlight = searchResults.map((searchResultLine) => searchResultLine.lineNumber); + + linesToHighlight.forEach((num) => { + if (num === line.lineNumber) { + applyHighlight = true; + } + }); + } + + return h( + 'div', + { + class: ['js-line', 'log-line', applyHighlight ? 'gl-bg-gray-500' : ''], + }, + [ + h(LineNumber, { + props: { + lineNumber: line.lineNumber, + path, + }, + }), + ...chars, + ], + ); }, }; </script> diff --git a/app/assets/javascripts/jobs/components/log/line_number.vue b/app/assets/javascripts/jobs/components/log/line_number.vue index c8ceac2c7ff..7ca9154d2fe 100644 --- a/app/assets/javascripts/jobs/components/log/line_number.vue +++ b/app/assets/javascripts/jobs/components/log/line_number.vue @@ -1,6 +1,4 @@ <script> -import { INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF } from '../../constants'; - export default { functional: true, props: { @@ -16,9 +14,7 @@ export default { render(h, { props }) { const { lineNumber, path } = props; - const parsedLineNumber = gon.features?.[INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF] - ? lineNumber - : lineNumber + 1; + const parsedLineNumber = lineNumber + 1; const lineId = `L${parsedLineNumber}`; const lineHref = `${path}#${lineId}`; diff --git a/app/assets/javascripts/jobs/components/log/log.vue b/app/assets/javascripts/jobs/components/log/log.vue index ef95d79b8ab..9647582b81d 100644 --- a/app/assets/javascripts/jobs/components/log/log.vue +++ b/app/assets/javascripts/jobs/components/log/log.vue @@ -8,6 +8,13 @@ export default { CollapsibleLogSection, LogLine, }, + props: { + searchResults: { + type: Array, + required: false, + default: () => [], + }, + }, computed: { ...mapState([ 'jobLogEndpoint', @@ -56,9 +63,16 @@ export default { :key="`collapsible-${index}`" :section="section" :job-log-endpoint="jobLogEndpoint" + :search-results="searchResults" @onClickCollapsibleLine="handleOnClickCollapsibleLine" /> - <log-line v-else :key="section.offset" :line="section" :path="jobLogEndpoint" /> + <log-line + v-else + :key="section.offset" + :line="section" + :path="jobLogEndpoint" + :search-results="searchResults" + /> </template> <div v-if="!isJobLogComplete" class="js-log-animation loader-animation pt-3 pl-3"> diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue index cc099dba72f..a42e45ee7e4 100644 --- a/app/assets/javascripts/jobs/components/sidebar.vue +++ b/app/assets/javascripts/jobs/components/sidebar.vue @@ -89,7 +89,7 @@ export default { <div class="blocks-container"> <div class="gl-py-5 gl-display-flex gl-align-items-center"> <tooltip-on-truncate :title="job.name" truncate-target="child" - ><h4 class="my-0 mr-2 gl-text-truncate"> + ><h4 class="gl-my-0 gl-mr-3 gl-text-truncate"> {{ job.name }} </h4> </tooltip-on-truncate> diff --git a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue index 2ba531c9e95..15c4e503685 100644 --- a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue +++ b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue @@ -42,14 +42,11 @@ export default { this.job.duration || this.job.finished_at || this.job.erased_at || - this.job.queued || + this.job.queued_duration || this.job.runner || this.job.coverage, ); }, - queued() { - return timeIntervalInWords(this.job.queued); - }, runnerHelpUrl() { return helpPagePath('ci/runners/configure_runners.html', { anchor: 'set-maximum-job-timeout-for-a-runner', @@ -60,6 +57,9 @@ export default { return `#${id} (${token}) ${description}`; }, + queuedDuration() { + return timeIntervalInWords(this.job.queued_duration); + }, shouldRenderBlock() { return Boolean(this.hasAnyDetail || this.hasTimeout || this.hasTags); }, @@ -98,7 +98,7 @@ export default { :title="$options.i18n.FINISHED" /> <detail-row v-if="job.erased_at" :value="erasedAt" :title="$options.i18n.ERASED" /> - <detail-row v-if="job.queued" :value="queued" :title="$options.i18n.QUEUED" /> + <detail-row v-if="job.queued_duration" :value="queuedDuration" :title="$options.i18n.QUEUED" /> <detail-row v-if="hasTimeout" :help-url="runnerHelpUrl" diff --git a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue index 02aeb46a22b..6f351d91165 100644 --- a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue +++ b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue @@ -222,7 +222,7 @@ export default { /> <gl-button v-else-if="isRetryable" - icon="repeat" + icon="retry" :title="$options.ACTIONS_RETRY" :aria-label="$options.ACTIONS_RETRY" :method="currentJobMethod" diff --git a/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql b/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql index f3ca958b3ca..5b1032c6448 100644 --- a/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql +++ b/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql @@ -1,8 +1,8 @@ -query getJobs($fullPath: ID!, $after: String, $statuses: [CiJobStatus!]) { +query getJobs($fullPath: ID!, $after: String, $first: Int = 30, $statuses: [CiJobStatus!]) { project(fullPath: $fullPath) { id __typename - jobs(after: $after, first: 30, statuses: $statuses) { + jobs(after: $after, first: $first, statuses: $statuses) { count pageInfo { endCursor diff --git a/app/assets/javascripts/jobs/components/table/jobs_table.vue b/app/assets/javascripts/jobs/components/table/jobs_table.vue index f513d2090fa..d8c5c292f52 100644 --- a/app/assets/javascripts/jobs/components/table/jobs_table.vue +++ b/app/assets/javascripts/jobs/components/table/jobs_table.vue @@ -45,6 +45,7 @@ export default { :fields="tableFields" :tbody-tr-attr="{ 'data-testid': 'jobs-table-row' }" :empty-text="$options.i18n.emptyText" + data-testid="jobs-table" show-empty stacked="lg" fixed diff --git a/app/assets/javascripts/jobs/components/table/jobs_table_app.vue b/app/assets/javascripts/jobs/components/table/jobs_table_app.vue index 1ac1a2d68e2..b3db5a94ac5 100644 --- a/app/assets/javascripts/jobs/components/table/jobs_table_app.vue +++ b/app/assets/javascripts/jobs/components/table/jobs_table_app.vue @@ -2,7 +2,6 @@ import { GlAlert, GlSkeletonLoader, GlIntersectionObserver, GlLoadingIcon } from '@gitlab/ui'; import { __ } from '~/locale'; import createFlash from '~/flash'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import JobsFilteredSearch from '../filtered_search/jobs_filtered_search.vue'; import eventHub from './event_hub'; import GetJobs from './graphql/queries/get_jobs.query.graphql'; @@ -28,7 +27,6 @@ export default { GlIntersectionObserver, GlLoadingIcon, }, - mixins: [glFeatureFlagMixin()], inject: { fullPath: { default: '', @@ -93,7 +91,7 @@ export default { return this.loading && !this.showLoadingSpinner; }, showFilteredSearch() { - return this.glFeatures?.jobsTableVueSearch && !this.scope; + return !this.scope; }, jobsCount() { return this.jobs.count; diff --git a/app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue b/app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue index 27e3b8028b7..68c6c669a1a 100644 --- a/app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue +++ b/app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue @@ -1,6 +1,7 @@ <script> import { GlBadge, GlTab, GlTabs, GlLoadingIcon } from '@gitlab/ui'; import { s__ } from '~/locale'; +import { limitedCounterWithDelimiter } from '~/lib/utils/text_utility'; export default { components: { @@ -29,7 +30,7 @@ export default { return [ { text: s__('Jobs|All'), - count: this.allJobsCount, + count: limitedCounterWithDelimiter(this.allJobsCount), scope: null, testId: 'jobs-all-tab', showBadge: true, diff --git a/app/assets/javascripts/jobs/constants.js b/app/assets/javascripts/jobs/constants.js index 97f31eee57c..3040d4e2379 100644 --- a/app/assets/javascripts/jobs/constants.js +++ b/app/assets/javascripts/jobs/constants.js @@ -24,5 +24,3 @@ export const JOB_RETRY_FORWARD_DEPLOYMENT_MODAL = { }; export const SUCCESS_STATUS = 'SUCCESS'; - -export const INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF = 'infinitelyCollapsibleSections'; diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js index 26dd38bbe08..5c63ad96ad0 100644 --- a/app/assets/javascripts/jobs/index.js +++ b/app/assets/javascripts/jobs/index.js @@ -1,10 +1,10 @@ +import { GlToast } from '@gitlab/ui'; import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import createDefaultClient from '~/lib/graphql'; -import BridgeApp from './bridge/app.vue'; import JobApp from './components/job_app.vue'; import createStore from './store'; +Vue.use(GlToast); + const initializeJobPage = (element) => { const store = createStore(); @@ -51,43 +51,7 @@ const initializeJobPage = (element) => { }); }; -const initializeBridgePage = (el) => { - const { - buildId, - downstreamPipelinePath, - emptyStateIllustrationPath, - pipelineIid, - projectFullPath, - } = el.dataset; - - Vue.use(VueApollo); - const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), - }); - - return new Vue({ - el, - apolloProvider, - provide: { - buildId, - downstreamPipelinePath, - emptyStateIllustrationPath, - pipelineIid, - projectFullPath, - }, - render(h) { - return h(BridgeApp); - }, - }); -}; - export default () => { const jobElement = document.getElementById('js-job-page'); - const bridgeElement = document.getElementById('js-bridge-page'); - - if (jobElement) { - initializeJobPage(jobElement); - } else { - initializeBridgePage(bridgeElement); - } + initializeJobPage(jobElement); }; diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js index eda2ee0349a..87c00ad4d70 100644 --- a/app/assets/javascripts/jobs/store/mutations.js +++ b/app/assets/javascripts/jobs/store/mutations.js @@ -1,7 +1,6 @@ import Vue from 'vue'; -import { INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF } from '../constants'; import * as types from './mutation_types'; -import { logLinesParser, logLinesParserLegacy, updateIncrementalJobLog } from './utils'; +import { logLinesParser, updateIncrementalJobLog } from './utils'; export default { [types.SET_JOB_ENDPOINT](state, endpoint) { @@ -21,26 +20,12 @@ export default { }, [types.RECEIVE_JOB_LOG_SUCCESS](state, log = {}) { - const infinitelyCollapsibleSectionsFlag = - gon.features?.[INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF]; if (log.state) { state.jobLogState = log.state; } if (log.append) { - if (infinitelyCollapsibleSectionsFlag) { - if (log.lines) { - const parsedResult = logLinesParser( - log.lines, - state.auxiliaryPartialJobLogHelpers, - state.jobLog, - ); - state.jobLog = parsedResult.parsedLines; - state.auxiliaryPartialJobLogHelpers = parsedResult.auxiliaryPartialJobLogHelpers; - } - } else { - state.jobLog = log.lines ? updateIncrementalJobLog(log.lines, state.jobLog) : state.jobLog; - } + state.jobLog = log.lines ? updateIncrementalJobLog(log.lines, state.jobLog) : state.jobLog; state.jobLogSize += log.size; } else { @@ -49,13 +34,7 @@ export default { // html or size. We keep the old value otherwise these // will be set to `null` - if (infinitelyCollapsibleSectionsFlag) { - const parsedResult = logLinesParser(log.lines); - state.jobLog = parsedResult.parsedLines; - state.auxiliaryPartialJobLogHelpers = parsedResult.auxiliaryPartialJobLogHelpers; - } else { - state.jobLog = log.lines ? logLinesParserLegacy(log.lines) : state.jobLog; - } + state.jobLog = log.lines ? logLinesParser(log.lines) : state.jobLog; state.jobLogSize = log.size || state.jobLogSize; } diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js index a1ba64aa71e..dfff65c364d 100644 --- a/app/assets/javascripts/jobs/store/state.js +++ b/app/assets/javascripts/jobs/store/state.js @@ -30,7 +30,4 @@ export default () => ({ selectedStage: '', stages: [], jobs: [], - - // to parse partial logs - auxiliaryPartialJobLogHelpers: {}, }); diff --git a/app/assets/javascripts/jobs/store/utils.js b/app/assets/javascripts/jobs/store/utils.js index 7dfe24afa23..a7b95154c1b 100644 --- a/app/assets/javascripts/jobs/store/utils.js +++ b/app/assets/javascripts/jobs/store/utils.js @@ -104,7 +104,7 @@ export const getIncrementalLineNumber = (acc) => { * @param Array accumulator * @returns Array parsed log lines */ -export const logLinesParserLegacy = (lines = [], accumulator = []) => +export const logLinesParser = (lines = [], accumulator = []) => lines.reduce( (acc, line, index) => { const lineNumber = accumulator.length > 0 ? getIncrementalLineNumber(acc) : index; @@ -131,82 +131,6 @@ export const logLinesParserLegacy = (lines = [], accumulator = []) => [...accumulator], ); -export const logLinesParser = (lines = [], previousJobLogState = {}, prevParsedLines = []) => { - let currentLineCount = previousJobLogState?.prevLineCount ?? 0; - let currentHeader = previousJobLogState?.currentHeader; - let isPreviousLineHeader = previousJobLogState?.isPreviousLineHeader ?? false; - const parsedLines = prevParsedLines.length > 0 ? prevParsedLines : []; - const sectionsQueue = previousJobLogState?.sectionsQueue ?? []; - - for (let i = 0; i < lines.length; i += 1) { - const line = lines[i]; - // First run we can use the current index, later runs we have to retrieve the last number of lines - currentLineCount = previousJobLogState?.prevLineCount ? currentLineCount + 1 : i + 1; - - if (line.section_header && !isPreviousLineHeader) { - // If there's no previous line header that means we're at the root of the log - - isPreviousLineHeader = true; - parsedLines.push(parseHeaderLine(line, currentLineCount)); - currentHeader = { index: parsedLines.length - 1 }; - } else if (line.section_header && isPreviousLineHeader) { - // If there's a current section, we can't push to the parsedLines array - sectionsQueue.push(currentHeader); - currentHeader = parseHeaderLine(line, currentLineCount); // Let's parse the incoming header line - } else if (line.section && !line.section_duration) { - // We're inside a collapsible section and want to parse a standard line - if (currentHeader?.index) { - // If the current section header is only an index, add the line as part of the lines - // array of the current collapsible section - parsedLines[currentHeader.index].lines.push(parseLine(line, currentLineCount)); - } else { - // Otherwise add it to the innermost collapsible section lines array - currentHeader.lines.push(parseLine(line, currentLineCount)); - } - } else if (line.section && line.section_duration) { - // NOTE: This marks the end of a section_header - const previousSection = sectionsQueue.pop(); - - // Add the duration to section header - // If at the root, just push the end to the current parsedLine, - // otherwise, push it to the previous sections queue - if (currentHeader?.index) { - parsedLines[currentHeader.index].line.section_duration = line.section_duration; - isPreviousLineHeader = false; - currentHeader = null; - } else if (currentHeader?.isHeader) { - currentHeader.line.section_duration = line.section_duration; - - if (previousSection && previousSection?.index) { - // Is the previous section on root? - parsedLines[previousSection.index].lines.push(currentHeader); - } else if (previousSection && !previousSection?.index) { - previousSection.lines.push(currentHeader); - } - - currentHeader = previousSection; - } else { - // On older job logs, there's no `section_header: true` response, it's just an object - // with the `section_duration` and `section` props, so we just parse it - // as a standard line - parsedLines.push(parseLine(line, currentLineCount)); - } - } else { - parsedLines.push(parseLine(line, currentLineCount)); - } - } - - return { - parsedLines, - auxiliaryPartialJobLogHelpers: { - isPreviousLineHeader, - currentHeader, - sectionsQueue, - prevLineCount: currentLineCount, - }, - }; -}; - /** * Finds the repeated offset, removes the old one * @@ -253,5 +177,5 @@ export const findOffsetAndRemove = (newLog = [], oldParsed = []) => { export const updateIncrementalJobLog = (newLog = [], oldParsed = []) => { const parsedLog = findOffsetAndRemove(newLog, oldParsed); - return logLinesParserLegacy(newLog, parsedLog); + return logLinesParser(newLog, parsedLog); }; |