Commit 9867eaf3 authored by Mike Greiling's avatar Mike Greiling

Prettify issue_show and jobs modules

parent 550f5574
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import animateMixin from '../mixins/animate'; import animateMixin from '../mixins/animate';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor'; import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
export default { export default {
mixins: [ mixins: [animateMixin, recaptchaModalImplementor],
animateMixin,
recaptchaModalImplementor,
],
props: { props: {
canUpdate: { canUpdate: {
type: Boolean, type: Boolean,
required: true, required: true,
},
descriptionHtml: {
type: String,
required: true,
},
descriptionText: {
type: String,
required: true,
},
taskStatus: {
type: String,
required: false,
default: '',
},
issuableType: {
type: String,
required: false,
default: 'issue',
},
updateUrl: {
type: String,
required: false,
default: null,
},
}, },
data() { descriptionHtml: {
return { type: String,
preAnimation: false, required: true,
pulseAnimation: false,
};
}, },
watch: { descriptionText: {
descriptionHtml() { type: String,
this.animateChange(); required: true,
},
taskStatus: {
type: String,
required: false,
default: '',
},
issuableType: {
type: String,
required: false,
default: 'issue',
},
updateUrl: {
type: String,
required: false,
default: null,
},
},
data() {
return {
preAnimation: false,
pulseAnimation: false,
};
},
watch: {
descriptionHtml() {
this.animateChange();
this.$nextTick(() => { this.$nextTick(() => {
this.renderGFM(); this.renderGFM();
}); });
},
taskStatus() {
this.updateTaskStatusText();
},
}, },
mounted() { taskStatus() {
this.renderGFM();
this.updateTaskStatusText(); this.updateTaskStatusText();
}, },
methods: { },
renderGFM() { mounted() {
$(this.$refs['gfm-content']).renderGFM(); this.renderGFM();
this.updateTaskStatusText();
},
methods: {
renderGFM() {
$(this.$refs['gfm-content']).renderGFM();
if (this.canUpdate) { if (this.canUpdate) {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new TaskList({ new TaskList({
dataType: this.issuableType, dataType: this.issuableType,
fieldName: 'description', fieldName: 'description',
selector: '.detail-page-description', selector: '.detail-page-description',
onSuccess: this.taskListUpdateSuccess.bind(this), onSuccess: this.taskListUpdateSuccess.bind(this),
}); });
} }
}, },
taskListUpdateSuccess(data) { taskListUpdateSuccess(data) {
try { try {
this.checkForSpam(data); this.checkForSpam(data);
this.closeRecaptcha(); this.closeRecaptcha();
} catch (error) { } catch (error) {
if (error && error.name === 'SpamError') this.openRecaptcha(); if (error && error.name === 'SpamError') this.openRecaptcha();
} }
}, },
updateTaskStatusText() { updateTaskStatusText() {
const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/); const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
const $issuableHeader = $('.issuable-meta'); const $issuableHeader = $('.issuable-meta');
const $tasks = $('#task_status', $issuableHeader); const $tasks = $('#task_status', $issuableHeader);
const $tasksShort = $('#task_status_short', $issuableHeader); const $tasksShort = $('#task_status_short', $issuableHeader);
if (taskRegexMatches) { if (taskRegexMatches) {
$tasks.text(this.taskStatus); $tasks.text(this.taskStatus);
$tasksShort.text( $tasksShort.text(
`${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? `${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? 's' : ''}`,
's' : );
''}`, } else {
); $tasks.text('');
} else { $tasksShort.text('');
$tasks.text(''); }
$tasksShort.text('');
}
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import updateMixin from '../mixins/update'; import updateMixin from '../mixins/update';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
const issuableTypes = { const issuableTypes = {
issue: __('Issue'), issue: __('Issue'),
epic: __('Epic'), epic: __('Epic'),
}; };
export default { export default {
mixins: [updateMixin], mixins: [updateMixin],
props: { props: {
canDestroy: { canDestroy: {
type: Boolean, type: Boolean,
required: true, required: true,
},
formState: {
type: Object,
required: true,
},
showDeleteButton: {
type: Boolean,
required: false,
default: true,
},
issuableType: {
type: String,
required: true,
},
}, },
data() { formState: {
return { type: Object,
deleteLoading: false, required: true,
};
}, },
computed: { showDeleteButton: {
isSubmitEnabled() { type: Boolean,
return this.formState.title.trim() !== ''; required: false,
}, default: true,
shouldShowDeleteButton() {
return this.canDestroy && this.showDeleteButton;
},
}, },
methods: { issuableType: {
closeForm() { type: String,
eventHub.$emit('close.form'); required: true,
}, },
deleteIssuable() { },
const confirmMessage = sprintf(__('%{issuableType} will be removed! Are you sure?'), { data() {
issuableType: issuableTypes[this.issuableType], return {
}); deleteLoading: false,
// eslint-disable-next-line no-alert };
if (window.confirm(confirmMessage)) { },
this.deleteLoading = true; computed: {
isSubmitEnabled() {
return this.formState.title.trim() !== '';
},
shouldShowDeleteButton() {
return this.canDestroy && this.showDeleteButton;
},
},
methods: {
closeForm() {
eventHub.$emit('close.form');
},
deleteIssuable() {
const confirmMessage = sprintf(__('%{issuableType} will be removed! Are you sure?'), {
issuableType: issuableTypes[this.issuableType],
});
// eslint-disable-next-line no-alert
if (window.confirm(confirmMessage)) {
this.deleteLoading = true;
eventHub.$emit('delete.issuable'); eventHub.$emit('delete.issuable');
} }
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue'; import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
export default { export default {
components: { components: {
timeAgoTooltip, timeAgoTooltip,
},
props: {
updatedAt: {
type: String,
required: false,
default: '',
}, },
props: { updatedByName: {
updatedAt: { type: String,
type: String, required: false,
required: false, default: '',
default: '',
},
updatedByName: {
type: String,
required: false,
default: '',
},
updatedByPath: {
type: String,
required: false,
default: '',
},
}, },
computed: { updatedByPath: {
hasUpdatedBy() { type: String,
return this.updatedByName && this.updatedByPath; required: false,
}, default: '',
}, },
}; },
computed: {
hasUpdatedBy() {
return this.updatedByName && this.updatedByPath;
},
},
};
</script> </script>
<template> <template>
...@@ -53,4 +53,3 @@ ...@@ -53,4 +53,3 @@
</span> </span>
</small> </small>
</template> </template>
<script> <script>
import updateMixin from '../../mixins/update'; import updateMixin from '../../mixins/update';
import markdownField from '../../../vue_shared/components/markdown/field.vue'; import markdownField from '../../../vue_shared/components/markdown/field.vue';
export default { export default {
components: { components: {
markdownField, markdownField,
},
mixins: [updateMixin],
props: {
formState: {
type: Object,
required: true,
}, },
mixins: [updateMixin], markdownPreviewPath: {
props: { type: String,
formState: { required: true,
type: Object,
required: true,
},
markdownPreviewPath: {
type: String,
required: true,
},
markdownDocsPath: {
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
enableAutocomplete: {
type: Boolean,
required: false,
default: true,
},
}, },
mounted() { markdownDocsPath: {
this.$refs.textarea.focus(); type: String,
required: true,
}, },
}; markdownVersion: {
type: Number,
required: false,
default: 0,
},
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
enableAutocomplete: {
type: Boolean,
required: false,
default: true,
},
},
mounted() {
this.$refs.textarea.focus();
},
};
</script> </script>
<template> <template>
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors'; import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors';
export default { export default {
props: { props: {
formState: { formState: {
type: Object, type: Object,
required: true, required: true,
},
issuableTemplates: {
type: Array,
required: false,
default: () => [],
},
projectPath: {
type: String,
required: true,
},
projectNamespace: {
type: String,
required: true,
},
}, },
computed: { issuableTemplates: {
issuableTemplatesJson() { type: Array,
return JSON.stringify(this.issuableTemplates); required: false,
}, default: () => [],
}, },
mounted() { projectPath: {
// Create the editor for the template type: String,
const editor = document.querySelector('.detail-page-description .note-textarea') || {}; required: true,
editor.setValue = (val) => { },
this.formState.description = val; projectNamespace: {
}; type: String,
editor.getValue = () => this.formState.description; required: true,
this.issuableTemplate = new IssuableTemplateSelectors({
$dropdowns: $(this.$refs.toggle),
editor,
});
}, },
}; },
computed: {
issuableTemplatesJson() {
return JSON.stringify(this.issuableTemplates);
},
},
mounted() {
// Create the editor for the template
const editor = document.querySelector('.detail-page-description .note-textarea') || {};
editor.setValue = val => {
this.formState.description = val;
};
editor.getValue = () => this.formState.description;
this.issuableTemplate = new IssuableTemplateSelectors({
$dropdowns: $(this.$refs.toggle),
editor,
});
},
};
</script> </script>
<template> <template>
......
<script> <script>
import updateMixin from '../../mixins/update'; import updateMixin from '../../mixins/update';
export default { export default {
mixins: [updateMixin], mixins: [updateMixin],
props: { props: {
formState: { formState: {
type: Object, type: Object,
required: true, required: true,
},
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
import lockedWarning from './locked_warning.vue'; import lockedWarning from './locked_warning.vue';
import titleField from './fields/title.vue'; import titleField from './fields/title.vue';
import descriptionField from './fields/description.vue'; import descriptionField from './fields/description.vue';
import editActions from './edit_actions.vue'; import editActions from './edit_actions.vue';
import descriptionTemplate from './fields/description_template.vue'; import descriptionTemplate from './fields/description_template.vue';
export default { export default {
components: { components: {
lockedWarning, lockedWarning,
titleField, titleField,
descriptionField, descriptionField,
descriptionTemplate, descriptionTemplate,
editActions, editActions,
},
props: {
canDestroy: {
type: Boolean,
required: true,
}, },
props: { formState: {
canDestroy: { type: Object,
type: Boolean, required: true,
required: true,
},
formState: {
type: Object,
required: true,
},
issuableTemplates: {
type: Array,
required: false,
default: () => [],
},
issuableType: {
type: String,
required: true,
},
markdownPreviewPath: {
type: String,
required: true,
},
markdownDocsPath: {
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
projectPath: {
type: String,
required: true,
},
projectNamespace: {
type: String,
required: true,
},
showDeleteButton: {
type: Boolean,
required: false,
default: true,
},
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
enableAutocomplete: {
type: Boolean,
required: false,
default: true,
},
}, },
computed: { issuableTemplates: {
hasIssuableTemplates() { type: Array,
return this.issuableTemplates.length; required: false,
}, default: () => [],
}, },
}; issuableType: {
type: String,
required: true,
},
markdownPreviewPath: {
type: String,
required: true,
},
markdownDocsPath: {
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
projectPath: {
type: String,
required: true,
},
projectNamespace: {
type: String,
required: true,
},
showDeleteButton: {
type: Boolean,
required: false,
default: true,
},
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
enableAutocomplete: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
hasIssuableTemplates() {
return this.issuableTemplates.length;
},
},
};
</script> </script>
<template> <template>
......
<script> <script>
export default { export default {
computed: { computed: {
currentPath() { currentPath() {
return window.location.pathname; return window.location.pathname;
},
}, },
}; },
};
</script> </script>
<template> <template>
......
...@@ -25,8 +25,10 @@ export default class Store { ...@@ -25,8 +25,10 @@ export default class Store {
} }
stateShouldUpdate(data) { stateShouldUpdate(data) {
return this.state.titleText !== data.title_text || return (
this.state.descriptionText !== data.description_text; this.state.titleText !== data.title_text ||
this.state.descriptionText !== data.description_text
);
} }
setFormState(state) { setFormState(state) {
......
<script> <script>
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago'; import timeagoMixin from '~/vue_shared/mixins/timeago';
export default { export default {
components: { components: {
TimeagoTooltip, TimeagoTooltip,
},
mixins: [timeagoMixin],
props: {
artifact: {
type: Object,
required: true,
}, },
mixins: [ },
timeagoMixin, computed: {
], isExpired() {
props: { return this.artifact.expired;
artifact: {
type: Object,
required: true,
},
}, },
computed: { // Only when the key is `false` we can render this block
isExpired() { willExpire() {
return this.artifact.expired; return this.artifact.expired === false;
},
// Only when the key is `false` we can render this block
willExpire() {
return this.artifact.expired === false;
},
}, },
}; },
};
</script> </script>
<template> <template>
<div class="block"> <div class="block">
......
<script> <script>
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
export default { export default {
components: { components: {
ClipboardButton, ClipboardButton,
},
props: {
commit: {
type: Object,
required: true,
}, },
props: { mergeRequest: {
commit: { type: Object,
type: Object, required: false,
required: true, default: null,
},
mergeRequest: {
type: Object,
required: false,
default: null,
},
isLastBlock: {
type: Boolean,
required: true,
},
}, },
}; isLastBlock: {
type: Boolean,
required: true,
},
},
};
</script> </script>
<template> <template>
<div <div
......
<script> <script>
export default { export default {
props: { props: {
illustrationPath: { illustrationPath: {
type: String, type: String,
required: true, required: true,
}, },
illustrationSizeClass: { illustrationSizeClass: {
type: String, type: String,
required: true, required: true,
}, },
title: { title: {
type: String, type: String,
required: true, required: true,
}, },
content: { content: {
type: String, type: String,
required: false, required: false,
default: null, default: null,
}, },
action: { action: {
type: Object, type: Object,
required: false, required: false,
default: null, default: null,
validator(value) { validator(value) {
return ( return (
value === null || value === null ||
(Object.prototype.hasOwnProperty.call(value, 'path') && (Object.prototype.hasOwnProperty.call(value, 'path') &&
Object.prototype.hasOwnProperty.call(value, 'method') && Object.prototype.hasOwnProperty.call(value, 'method') &&
Object.prototype.hasOwnProperty.call(value, 'button_title')) Object.prototype.hasOwnProperty.call(value, 'button_title'))
); );
},
}, },
}, },
}; },
};
</script> </script>
<template> <template>
<div class="row empty-state"> <div class="row empty-state">
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue';
import { sprintf, __ } from '../../locale'; import { sprintf, __ } from '../../locale';
export default { export default {
components: { components: {
CiIcon, CiIcon,
},
props: {
deploymentStatus: {
type: Object,
required: true,
}, },
props: { iconStatus: {
deploymentStatus: { type: Object,
type: Object, required: true,
required: true,
},
iconStatus: {
type: Object,
required: true,
},
}, },
computed: { },
environment() { computed: {
let environmentText; environment() {
switch (this.deploymentStatus.status) { let environmentText;
case 'last': switch (this.deploymentStatus.status) {
case 'last':
environmentText = sprintf(
__('This job is the most recent deployment to %{link}.'),
{ link: this.environmentLink },
false,
);
break;
case 'out_of_date':
if (this.hasLastDeployment) {
environmentText = sprintf( environmentText = sprintf(
__('This job is the most recent deployment to %{link}.'), __(
{ link: this.environmentLink }, 'This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}.',
),
{
environmentLink: this.environmentLink,
deploymentLink: this.deploymentLink(`#${this.lastDeployment.iid}`),
},
false, false,
); );
break; } else {
case 'out_of_date':
if (this.hasLastDeployment) {
environmentText = sprintf(
__(
'This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}.',
),
{
environmentLink: this.environmentLink,
deploymentLink: this.deploymentLink(`#${this.lastDeployment.iid}`),
},
false,
);
} else {
environmentText = sprintf(
__('This job is an out-of-date deployment to %{environmentLink}.'),
{ environmentLink: this.environmentLink },
false,
);
}
break;
case 'failed':
environmentText = sprintf( environmentText = sprintf(
__('The deployment of this job to %{environmentLink} did not succeed.'), __('This job is an out-of-date deployment to %{environmentLink}.'),
{ environmentLink: this.environmentLink }, { environmentLink: this.environmentLink },
false, false,
); );
break; }
case 'creating':
if (this.hasLastDeployment) { break;
environmentText = sprintf( case 'failed':
__( environmentText = sprintf(
'This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}.', __('The deployment of this job to %{environmentLink} did not succeed.'),
), { environmentLink: this.environmentLink },
{
environmentLink: this.environmentLink,
deploymentLink: this.deploymentLink(__('latest deployment')),
},
false,
);
} else {
environmentText = sprintf(
__('This job is creating a deployment to %{environmentLink}.'),
{ environmentLink: this.environmentLink },
false,
);
}
break;
default:
break;
}
return environmentText;
},
environmentLink() {
if (this.hasEnvironment) {
return sprintf(
'%{startLink}%{name}%{endLink}',
{
startLink: `<a href="${
this.deploymentStatus.environment.environment_path
}" class="js-environment-link">`,
name: _.escape(this.deploymentStatus.environment.name),
endLink: '</a>',
},
false, false,
); );
} break;
return ''; case 'creating':
}, if (this.hasLastDeployment) {
hasLastDeployment() { environmentText = sprintf(
return this.hasEnvironment && this.deploymentStatus.environment.last_deployment; __(
}, 'This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}.',
lastDeployment() { ),
return this.hasLastDeployment ? this.deploymentStatus.environment.last_deployment : {}; {
}, environmentLink: this.environmentLink,
hasEnvironment() { deploymentLink: this.deploymentLink(__('latest deployment')),
return !_.isEmpty(this.deploymentStatus.environment); },
}, false,
lastDeploymentPath() { );
return !_.isEmpty(this.lastDeployment.deployable) ? this.lastDeployment.deployable.build_path : ''; } else {
}, environmentText = sprintf(
__('This job is creating a deployment to %{environmentLink}.'),
{ environmentLink: this.environmentLink },
false,
);
}
break;
default:
break;
}
return environmentText;
}, },
methods: { environmentLink() {
deploymentLink(name) { if (this.hasEnvironment) {
return sprintf( return sprintf(
'%{startLink}%{name}%{endLink}', '%{startLink}%{name}%{endLink}',
{ {
startLink: `<a href="${this.lastDeploymentPath}" class="js-job-deployment-link">`, startLink: `<a href="${
name, this.deploymentStatus.environment.environment_path
}" class="js-environment-link">`,
name: _.escape(this.deploymentStatus.environment.name),
endLink: '</a>', endLink: '</a>',
}, },
false, false,
); );
}, }
return '';
},
hasLastDeployment() {
return this.hasEnvironment && this.deploymentStatus.environment.last_deployment;
},
lastDeployment() {
return this.hasLastDeployment ? this.deploymentStatus.environment.last_deployment : {};
},
hasEnvironment() {
return !_.isEmpty(this.deploymentStatus.environment);
},
lastDeploymentPath() {
return !_.isEmpty(this.lastDeployment.deployable)
? this.lastDeployment.deployable.build_path
: '';
},
},
methods: {
deploymentLink(name) {
return sprintf(
'%{startLink}%{name}%{endLink}',
{
startLink: `<a href="${this.lastDeploymentPath}" class="js-job-deployment-link">`,
name,
endLink: '</a>',
},
false,
);
}, },
}; },
};
</script> </script>
<template> <template>
<div class="prepend-top-default js-environment-container"> <div class="prepend-top-default js-environment-container">
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
export default { export default {
components: { components: {
TimeagoTooltip, TimeagoTooltip,
},
props: {
user: {
type: Object,
required: false,
default: () => ({}),
}, },
props: { erasedAt: {
user: { type: String,
type: Object, required: true,
required: false,
default: () => ({}),
},
erasedAt: {
type: String,
required: true,
},
}, },
computed: { },
isErasedByUser() { computed: {
return !_.isEmpty(this.user); isErasedByUser() {
}, return !_.isEmpty(this.user);
}, },
}; },
};
</script> </script>
<template> <template>
<div class="prepend-top-default js-build-erased"> <div class="prepend-top-default js-build-erased">
......
<script> <script>
export default { export default {
name: 'JobLog', name: 'JobLog',
props: { props: {
trace: { trace: {
type: String, type: String,
required: true, required: true,
},
isComplete: {
type: Boolean,
required: true,
},
}, },
}; isComplete: {
type: Boolean,
required: true,
},
},
};
</script> </script>
<template> <template>
<pre class="build-trace"> <pre class="build-trace">
......
<script> <script>
import { polyfillSticky } from '~/lib/utils/sticky'; import { polyfillSticky } from '~/lib/utils/sticky';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import { numberToHumanSize } from '~/lib/utils/number_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
import { sprintf } from '~/locale'; import { sprintf } from '~/locale';
export default { export default {
components: { components: {
Icon, Icon,
},
directives: {
tooltip,
},
props: {
erasePath: {
type: String,
required: false,
default: null,
}, },
directives: { size: {
tooltip, type: Number,
required: true,
}, },
props: { rawPath: {
erasePath: { type: String,
type: String, required: false,
required: false, default: null,
default: null,
},
size: {
type: Number,
required: true,
},
rawPath: {
type: String,
required: false,
default: null,
},
isScrollTopDisabled: {
type: Boolean,
required: true,
},
isScrollBottomDisabled: {
type: Boolean,
required: true,
},
isScrollingDown: {
type: Boolean,
required: true,
},
isTraceSizeVisible: {
type: Boolean,
required: true,
},
}, },
computed: { isScrollTopDisabled: {
jobLogSize() { type: Boolean,
return sprintf('Showing last %{size} of log -', { required: true,
size: numberToHumanSize(this.size),
});
},
}, },
mounted() { isScrollBottomDisabled: {
polyfillSticky(this.$el); type: Boolean,
required: true,
}, },
methods: { isScrollingDown: {
handleScrollToTop() { type: Boolean,
this.$emit('scrollJobLogTop'); required: true,
},
handleScrollToBottom() {
this.$emit('scrollJobLogBottom');
},
}, },
isTraceSizeVisible: {
}; type: Boolean,
required: true,
},
},
computed: {
jobLogSize() {
return sprintf('Showing last %{size} of log -', {
size: numberToHumanSize(this.size),
});
},
},
mounted() {
polyfillSticky(this.$el);
},
methods: {
handleScrollToTop() {
this.$emit('scrollJobLogTop');
},
handleScrollToBottom() {
this.$emit('scrollJobLogBottom');
},
},
};
</script> </script>
<template> <template>
<div class="top-bar"> <div class="top-bar">
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
export default { export default {
components: { components: {
CiIcon, CiIcon,
Icon, Icon,
},
directives: {
tooltip,
},
props: {
jobs: {
type: Array,
required: true,
}, },
directives: { jobId: {
tooltip, type: Number,
required: true,
}, },
props: { },
jobs: { methods: {
type: Array, isJobActive(currentJobId) {
required: true, return this.jobId === currentJobId;
},
jobId: {
type: Number,
required: true,
},
}, },
methods: { tooltipText(job) {
isJobActive(currentJobId) { return `${_.escape(job.name)} - ${job.status.tooltip}`;
return this.jobId === currentJobId;
},
tooltipText(job) {
return `${_.escape(job.name)} - ${job.status.tooltip}`;
},
}, },
}; },
};
</script> </script>
<template> <template>
<div class="js-jobs-container builds-container"> <div class="js-jobs-container builds-container">
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import timeagoMixin from '~/vue_shared/mixins/timeago'; import timeagoMixin from '~/vue_shared/mixins/timeago';
import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import DetailRow from './sidebar_detail_row.vue'; import DetailRow from './sidebar_detail_row.vue';
import ArtifactsBlock from './artifacts_block.vue'; import ArtifactsBlock from './artifacts_block.vue';
import TriggerBlock from './trigger_block.vue'; import TriggerBlock from './trigger_block.vue';
import CommitBlock from './commit_block.vue'; import CommitBlock from './commit_block.vue';
import StagesDropdown from './stages_dropdown.vue'; import StagesDropdown from './stages_dropdown.vue';
import JobsContainer from './jobs_container.vue'; import JobsContainer from './jobs_container.vue';
export default { export default {
name: 'JobSidebar', name: 'JobSidebar',
components: { components: {
ArtifactsBlock, ArtifactsBlock,
CommitBlock, CommitBlock,
DetailRow, DetailRow,
Icon, Icon,
TriggerBlock, TriggerBlock,
StagesDropdown, StagesDropdown,
JobsContainer, JobsContainer,
},
mixins: [timeagoMixin],
props: {
runnerHelpUrl: {
type: String,
required: false,
default: '',
}, },
mixins: [timeagoMixin], terminalPath: {
props: { type: String,
runnerHelpUrl: { required: false,
type: String, default: null,
required: false,
default: '',
},
terminalPath: {
type: String,
required: false,
default: null,
},
}, },
computed: { },
...mapState(['job', 'isLoading', 'stages', 'jobs']), computed: {
coverage() { ...mapState(['job', 'isLoading', 'stages', 'jobs']),
return `${this.job.coverage}%`; coverage() {
}, return `${this.job.coverage}%`;
duration() { },
return timeIntervalInWords(this.job.duration); duration() {
}, return timeIntervalInWords(this.job.duration);
queued() { },
return timeIntervalInWords(this.job.queued); queued() {
}, return timeIntervalInWords(this.job.queued);
runnerId() { },
return `${this.job.runner.description} (#${this.job.runner.id})`; runnerId() {
}, return `${this.job.runner.description} (#${this.job.runner.id})`;
retryButtonClass() { },
let className = retryButtonClass() {
'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block'; let className =
className += 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block';
this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary'; className +=
return className; this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
}, return className;
hasTimeout() { },
return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null; hasTimeout() {
}, return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
timeout() { },
if (this.job.metadata == null) { timeout() {
return ''; if (this.job.metadata == null) {
} return '';
}
let t = this.job.metadata.timeout_human_readable; let t = this.job.metadata.timeout_human_readable;
if (this.job.metadata.timeout_source !== '') { if (this.job.metadata.timeout_source !== '') {
t += ` (from ${this.job.metadata.timeout_source})`; t += ` (from ${this.job.metadata.timeout_source})`;
} }
return t; return t;
}, },
renderBlock() { renderBlock() {
return ( return (
this.job.merge_request || this.job.merge_request ||
this.job.duration || this.job.duration ||
this.job.finished_data || this.job.finished_data ||
this.job.erased_at || this.job.erased_at ||
this.job.queued || this.job.queued ||
this.job.runner || this.job.runner ||
this.job.coverage || this.job.coverage ||
this.job.tags.length || this.job.tags.length ||
this.job.cancel_path this.job.cancel_path
); );
}, },
hasArtifact() { hasArtifact() {
return !_.isEmpty(this.job.artifact); return !_.isEmpty(this.job.artifact);
}, },
hasTriggers() { hasTriggers() {
return !_.isEmpty(this.job.trigger); return !_.isEmpty(this.job.trigger);
}, },
hasStages() { hasStages() {
return ( return (
(this.job && (this.job &&
this.job.pipeline && this.job.pipeline &&
this.job.pipeline.stages && this.job.pipeline.stages &&
this.job.pipeline.stages.length > 0) || this.job.pipeline.stages.length > 0) ||
false false
); );
},
commit() {
return this.job.pipeline.commit || {};
},
}, },
methods: { commit() {
...mapActions(['fetchJobsForStage']), return this.job.pipeline.commit || {};
}, },
}; },
methods: {
...mapActions(['fetchJobsForStage']),
},
};
</script> </script>
<template> <template>
<aside <aside
......
<script> <script>
export default { export default {
name: 'SidebarDetailRow', name: 'SidebarDetailRow',
props: { props: {
title: { title: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
},
value: {
type: String,
required: true,
},
helpUrl: {
type: String,
required: false,
default: '',
},
}, },
computed: { value: {
hasTitle() { type: String,
return this.title.length > 0; required: true,
},
hasHelpURL() {
return this.helpUrl.length > 0;
},
}, },
}; helpUrl: {
type: String,
required: false,
default: '',
},
},
computed: {
hasTitle() {
return this.title.length > 0;
},
hasHelpURL() {
return this.helpUrl.length > 0;
},
},
};
</script> </script>
<template> <template>
<p class="build-detail-row"> <p class="build-detail-row">
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
export default { export default {
components: { components: {
CiIcon, CiIcon,
Icon, Icon,
},
props: {
pipeline: {
type: Object,
required: true,
}, },
props: { stages: {
pipeline: { type: Array,
type: Object, required: true,
required: true,
},
stages: {
type: Array,
required: true,
},
}, },
data() { },
return { data() {
selectedStage: this.stages.length > 0 ? this.stages[0].name : __('More'), return {
}; selectedStage: this.stages.length > 0 ? this.stages[0].name : __('More'),
};
},
computed: {
hasRef() {
return !_.isEmpty(this.pipeline.ref);
}, },
computed: { },
hasRef() { watch: {
return !_.isEmpty(this.pipeline.ref); // When the component is initially mounted it may start with an empty stages array.
}, // Once the prop is updated, we set the first stage as the selected one
stages(newVal) {
if (newVal.length) {
this.selectedStage = newVal[0].name;
}
}, },
watch: { },
// When the component is initially mounted it may start with an empty stages array. methods: {
// Once the prop is updated, we set the first stage as the selected one onStageClick(stage) {
stages(newVal) { this.$emit('requestSidebarStageDropdown', stage);
if (newVal.length) { this.selectedStage = stage.name;
this.selectedStage = newVal[0].name;
}
},
}, },
methods: { },
onStageClick(stage) { };
this.$emit('requestSidebarStageDropdown', stage);
this.selectedStage = stage.name;
},
},
};
</script> </script>
<template> <template>
<div class="block-last dropdown"> <div class="block-last dropdown">
......
<script> <script>
export default { export default {
props: { props: {
trigger: { trigger: {
type: Object, type: Object,
required: true, required: true,
},
}, },
data() { },
return { data() {
areVariablesVisible: false, return {
}; areVariablesVisible: false,
};
},
computed: {
hasVariables() {
return this.trigger.variables && this.trigger.variables.length > 0;
}, },
computed: { },
hasVariables() { methods: {
return this.trigger.variables && this.trigger.variables.length > 0; revealVariables() {
}, this.areVariablesVisible = true;
}, },
methods: { },
revealVariables() { };
this.areVariablesVisible = true;
},
},
};
</script> </script>
<template> <template>
......
...@@ -7,9 +7,10 @@ import mutations from './mutations'; ...@@ -7,9 +7,10 @@ import mutations from './mutations';
Vue.use(Vuex); Vue.use(Vuex);
export default () => new Vuex.Store({ export default () =>
actions, new Vuex.Store({
mutations, actions,
getters, mutations,
state: state(), getters,
}); state: state(),
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment