summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/jobs
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/jobs')
-rw-r--r--app/assets/javascripts/jobs/bridge/app.vue118
-rw-r--r--app/assets/javascripts/jobs/bridge/components/constants.js1
-rw-r--r--app/assets/javascripts/jobs/bridge/components/empty_state.vue45
-rw-r--r--app/assets/javascripts/jobs/bridge/components/sidebar.vue105
-rw-r--r--app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql70
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue12
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue95
-rw-r--r--app/assets/javascripts/jobs/components/log/collapsible_section.vue39
-rw-r--r--app/assets/javascripts/jobs/components/log/line.vue43
-rw-r--r--app/assets/javascripts/jobs/components/log/line_number.vue6
-rw-r--r--app/assets/javascripts/jobs/components/log/log.vue16
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue2
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_job_details_container.vue10
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/actions_cell.vue2
-rw-r--r--app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql4
-rw-r--r--app/assets/javascripts/jobs/components/table/jobs_table.vue1
-rw-r--r--app/assets/javascripts/jobs/components/table/jobs_table_app.vue4
-rw-r--r--app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue3
-rw-r--r--app/assets/javascripts/jobs/constants.js2
-rw-r--r--app/assets/javascripts/jobs/index.js44
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js27
-rw-r--r--app/assets/javascripts/jobs/store/state.js3
-rw-r--r--app/assets/javascripts/jobs/store/utils.js80
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);
};