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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/jobs/components/job')
-rw-r--r--app/assets/javascripts/jobs/components/job/empty_state.vue31
-rw-r--r--app/assets/javascripts/jobs/components/job/graphql/mutations/job_retry_with_variables.mutation.graphql16
-rw-r--r--app/assets/javascripts/jobs/components/job/graphql/queries/get_job.query.graphql17
-rw-r--r--app/assets/javascripts/jobs/components/job/job_app.vue25
-rw-r--r--app/assets/javascripts/jobs/components/job/legacy_manual_variables_form.vue192
-rw-r--r--app/assets/javascripts/jobs/components/job/manual_variables_form.vue163
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/job_sidebar_retry_button.vue29
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue104
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/sidebar.vue21
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/sidebar_header.vue87
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/sidebar_job_details_container.vue7
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/trigger_block.vue6
12 files changed, 297 insertions, 401 deletions
diff --git a/app/assets/javascripts/jobs/components/job/empty_state.vue b/app/assets/javascripts/jobs/components/job/empty_state.vue
index 65b9600e664..d0a39025807 100644
--- a/app/assets/javascripts/jobs/components/job/empty_state.vue
+++ b/app/assets/javascripts/jobs/components/job/empty_state.vue
@@ -1,16 +1,12 @@
<script>
import { GlLink } from '@gitlab/ui';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import LegacyManualVariablesForm from '~/jobs/components/job/legacy_manual_variables_form.vue';
import ManualVariablesForm from '~/jobs/components/job/manual_variables_form.vue';
export default {
components: {
GlLink,
- LegacyManualVariablesForm,
ManualVariablesForm,
},
- mixins: [glFeatureFlagsMixin()],
props: {
illustrationPath: {
type: String,
@@ -20,6 +16,14 @@ export default {
type: String,
required: true,
},
+ isRetryable: {
+ type: Boolean,
+ required: true,
+ },
+ jobId: {
+ type: Number,
+ required: true,
+ },
title: {
type: String,
required: true,
@@ -54,9 +58,6 @@ export default {
},
},
computed: {
- isGraphQL() {
- return this.glFeatures?.graphqlJobApp;
- },
shouldRenderManualVariables() {
return this.playable && !this.scheduled;
},
@@ -77,14 +78,14 @@ export default {
<p v-if="content" data-testid="job-empty-state-content">{{ content }}</p>
</div>
- <template v-if="isGraphQL">
- <manual-variables-form v-if="shouldRenderManualVariables" :action="action" />
- </template>
- <template v-else>
- <legacy-manual-variables-form v-if="shouldRenderManualVariables" :action="action" />
- </template>
- <div class="text-content">
- <div v-if="action && !shouldRenderManualVariables" class="text-center">
+ <manual-variables-form
+ v-if="shouldRenderManualVariables"
+ :is-retryable="isRetryable"
+ :job-id="jobId"
+ @hideManualVariablesForm="$emit('hideManualVariablesForm')"
+ />
+ <div v-if="action && !shouldRenderManualVariables" class="text-content">
+ <div class="text-center">
<gl-link
:href="action.path"
:data-method="action.method"
diff --git a/app/assets/javascripts/jobs/components/job/graphql/mutations/job_retry_with_variables.mutation.graphql b/app/assets/javascripts/jobs/components/job/graphql/mutations/job_retry_with_variables.mutation.graphql
new file mode 100644
index 00000000000..2b79892a072
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job/graphql/mutations/job_retry_with_variables.mutation.graphql
@@ -0,0 +1,16 @@
+mutation retryJobWithVariables($id: CiBuildID!, $variables: [CiVariableInput!]) {
+ jobRetry(input: { id: $id, variables: $variables }) {
+ job {
+ id
+ manualVariables {
+ nodes {
+ id
+ key
+ value
+ }
+ }
+ webPath
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/jobs/components/job/graphql/queries/get_job.query.graphql b/app/assets/javascripts/jobs/components/job/graphql/queries/get_job.query.graphql
new file mode 100644
index 00000000000..aaf1dec8e0f
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job/graphql/queries/get_job.query.graphql
@@ -0,0 +1,17 @@
+query getJob($fullPath: ID!, $id: JobID!) {
+ project(fullPath: $fullPath) {
+ id
+ job(id: $id) {
+ id
+ manualJob
+ manualVariables {
+ nodes {
+ id
+ key
+ value
+ }
+ }
+ name
+ }
+ }
+}
diff --git a/app/assets/javascripts/jobs/components/job/job_app.vue b/app/assets/javascripts/jobs/components/job/job_app.vue
index 81b65d175a7..c6d900ef13e 100644
--- a/app/assets/javascripts/jobs/components/job/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job/job_app.vue
@@ -1,8 +1,9 @@
<script>
-import { GlLoadingIcon, GlIcon, GlSafeHtmlDirective as SafeHtml, GlAlert } from '@gitlab/ui';
+import { GlLoadingIcon, GlIcon, GlAlert } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { throttle, isEmpty } from 'lodash';
import { mapGetters, mapState, mapActions } from 'vuex';
+import SafeHtml from '~/vue_shared/directives/safe_html';
import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
import { __, sprintf } from '~/locale';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
@@ -71,6 +72,7 @@ export default {
data() {
return {
searchResults: [],
+ showUpdateVariablesState: false,
};
},
computed: {
@@ -121,6 +123,10 @@ export default {
return this.shouldRenderCalloutMessage && !this.hasUnmetPrerequisitesFailure;
},
+ isJobRetryable() {
+ return Boolean(this.job.retry_path);
+ },
+
itemName() {
return sprintf(__('Job %{jobName}'), { jobName: this.job.name });
},
@@ -168,10 +174,16 @@ export default {
'toggleScrollButtons',
'toggleScrollAnimation',
]),
+ onHideManualVariablesForm() {
+ this.showUpdateVariablesState = false;
+ },
onResize() {
this.updateSidebar();
this.updateScroll();
},
+ onUpdateVariables() {
+ this.showUpdateVariablesState = true;
+ },
updateSidebar() {
const breakpoint = bp.getBreakpointSize();
if (breakpoint === 'xs' || breakpoint === 'sm') {
@@ -271,14 +283,12 @@ export default {
</div>
<!-- job log -->
<div
- v-if="hasJobLog"
+ v-if="hasJobLog && !showUpdateVariablesState"
class="build-log-container gl-relative"
:class="{ 'gl-mt-3': !job.archived }"
>
<log-top-bar
:class="{
- 'sidebar-expanded': isSidebarOpen,
- 'sidebar-collapsed': !isSidebarOpen,
'has-archived-block': job.archived,
}"
:size="jobLogSize"
@@ -299,14 +309,17 @@ export default {
<!-- empty state -->
<empty-state
- v-if="!hasJobLog"
+ v-if="!hasJobLog || showUpdateVariablesState"
:illustration-path="emptyStateIllustration.image"
:illustration-size-class="emptyStateIllustration.size"
+ :is-retryable="isJobRetryable"
+ :job-id="job.id"
:title="emptyStateTitle"
:content="emptyStateIllustration.content"
:action="emptyStateAction"
:playable="job.playable"
:scheduled="job.scheduled"
+ @hideManualVariablesForm="onHideManualVariablesForm()"
/>
<!-- EO empty state -->
@@ -320,9 +333,9 @@ export default {
'right-sidebar-expanded': isSidebarOpen,
'right-sidebar-collapsed': !isSidebarOpen,
}"
- :erase-path="job.erase_path"
:artifact-help-url="artifactHelpUrl"
data-testid="job-sidebar"
+ @updateVariables="onUpdateVariables()"
/>
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/job/legacy_manual_variables_form.vue b/app/assets/javascripts/jobs/components/job/legacy_manual_variables_form.vue
deleted file mode 100644
index 1898e02c94e..00000000000
--- a/app/assets/javascripts/jobs/components/job/legacy_manual_variables_form.vue
+++ /dev/null
@@ -1,192 +0,0 @@
-<script>
-import {
- GlFormInputGroup,
- GlInputGroupText,
- GlFormInput,
- GlButton,
- GlLink,
- GlSprintf,
-} from '@gitlab/ui';
-import { uniqueId } from 'lodash';
-import { mapActions } from 'vuex';
-import { helpPagePath } from '~/helpers/help_page_helper';
-import { s__ } from '~/locale';
-
-export default {
- name: 'ManualVariablesForm',
- components: {
- GlFormInputGroup,
- GlInputGroupText,
- GlFormInput,
- GlButton,
- GlLink,
- GlSprintf,
- },
- props: {
- action: {
- type: Object,
- required: false,
- default: null,
- validator(value) {
- return (
- value === null ||
- (Object.prototype.hasOwnProperty.call(value, 'path') &&
- Object.prototype.hasOwnProperty.call(value, 'method') &&
- Object.prototype.hasOwnProperty.call(value, 'button_title'))
- );
- },
- },
- },
- inputTypes: {
- key: 'key',
- value: 'value',
- },
- i18n: {
- header: s__('CiVariables|Variables'),
- keyLabel: s__('CiVariables|Key'),
- valueLabel: s__('CiVariables|Value'),
- keyPlaceholder: s__('CiVariables|Input variable key'),
- valuePlaceholder: s__('CiVariables|Input variable value'),
- formHelpText: s__(
- 'CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default',
- ),
- },
- data() {
- return {
- variables: [
- {
- key: '',
- secretValue: '',
- id: uniqueId(),
- },
- ],
- triggerBtnDisabled: false,
- };
- },
- computed: {
- variableSettings() {
- return helpPagePath('ci/variables/index', { anchor: 'add-a-cicd-variable-to-a-project' });
- },
- preparedVariables() {
- // we need to ensure no empty variables are passed to the API
- // and secretValue should be snake_case when passed to the API
- return this.variables
- .filter((variable) => variable.key !== '')
- .map(({ key, secretValue }) => ({ key, secret_value: secretValue }));
- },
- },
- methods: {
- ...mapActions(['triggerManualJob']),
- addEmptyVariable() {
- const lastVar = this.variables[this.variables.length - 1];
-
- if (lastVar.key === '') {
- return;
- }
-
- this.variables.push({
- key: '',
- secret_value: '',
- id: uniqueId(),
- });
- },
- canRemove(index) {
- return index < this.variables.length - 1;
- },
- deleteVariable(id) {
- this.variables.splice(
- this.variables.findIndex((el) => el.id === id),
- 1,
- );
- },
- inputRef(type, id) {
- return `${this.$options.inputTypes[type]}-${id}`;
- },
- trigger() {
- this.triggerBtnDisabled = true;
-
- this.triggerManualJob(this.preparedVariables);
- },
- },
-};
-</script>
-<template>
- <div class="row gl-justify-content-center">
- <div class="col-10" data-testid="manual-vars-form">
- <label>{{ $options.i18n.header }}</label>
-
- <div
- v-for="(variable, index) in variables"
- :key="variable.id"
- class="gl-display-flex gl-align-items-center gl-mb-4"
- data-testid="ci-variable-row"
- >
- <gl-form-input-group class="gl-mr-4 gl-flex-grow-1">
- <template #prepend>
- <gl-input-group-text>
- {{ $options.i18n.keyLabel }}
- </gl-input-group-text>
- </template>
- <gl-form-input
- :ref="inputRef('key', variable.id)"
- v-model="variable.key"
- :placeholder="$options.i18n.keyPlaceholder"
- data-testid="ci-variable-key"
- @change="addEmptyVariable"
- />
- </gl-form-input-group>
-
- <gl-form-input-group class="gl-flex-grow-2">
- <template #prepend>
- <gl-input-group-text>
- {{ $options.i18n.valueLabel }}
- </gl-input-group-text>
- </template>
- <gl-form-input
- :ref="inputRef('value', variable.id)"
- v-model="variable.secretValue"
- :placeholder="$options.i18n.valuePlaceholder"
- data-testid="ci-variable-value"
- />
- </gl-form-input-group>
-
- <gl-button
- v-if="canRemove(index)"
- class="gl-flex-grow-0 gl-flex-basis-0"
- category="tertiary"
- variant="danger"
- icon="clear"
- :aria-label="__('Delete variable')"
- data-testid="delete-variable-btn"
- @click="deleteVariable(variable.id)"
- />
-
- <!-- delete variable button placeholder to not break flex layout -->
- <div v-else class="gl-w-7 gl-mr-3" data-testid="delete-variable-btn-placeholder"></div>
- </div>
-
- <div class="gl-text-center gl-mt-5">
- <gl-sprintf :message="$options.i18n.formHelpText">
- <template #link="{ content }">
- <gl-link :href="variableSettings" target="_blank">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
- </div>
- <div class="gl-display-flex gl-justify-content-center gl-mt-5">
- <gl-button
- class="gl-mt-5"
- variant="confirm"
- category="primary"
- :aria-label="__('Trigger manual job')"
- :disabled="triggerBtnDisabled"
- data-testid="trigger-manual-job-btn"
- @click="trigger"
- >
- {{ action.button_title }}
- </gl-button>
- </div>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/jobs/components/job/manual_variables_form.vue b/app/assets/javascripts/jobs/components/job/manual_variables_form.vue
index 2f97301979c..d7bbd6daed2 100644
--- a/app/assets/javascripts/jobs/components/job/manual_variables_form.vue
+++ b/app/assets/javascripts/jobs/components/job/manual_variables_form.vue
@@ -5,15 +5,24 @@ import {
GlFormInput,
GlButton,
GlLink,
+ GlLoadingIcon,
GlSprintf,
+ GlTooltipDirective,
} from '@gitlab/ui';
-import { uniqueId } from 'lodash';
+import { cloneDeep, uniqueId } from 'lodash';
import { mapActions } from 'vuex';
+import { fetchPolicies } from '~/lib/graphql';
+import { createAlert } from '~/flash';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { JOB_GRAPHQL_ERRORS, GRAPHQL_ID_TYPES } from '~/jobs/constants';
import { helpPagePath } from '~/helpers/help_page_helper';
+import { redirectTo } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
+import GetJob from './graphql/queries/get_job.query.graphql';
+import retryJobWithVariablesMutation from './graphql/mutations/job_retry_with_variables.mutation.graphql';
// This component is a port of ~/jobs/components/job/legacy_manual_variables_form.vue
-// It is meant to fetch the job information via GraphQL instead of REST API.
+// It is meant to fetch/update the job information via GraphQL instead of REST API.
export default {
name: 'ManualVariablesForm',
@@ -23,59 +32,93 @@ export default {
GlFormInput,
GlButton,
GlLink,
+ GlLoadingIcon,
GlSprintf,
},
- props: {
- action: {
- type: Object,
- required: false,
- default: null,
- validator(value) {
- return (
- value === null ||
- (Object.prototype.hasOwnProperty.call(value, 'path') &&
- Object.prototype.hasOwnProperty.call(value, 'method') &&
- Object.prototype.hasOwnProperty.call(value, 'button_title'))
- );
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ inject: ['projectPath'],
+ apollo: {
+ variables: {
+ query: GetJob,
+ variables() {
+ return {
+ fullPath: this.projectPath,
+ id: convertToGraphQLId(GRAPHQL_ID_TYPES.commitStatus, this.jobId),
+ };
+ },
+ fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
+ update(data) {
+ const jobVariables = cloneDeep(data?.project?.job?.manualVariables?.nodes);
+ return [...jobVariables.reverse(), ...this.variables];
+ },
+ error() {
+ createAlert({ message: JOB_GRAPHQL_ERRORS.jobQueryErrorText });
},
},
},
+ props: {
+ isRetryable: {
+ type: Boolean,
+ required: true,
+ },
+ jobId: {
+ type: Number,
+ required: true,
+ },
+ },
inputTypes: {
key: 'key',
value: 'value',
},
i18n: {
+ clearInputs: s__('CiVariables|Clear inputs'),
+ formHelpText: s__(
+ 'CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default',
+ ),
header: s__('CiVariables|Variables'),
keyLabel: s__('CiVariables|Key'),
- valueLabel: s__('CiVariables|Value'),
keyPlaceholder: s__('CiVariables|Input variable key'),
+ runAgainButtonText: s__('CiVariables|Run job again'),
+ triggerButtonText: s__('CiVariables|Trigger this manual action'),
+ valueLabel: s__('CiVariables|Value'),
valuePlaceholder: s__('CiVariables|Input variable value'),
- formHelpText: s__(
- 'CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default',
- ),
+ },
+ variableValueKeys: {
+ rest: 'secret_value',
+ gql: 'value',
},
data() {
return {
+ job: {},
variables: [
{
- key: '',
- secretValue: '',
id: uniqueId(),
+ key: '',
+ value: '',
},
],
+ runAgainBtnDisabled: false,
triggerBtnDisabled: false,
};
},
computed: {
- variableSettings() {
- return helpPagePath('ci/variables/index', { anchor: 'add-a-cicd-variable-to-a-project' });
- },
preparedVariables() {
- // we need to ensure no empty variables are passed to the API
- // and secretValue should be snake_case when passed to the API
+ // filtering out 'id' along with empty variables to send only key, value in the mutation.
+ // This will be removed in: https://gitlab.com/gitlab-org/gitlab/-/issues/377268
+
return this.variables
.filter((variable) => variable.key !== '')
- .map(({ key, secretValue }) => ({ key, secret_value: secretValue }));
+ .map(({ key, value }) => ({ key, [this.valueKey]: value }));
+ },
+ valueKey() {
+ return this.isRetryable
+ ? this.$options.variableValueKeys.gql
+ : this.$options.variableValueKeys.rest;
+ },
+ variableSettings() {
+ return helpPagePath('ci/variables/index', { anchor: 'add-a-cicd-variable-to-a-project' });
},
},
methods: {
@@ -88,9 +131,9 @@ export default {
}
this.variables.push({
- key: '',
- secret_value: '',
id: uniqueId(),
+ key: '',
+ value: '',
});
},
canRemove(index) {
@@ -105,7 +148,34 @@ export default {
inputRef(type, id) {
return `${this.$options.inputTypes[type]}-${id}`;
},
- trigger() {
+ navigateToRetriedJob(retryPath) {
+ redirectTo(retryPath);
+ },
+ async retryJob() {
+ try {
+ const { data } = await this.$apollo.mutate({
+ mutation: retryJobWithVariablesMutation,
+ variables: {
+ id: convertToGraphQLId(GRAPHQL_ID_TYPES.ciBuild, this.jobId),
+ // we need to ensure no empty variables are passed to the API
+ variables: this.preparedVariables,
+ },
+ });
+ if (data.jobRetry?.errors?.length) {
+ createAlert({ message: data.jobRetry.errors[0] });
+ } else {
+ this.navigateToRetriedJob(data.jobRetry?.job?.webPath);
+ }
+ } catch (error) {
+ createAlert({ message: JOB_GRAPHQL_ERRORS.retryMutationErrorText });
+ }
+ },
+ runAgain() {
+ this.runAgainBtnDisabled = true;
+
+ this.retryJob();
+ },
+ triggerJob() {
this.triggerBtnDisabled = true;
this.triggerManualJob(this.preparedVariables);
@@ -114,7 +184,8 @@ export default {
};
</script>
<template>
- <div class="row gl-justify-content-center">
+ <gl-loading-icon v-if="$apollo.queries.variables.loading" class="gl-mt-9" size="lg" />
+ <div v-else class="row gl-justify-content-center">
<div class="col-10" data-testid="manual-vars-form">
<label>{{ $options.i18n.header }}</label>
@@ -147,7 +218,7 @@ export default {
</template>
<gl-form-input
:ref="inputRef('value', variable.id)"
- v-model="variable.secretValue"
+ v-model="variable.value"
:placeholder="$options.i18n.valuePlaceholder"
data-testid="ci-variable-value"
/>
@@ -155,11 +226,13 @@ export default {
<gl-button
v-if="canRemove(index)"
+ v-gl-tooltip
+ :aria-label="$options.i18n.clearInputs"
+ :title="$options.i18n.clearInputs"
class="gl-flex-grow-0 gl-flex-basis-0"
category="tertiary"
variant="danger"
icon="clear"
- :aria-label="__('Delete variable')"
data-testid="delete-variable-btn"
@click="deleteVariable(variable.id)"
/>
@@ -177,7 +250,27 @@ export default {
</template>
</gl-sprintf>
</div>
- <div class="gl-display-flex gl-justify-content-center gl-mt-5">
+ <div v-if="isRetryable" class="gl-display-flex gl-justify-content-center gl-mt-5">
+ <gl-button
+ class="gl-mt-5"
+ :aria-label="__('Cancel')"
+ data-testid="cancel-btn"
+ @click="$emit('hideManualVariablesForm')"
+ >{{ __('Cancel') }}</gl-button
+ >
+ <gl-button
+ class="gl-mt-5"
+ variant="confirm"
+ category="primary"
+ :aria-label="__('Run manual job again')"
+ :disabled="runAgainBtnDisabled"
+ data-testid="run-manual-job-btn"
+ @click="runAgain"
+ >
+ {{ $options.i18n.runAgainButtonText }}
+ </gl-button>
+ </div>
+ <div v-else class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-button
class="gl-mt-5"
variant="confirm"
@@ -185,9 +278,9 @@ export default {
:aria-label="__('Trigger manual job')"
:disabled="triggerBtnDisabled"
data-testid="trigger-manual-job-btn"
- @click="trigger"
+ @click="triggerJob"
>
- {{ action.button_title }}
+ {{ $options.i18n.triggerButtonText }}
</gl-button>
</div>
</div>
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/job_sidebar_retry_button.vue b/app/assets/javascripts/jobs/components/job/sidebar/job_sidebar_retry_button.vue
index dd620977f0c..7183a8b5d03 100644
--- a/app/assets/javascripts/jobs/components/job/sidebar/job_sidebar_retry_button.vue
+++ b/app/assets/javascripts/jobs/components/job/sidebar/job_sidebar_retry_button.vue
@@ -1,15 +1,17 @@
<script>
-import { GlButton, GlModalDirective } from '@gitlab/ui';
+import { GlButton, GlDropdown, GlDropdownItem, GlModalDirective } from '@gitlab/ui';
import { mapGetters } from 'vuex';
import { JOB_SIDEBAR_COPY } from '~/jobs/constants';
export default {
name: 'JobSidebarRetryButton',
i18n: {
- retryLabel: JOB_SIDEBAR_COPY.retry,
+ ...JOB_SIDEBAR_COPY,
},
components: {
GlButton,
+ GlDropdown,
+ GlDropdownItem,
},
directives: {
GlModal: GlModalDirective,
@@ -23,6 +25,10 @@ export default {
type: String,
required: true,
},
+ isManualJob: {
+ type: Boolean,
+ required: true,
+ },
},
computed: {
...mapGetters(['hasForwardDeploymentFailure']),
@@ -33,17 +39,30 @@ export default {
<gl-button
v-if="hasForwardDeploymentFailure"
v-gl-modal="modalId"
- :aria-label="$options.i18n.retryLabel"
+ :aria-label="$options.i18n.retryJobLabel"
category="primary"
variant="confirm"
icon="retry"
data-testid="retry-job-button"
/>
-
+ <gl-dropdown
+ v-else-if="isManualJob"
+ icon="retry"
+ category="primary"
+ :right="true"
+ variant="confirm"
+ >
+ <gl-dropdown-item :href="href" data-method="post">
+ {{ $options.i18n.runAgainJobButtonLabel }}
+ </gl-dropdown-item>
+ <gl-dropdown-item @click="$emit('updateVariablesClicked')">
+ {{ $options.i18n.updateVariables }}
+ </gl-dropdown-item>
+ </gl-dropdown>
<gl-button
v-else
:href="href"
- :aria-label="$options.i18n.retryLabel"
+ :aria-label="$options.i18n.retryJobLabel"
category="primary"
variant="confirm"
icon="retry"
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue b/app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue
deleted file mode 100644
index 64b497c3550..00000000000
--- a/app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue
+++ /dev/null
@@ -1,104 +0,0 @@
-<script>
-import { GlButton, GlTooltipDirective } from '@gitlab/ui';
-import { mapActions } from 'vuex';
-import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
-import { JOB_SIDEBAR_COPY, forwardDeploymentFailureModalId } from '~/jobs/constants';
-import JobSidebarRetryButton from './job_sidebar_retry_button.vue';
-
-export default {
- name: 'LegacySidebarHeader',
- i18n: {
- ...JOB_SIDEBAR_COPY,
- },
- forwardDeploymentFailureModalId,
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- components: {
- GlButton,
- JobSidebarRetryButton,
- TooltipOnTruncate,
- },
- props: {
- job: {
- type: Object,
- required: true,
- default: () => ({}),
- },
- erasePath: {
- type: String,
- required: false,
- default: null,
- },
- },
- computed: {
- retryButtonCategory() {
- return this.job.status && this.job.recoverable ? 'primary' : 'secondary';
- },
- buttonTitle() {
- return this.job.status && this.job.status.text === 'passed'
- ? this.$options.i18n.runAgainJobButtonLabel
- : this.$options.i18n.retryJobButtonLabel;
- },
- },
- methods: {
- ...mapActions(['toggleSidebar']),
- },
-};
-</script>
-
-<template>
- <div class="gl-py-5 gl-display-flex gl-align-items-center">
- <tooltip-on-truncate :title="job.name" truncate-target="child"
- ><h4 class="gl-my-0 gl-mr-3 gl-text-truncate">
- {{ job.name }}
- </h4>
- </tooltip-on-truncate>
- <div class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right">
- <gl-button
- v-if="erasePath"
- v-gl-tooltip.left
- :title="$options.i18n.eraseLogButtonLabel"
- :aria-label="$options.i18n.eraseLogButtonLabel"
- :href="erasePath"
- :data-confirm="$options.i18n.eraseLogConfirmText"
- class="gl-mr-2"
- data-testid="job-log-erase-link"
- data-confirm-btn-variant="danger"
- data-method="post"
- icon="remove"
- />
- <job-sidebar-retry-button
- v-if="job.retry_path"
- v-gl-tooltip.left
- :title="buttonTitle"
- :aria-label="buttonTitle"
- :category="retryButtonCategory"
- :href="job.retry_path"
- :modal-id="$options.forwardDeploymentFailureModalId"
- variant="confirm"
- data-qa-selector="retry_button"
- data-testid="retry-button"
- />
- <gl-button
- v-if="job.cancel_path"
- v-gl-tooltip.left
- :title="$options.i18n.cancelJobButtonLabel"
- :aria-label="$options.i18n.cancelJobButtonLabel"
- :href="job.cancel_path"
- variant="danger"
- icon="cancel"
- data-method="post"
- data-testid="cancel-button"
- rel="nofollow"
- />
- <gl-button
- :aria-label="$options.i18n.toggleSidebar"
- category="tertiary"
- class="gl-md-display-none gl-ml-2"
- icon="chevron-double-lg-right"
- @click="toggleSidebar"
- />
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/sidebar.vue b/app/assets/javascripts/jobs/components/job/sidebar/sidebar.vue
index aac6a0ad6d3..69271cc9022 100644
--- a/app/assets/javascripts/jobs/components/job/sidebar/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/job/sidebar/sidebar.vue
@@ -2,14 +2,12 @@
import { GlButton, GlIcon } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { mapActions, mapGetters, mapState } from 'vuex';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { JOB_SIDEBAR_COPY, forwardDeploymentFailureModalId } from '~/jobs/constants';
+import ArtifactsBlock from './artifacts_block.vue';
import CommitBlock from './commit_block.vue';
import JobsContainer from './jobs_container.vue';
import JobRetryForwardDeploymentModal from './job_retry_forward_deployment_modal.vue';
import JobSidebarDetailsContainer from './sidebar_job_details_container.vue';
-import ArtifactsBlock from './artifacts_block.vue';
-import LegacySidebarHeader from './legacy_sidebar_header.vue';
import SidebarHeader from './sidebar_header.vue';
import StagesDropdown from './stages_dropdown.vue';
import TriggerBlock from './trigger_block.vue';
@@ -29,23 +27,16 @@ export default {
JobsContainer,
JobRetryForwardDeploymentModal,
JobSidebarDetailsContainer,
- LegacySidebarHeader,
SidebarHeader,
StagesDropdown,
TriggerBlock,
},
- mixins: [glFeatureFlagsMixin()],
props: {
artifactHelpUrl: {
type: String,
required: false,
default: '',
},
- erasePath: {
- type: String,
- required: false,
- default: null,
- },
},
computed: {
...mapGetters(['hasForwardDeploymentFailure']),
@@ -57,9 +48,6 @@ export default {
hasTriggers() {
return !isEmpty(this.job.trigger);
},
- isGraphQL() {
- return this.glFeatures?.graphqlJobApp;
- },
commit() {
return this.job?.pipeline?.commit || {};
},
@@ -89,8 +77,11 @@ export default {
<aside class="right-sidebar build-sidebar" data-offset-top="101" data-spy="affix">
<div class="sidebar-container">
<div class="blocks-container">
- <sidebar-header v-if="isGraphQL" :erase-path="erasePath" :job="job" />
- <legacy-sidebar-header v-else :erase-path="erasePath" :job="job" />
+ <sidebar-header
+ :rest-job="job"
+ :job-id="job.id"
+ @updateVariables="$emit('updateVariables')"
+ />
<div
v-if="job.terminal_path || job.new_issue_path"
class="gl-py-5"
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/sidebar_header.vue b/app/assets/javascripts/jobs/components/job/sidebar/sidebar_header.vue
index 523710598bf..40aec0b0536 100644
--- a/app/assets/javascripts/jobs/components/job/sidebar/sidebar_header.vue
+++ b/app/assets/javascripts/jobs/components/job/sidebar/sidebar_header.vue
@@ -1,13 +1,19 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import { mapActions } from 'vuex';
+import { createAlert } from '~/flash';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
-import { JOB_SIDEBAR_COPY, forwardDeploymentFailureModalId } from '~/jobs/constants';
+import {
+ JOB_GRAPHQL_ERRORS,
+ GRAPHQL_ID_TYPES,
+ JOB_SIDEBAR_COPY,
+ forwardDeploymentFailureModalId,
+ PASSED_STATUS,
+} from '~/jobs/constants';
+import GetJob from '../graphql/queries/get_job.query.graphql';
import JobSidebarRetryButton from './job_sidebar_retry_button.vue';
-// This component is a port of ~/jobs/components/job/sidebar/legacy_sidebar_header.vue
-// It is meant to fetch the job information via GraphQL instead of REST API.
-
export default {
name: 'SidebarHeader',
i18n: {
@@ -22,21 +28,58 @@ export default {
JobSidebarRetryButton,
TooltipOnTruncate,
},
- props: {
+ inject: ['projectPath'],
+ apollo: {
job: {
+ query: GetJob,
+ variables() {
+ return {
+ fullPath: this.projectPath,
+ id: convertToGraphQLId(GRAPHQL_ID_TYPES.commitStatus, this.jobId),
+ };
+ },
+ update(data) {
+ const { name, manualJob } = data?.project?.job || {};
+ return {
+ name,
+ manualJob,
+ };
+ },
+ error() {
+ createAlert({ message: JOB_GRAPHQL_ERRORS.jobQueryErrorText });
+ },
+ },
+ },
+ props: {
+ jobId: {
+ type: Number,
+ required: true,
+ },
+ restJob: {
type: Object,
required: true,
default: () => ({}),
},
- erasePath: {
- type: String,
- required: false,
- default: null,
- },
+ },
+ data() {
+ return {
+ job: {},
+ };
},
computed: {
+ buttonTitle() {
+ return this.restJob.status?.text === PASSED_STATUS
+ ? this.$options.i18n.runAgainJobButtonLabel
+ : this.$options.i18n.retryJobLabel;
+ },
+ canShowJobRetryButton() {
+ return this.restJob.retry_path && !this.$apollo.queries.job.loading;
+ },
+ isManualJob() {
+ return this.job?.manualJob;
+ },
retryButtonCategory() {
- return this.job.status && this.job.recoverable ? 'primary' : 'secondary';
+ return this.restJob.status && this.restJob.recoverable ? 'primary' : 'secondary';
},
},
methods: {
@@ -48,17 +91,15 @@ export default {
<template>
<div class="gl-py-5 gl-display-flex gl-align-items-center">
<tooltip-on-truncate :title="job.name" truncate-target="child"
- ><h4 class="gl-my-0 gl-mr-3 gl-text-truncate">
- {{ job.name }}
- </h4>
+ ><h4 class="gl-my-0 gl-mr-3 gl-text-truncate" data-testid="job-name">{{ job.name }}</h4>
</tooltip-on-truncate>
<div class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right">
<gl-button
- v-if="erasePath"
+ v-if="restJob.erase_path"
v-gl-tooltip.left
:title="$options.i18n.eraseLogButtonLabel"
:aria-label="$options.i18n.eraseLogButtonLabel"
- :href="erasePath"
+ :href="restJob.erase_path"
:data-confirm="$options.i18n.eraseLogConfirmText"
class="gl-mr-2"
data-testid="job-log-erase-link"
@@ -67,23 +108,25 @@ export default {
icon="remove"
/>
<job-sidebar-retry-button
- v-if="job.retry_path"
+ v-if="canShowJobRetryButton"
v-gl-tooltip.left
- :title="$options.i18n.retryJobButtonLabel"
- :aria-label="$options.i18n.retryJobButtonLabel"
+ :title="buttonTitle"
+ :aria-label="buttonTitle"
+ :is-manual-job="isManualJob"
:category="retryButtonCategory"
- :href="job.retry_path"
+ :href="restJob.retry_path"
:modal-id="$options.forwardDeploymentFailureModalId"
variant="confirm"
data-qa-selector="retry_button"
data-testid="retry-button"
+ @updateVariablesClicked="$emit('updateVariables')"
/>
<gl-button
- v-if="job.cancel_path"
+ v-if="restJob.cancel_path"
v-gl-tooltip.left
:title="$options.i18n.cancelJobButtonLabel"
:aria-label="$options.i18n.cancelJobButtonLabel"
- :href="job.cancel_path"
+ :href="restJob.cancel_path"
variant="danger"
icon="cancel"
data-method="post"
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/sidebar_job_details_container.vue b/app/assets/javascripts/jobs/components/job/sidebar/sidebar_job_details_container.vue
index 3b1509e5be5..8300a22cb67 100644
--- a/app/assets/javascripts/jobs/components/job/sidebar/sidebar_job_details_container.vue
+++ b/app/assets/javascripts/jobs/components/job/sidebar/sidebar_job_details_container.vue
@@ -1,6 +1,7 @@
<script>
import { mapState } from 'vuex';
import { GlBadge } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
import { __, sprintf } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
@@ -79,7 +80,9 @@ export default {
TAGS: __('Tags:'),
TIMEOUT: __('Timeout'),
},
- RUNNER_HELP_URL: 'https://docs.gitlab.com/runner/register/index.html',
+ TIMEOUT_HELP_URL: helpPagePath('/ci/pipelines/settings.md', {
+ anchor: 'set-a-limit-for-how-long-jobs-can-run',
+ }),
};
</script>
@@ -96,7 +99,7 @@ export default {
<detail-row v-if="job.queued_duration" :value="queuedDuration" :title="$options.i18n.QUEUED" />
<detail-row
v-if="hasTimeout"
- :help-url="$options.RUNNER_HELP_URL"
+ :help-url="$options.TIMEOUT_HELP_URL"
:value="timeout"
data-testid="job-timeout"
:title="$options.i18n.TIMEOUT"
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/trigger_block.vue b/app/assets/javascripts/jobs/components/job/sidebar/trigger_block.vue
index 1afc1c9a595..c9172fe0322 100644
--- a/app/assets/javascripts/jobs/components/job/sidebar/trigger_block.vue
+++ b/app/assets/javascripts/jobs/components/job/sidebar/trigger_block.vue
@@ -2,9 +2,7 @@
import { GlButton, GlTableLite } from '@gitlab/ui';
import { __ } from '~/locale';
-const DEFAULT_TD_CLASSES = 'gl-w-half gl-font-sm! gl-border-gray-200!';
-const DEFAULT_TH_CLASSES =
- 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-200! gl-border-b-1!';
+const DEFAULT_TD_CLASSES = 'gl-font-sm!';
export default {
fields: [
@@ -13,14 +11,12 @@ export default {
label: __('Key'),
tdAttr: { 'data-testid': 'trigger-build-key' },
tdClass: DEFAULT_TD_CLASSES,
- thClass: DEFAULT_TH_CLASSES,
},
{
key: 'value',
label: __('Value'),
tdAttr: { 'data-testid': 'trigger-build-value' },
tdClass: DEFAULT_TD_CLASSES,
- thClass: DEFAULT_TH_CLASSES,
},
],
components: {