Commit 249b5015 authored by Phil Hughes's avatar Phil Hughes

Merge branch '34312-eslint-vue-plugin' into 'master'

Resolve "Add eslint-vue-plugin to our stack"

Closes #34312

See merge request gitlab-org/gitlab-ce!16210
parents 16e2e4c7 3dfc91cf
......@@ -13,7 +13,8 @@ engines:
exclude_paths:
- "lib/api/v3/*"
eslint:
enabled: true
# eslint-plugin-vue is locked to version 2 in codeclimate, we need version 4
enabled: false
rubocop:
enabled: true
channel: "gitlab-rubocop-0-52"
......
......@@ -4,7 +4,10 @@
"browser": true,
"es6": true
},
"extends": "airbnb-base",
"extends": [
"airbnb-base",
"plugin:vue/recommended"
],
"globals": {
"__webpack_public_path__": true,
"_": false,
......@@ -12,7 +15,9 @@
"gon": false,
"localStorage": false
},
"parser": "babel-eslint",
"parserOptions": {
"parser": "babel-eslint"
},
"plugins": [
"filenames",
"import",
......@@ -20,7 +25,7 @@
"promise"
],
"settings": {
"html/html-extensions": [".html", ".html.raw", ".vue"],
"html/html-extensions": [".html", ".html.raw"],
"import/resolver": {
"webpack": {
"config": "./config/webpack.config.js"
......@@ -32,6 +37,15 @@
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error",
"no-underscore-dangle": ["error", { "allow": ["__"]}]
"no-underscore-dangle": ["error", { "allow": ["__"]}],
"vue/html-self-closing": ["error", {
"html": {
"void": "always",
"normal": "never",
"component": "always"
},
"svg": "always",
"math": "always"
}]
}
}
......@@ -8,6 +8,9 @@ export default () => {
new Vue({
el,
components: {
notebookLab,
},
data() {
return {
error: false,
......@@ -16,8 +19,41 @@ export default () => {
json: {},
};
},
components: {
notebookLab,
mounted() {
if (gon.katex_css_url) {
const katexStyles = document.createElement('link');
katexStyles.setAttribute('rel', 'stylesheet');
katexStyles.setAttribute('href', gon.katex_css_url);
document.head.appendChild(katexStyles);
}
if (gon.katex_js_url) {
const katexScript = document.createElement('script');
katexScript.addEventListener('load', () => {
this.loadFile();
});
katexScript.setAttribute('src', gon.katex_js_url);
document.head.appendChild(katexScript);
} else {
this.loadFile();
}
},
methods: {
loadFile() {
axios.get(el.dataset.endpoint)
.then(res => res.data)
.then((data) => {
this.json = data;
this.loading = false;
})
.catch((e) => {
if (e.status !== 200) {
this.loadError = true;
}
this.error = true;
});
},
},
template: `
<div class="container-fluid md prepend-top-default append-bottom-default">
......@@ -46,41 +82,5 @@ export default () => {
</p>
</div>
`,
methods: {
loadFile() {
axios.get(el.dataset.endpoint)
.then(res => res.data)
.then((data) => {
this.json = data;
this.loading = false;
})
.catch((e) => {
if (e.status !== 200) {
this.loadError = true;
}
this.error = true;
});
},
},
mounted() {
if (gon.katex_css_url) {
const katexStyles = document.createElement('link');
katexStyles.setAttribute('rel', 'stylesheet');
katexStyles.setAttribute('href', gon.katex_css_url);
document.head.appendChild(katexStyles);
}
if (gon.katex_js_url) {
const katexScript = document.createElement('script');
katexScript.addEventListener('load', () => {
this.loadFile();
});
katexScript.setAttribute('src', gon.katex_js_url);
document.head.appendChild(katexScript);
} else {
this.loadFile();
}
},
});
};
......@@ -7,6 +7,9 @@ export default () => {
return new Vue({
el,
components: {
pdfLab,
},
data() {
return {
error: false,
......@@ -15,9 +18,6 @@ export default () => {
pdf: el.dataset.endpoint,
};
},
components: {
pdfLab,
},
methods: {
onLoad() {
this.loading = false;
......
......@@ -171,19 +171,14 @@ $(() => {
});
gl.IssueBoardsModalAddBtn = new Vue({
mixins: [gl.issueBoards.ModalMixins],
el: document.getElementById('js-add-issues-btn'),
mixins: [gl.issueBoards.ModalMixins],
data() {
return {
modal: ModalStore.store,
store: Store.state,
};
},
watch: {
disabled() {
this.updateTooltip();
},
},
computed: {
disabled() {
if (!this.store) {
......@@ -199,6 +194,14 @@ $(() => {
return '';
},
},
watch: {
disabled() {
this.updateTooltip();
},
},
mounted() {
this.updateTooltip();
},
methods: {
updateTooltip() {
const $tooltip = $(this.$refs.addIssuesButton);
......@@ -217,9 +220,6 @@ $(() => {
}
},
},
mounted() {
this.updateTooltip();
},
template: `
<div class="board-extra-actions">
<button
......
......@@ -10,12 +10,30 @@ export default {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
props: {
list: Object,
issue: Object,
issueLinkBase: String,
disabled: Boolean,
index: Number,
rootPath: String,
list: {
type: Object,
default: () => ({}),
},
issue: {
type: Object,
default: () => ({}),
},
issueLinkBase: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
index: {
type: Number,
default: 0,
},
rootPath: {
type: String,
default: '',
},
},
data() {
return {
......@@ -54,8 +72,13 @@ export default {
</script>
<template>
<li class="card"
:class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }"
<li
class="card"
:class="{
'user-can-drag': !disabled && issue.id,
'is-disabled': disabled || !issue.id,
'is-active': issueDetailVisible
}"
:index="index"
:data-issue-id="issue.id"
@mousedown="mouseDown"
......@@ -66,6 +89,7 @@ export default {
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath"
:update-filters="true" />
:update-filters="true"
/>
</li>
</template>
<script>
import { s__, sprintf } from '../../locale';
import eventHub from '../event_hub';
import loadingButton from '../../vue_shared/components/loading_button.vue';
import {
/* eslint-disable vue/require-default-prop */
import { s__, sprintf } from '../../locale';
import eventHub from '../event_hub';
import loadingButton from '../../vue_shared/components/loading_button.vue';
import {
APPLICATION_NOT_INSTALLABLE,
APPLICATION_SCHEDULED,
APPLICATION_INSTALLABLE,
......@@ -12,9 +13,12 @@ import {
REQUEST_LOADING,
REQUEST_SUCCESS,
REQUEST_FAILURE,
} from '../constants';
} from '../constants';
export default {
export default {
components: {
loadingButton,
},
props: {
id: {
type: String,
......@@ -49,9 +53,6 @@ export default {
required: false,
},
},
components: {
loadingButton,
},
computed: {
rowJsClass() {
return `js-cluster-application-row-${this.id}`;
......@@ -66,7 +67,8 @@ export default {
// Avoid the potential for the real-time data to say APPLICATION_INSTALLABLE but
// we already made a request to install and are just waiting for the real-time
// to sync up.
return (this.status !== APPLICATION_INSTALLABLE && this.status !== APPLICATION_ERROR) ||
return (this.status !== APPLICATION_INSTALLABLE
&& this.status !== APPLICATION_ERROR) ||
this.requestStatus === REQUEST_LOADING ||
this.requestStatus === REQUEST_SUCCESS;
},
......@@ -78,7 +80,8 @@ export default {
this.status === APPLICATION_ERROR
) {
label = s__('ClusterIntegration|Install');
} else if (this.status === APPLICATION_SCHEDULED || this.status === APPLICATION_INSTALLING) {
} else if (this.status === APPLICATION_SCHEDULED ||
this.status === APPLICATION_INSTALLING) {
label = s__('ClusterIntegration|Installing');
} else if (this.status === APPLICATION_INSTALLED) {
label = s__('ClusterIntegration|Installed');
......@@ -87,7 +90,8 @@ export default {
return label;
},
hasError() {
return this.status === APPLICATION_ERROR || this.requestStatus === REQUEST_FAILURE;
return this.status === APPLICATION_ERROR ||
this.requestStatus === REQUEST_FAILURE;
},
generalErrorDescription() {
return sprintf(
......@@ -102,7 +106,7 @@ export default {
eventHub.$emit('installApplication', this.id);
},
},
};
};
</script>
<template>
......
<script>
import _ from 'underscore';
import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue';
import _ from 'underscore';
import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue';
export default {
export default {
components: {
applicationRow,
},
props: {
applications: {
type: Object,
......@@ -13,15 +16,15 @@ export default {
helpPath: {
type: String,
required: false,
default: '',
},
},
components: {
applicationRow,
},
computed: {
generalApplicationDescription() {
return sprintf(
_.escape(s__('ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}')), {
_.escape(s__(`ClusterIntegration|Install applications on your cluster.
Read more about %{helpLink}`)),
{
helpLink: `<a href="${this.helpPath}">
${_.escape(s__('ClusterIntegration|installing applications'))}
</a>`,
......@@ -43,7 +46,10 @@ export default {
));
const extraCostParagraph = sprintf(
_.escape(s__('ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}')), {
_.escape(s__(`ClusterIntegration|%{boldNotice} This will add some
extra resources like a load balancer,
which incur additional costs. See %{pricingLink}`)),
{
boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|GKE pricing'))}
......@@ -69,8 +75,11 @@ export default {
},
prometheusDescription() {
return sprintf(
_.escape(s__('ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications.')), {
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html", target="_blank" rel="noopener noreferrer">
_.escape(s__(`ClusterIntegration|Prometheus is an open-source monitoring system
with %{gitlabIntegrationLink} to monitor deployed applications.`)),
{
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
</a>`,
},
......@@ -78,7 +87,7 @@ export default {
);
},
},
};
};
</script>
<template>
......@@ -126,7 +135,10 @@ export default {
:request-status="applications.prometheus.requestStatus"
:request-reason="applications.prometheus.requestReason"
/>
<!-- NOTE: Don't forget to update `clusters.scss` min-height for this block and uncomment `application_spec` tests -->
<!--
NOTE: Don't forget to update `clusters.scss`
min-height for this block and uncomment `application_spec` tests
-->
<!-- Add GitLab Runner row, all other plumbing is complete -->
</div>
</div>
......
......@@ -94,7 +94,7 @@ export default class ImageFile {
});
return [maxWidth, maxHeight];
}
// eslint-disable-next-line
views = {
'two-up': function() {
return $('.two-up.view .wrap', this.file).each((function(_this) {
......
......@@ -4,6 +4,10 @@
import pipelinesMixin from '../../pipelines/mixins/pipelines';
export default {
mixins: [
pipelinesMixin,
],
props: {
endpoint: {
type: String,
......@@ -31,9 +35,6 @@
default: 'child',
},
},
mixins: [
pipelinesMixin,
],
data() {
const store = new PipelineStore();
......@@ -110,7 +111,8 @@
<div
class="table-holder"
v-if="shouldRenderTable">
v-if="shouldRenderTable"
>
<pipelines-table-component
:pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown"
......
......@@ -26,28 +26,34 @@
class="js-ca-dismiss-button dismiss-button"
type="button"
:aria-label="__('Dismiss Cycle Analytics introduction box')"
@click="dismissOverviewDialog">
@click="dismissOverviewDialog"
>
<i
class="fa fa-times"
aria-hidden="true">
</i>
</button>
<div class="svg-container" v-html="iconCycleAnalyticsSplash">
<div
class="svg-container"
v-html="iconCycleAnalyticsSplash"
>
</div>
<div class="inner-content">
<h4>
{{__('Introducing Cycle Analytics')}}
{{ __('Introducing Cycle Analytics') }}
</h4>
<p>
{{ __('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.') }}
{{ __(`Cycle Analytics gives an overview
of how much time it takes to go from idea to production in your project.`) }}
</p>
<p>
<a
:href="documentationLink"
target="_blank"
rel="nofollow"
class="btn">
{{__('Read more')}}
class="btn"
>
{{ __('Read more') }}
</a>
</p>
</div>
......
......@@ -2,25 +2,34 @@
import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
props: {
count: {
type: Number,
required: true,
},
},
directives: {
tooltip,
},
};
</script>
<template>
<span v-if="count === 50" class="events-info pull-right">
<span
v-if="count === 50"
class="events-info pull-right"
>
<i
class="fa fa-warning"
v-tooltip
aria-hidden="true"
:title="n__('Limited to showing %d event at most', 'Limited to showing %d events at most', 50)"
data-placement="top"></i>
:title="n__(
'Limited to showing %d event at most',
'Limited to showing %d events at most',
50
)"
data-placement="top"
>
</i>
{{ n__('Showing %d event', 'Showing %d events', 50) }}
</span>
</template>
......@@ -4,15 +4,21 @@
import totalTime from './total_time_component.vue';
export default {
props: {
items: Array,
stage: Object,
},
components: {
userAvatarImage,
limitWarning,
totalTime,
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
},
};
</script>
<template>
......@@ -22,28 +28,44 @@
<limit-warning :count="items.length" />
</div>
<ul class="stage-event-list">
<li v-for="mergeRequest in items" class="stage-event-item">
<li
v-for="(mergeRequest, i) in items"
:key="i"
class="stage-event-item"
>
<div class="item-details">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
<user-avatar-image :img-src="mergeRequest.author.avatarUrl" />
<h5 class="item-title merge-merquest-title">
<a :href="mergeRequest.url">
{{ mergeRequest.title }}
</a>
</h5>
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
<a
:href="mergeRequest.url"
class="issue-link">
!{{ mergeRequest.iid }}
</a>
&middot;
<span>
{{ s__('OpenedNDaysAgo|Opened') }}
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
<a
:href="mergeRequest.url"
class="issue-date">
{{ mergeRequest.createdAt }}
</a>
</span>
<span>
{{ s__('ByAuthor|by') }}
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
<a
:href="mergeRequest.author.webUrl"
class="issue-author-link">
{{ mergeRequest.author.name }}
</a>
</span>
</div>
<div class="item-time">
<total-time :time="mergeRequest.totalTime"></total-time>
<total-time :time="mergeRequest.totalTime" />
</div>
</li>
</ul>
......
......@@ -4,15 +4,21 @@
import totalTime from './total_time_component.vue';
export default {
props: {
items: Array,
stage: Object,
},
components: {
userAvatarImage,
limitWarning,
totalTime,
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
},
};
</script>
<template>
......@@ -25,30 +31,43 @@
<li
v-for="(issue, i) in items"
:key="i"
class="stage-event-item">
class="stage-event-item"
>
<div class="item-details">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="issue.author.avatarUrl"/>
<h5 class="item-title issue-title">
<a class="issue-title" :href="issue.url">
<a
class="issue-title"
:href="issue.url"
>
{{ issue.title }}
</a>
</h5>
<a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
<a
:href="issue.url"
class="issue-link"
>#{{ issue.iid }}</a>
&middot;
<span>
{{ s__('OpenedNDaysAgo|Opened') }}
<a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
<a
:href="issue.url"
class="issue-date"
>{{ issue.createdAt }}</a>
</span>
<span>
{{ s__('ByAuthor|by') }}
<a :href="issue.author.webUrl" class="issue-author-link">
<a
:href="issue.author.webUrl"
class="issue-author-link"
>
{{ issue.author.name }}
</a>
</span>
</div>
<div class="item-time">
<total-time :time="issue.totalTime"/>
<total-time :time="issue.totalTime" />
</div>
</li>
</ul>
......
......@@ -5,15 +5,21 @@
import totalTime from './total_time_component.vue';
export default {
props: {
items: Array,
stage: Object,
},
components: {
userAvatarImage,
totalTime,
limitWarning,
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
},
computed: {
iconCommit() {
return iconCommit;
......@@ -31,10 +37,11 @@
<li
v-for="(commit, i) in items"
:key="i"
class="stage-event-item">
class="stage-event-item"
>
<div class="item-details item-conmmit-component">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="commit.author.avatarUrl"/>
<user-avatar-image :img-src="commit.author.avatarUrl" />
<h5 class="item-title commit-title">
<a :href="commit.commitUrl">
{{ commit.title }}
......@@ -42,10 +49,20 @@
</h5>
<span>
{{ s__('FirstPushedBy|First') }}
<span class="commit-icon" v-html="iconCommit"></span>
<a :href="commit.commitUrl" class="commit-hash-link commit-sha">{{ commit.shortSha }}</a>
<span
class="commit-icon"
v-html="iconCommit"
>
</span>
<a
:href="commit.commitUrl"
class="commit-hash-link commit-sha"
>{{ commit.shortSha }}</a>
{{ s__('FirstPushedBy|pushed by') }}
<a :href="commit.author.webUrl" class="commit-author-link">
<a
:href="commit.author.webUrl"
class="commit-author-link"
>
{{ commit.author.name }}
</a>
</span>
......
......@@ -5,16 +5,22 @@
import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
items: Array,
stage: Object,
},
components: {
userAvatarImage,
totalTime,
limitWarning,
icon,
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
},
};
</script>
<template>
......@@ -27,7 +33,8 @@
<li
v-for="(mergeRequest, i) in items"
:key="i"
class="stage-event-item">
class="stage-event-item"
>
<div class="item-details">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
......@@ -36,34 +43,52 @@
{{ mergeRequest.title }}
</a>
</h5>
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
<a
:href="mergeRequest.url"
class="issue-link"
>!{{ mergeRequest.iid }}</a>
&middot;
<span>
{{ s__('OpenedNDaysAgo|Opened') }}
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
<a
:href="mergeRequest.url"
class="issue-date"
>{{ mergeRequest.createdAt }}</a>
</span>
<span>
{{ s__('ByAuthor|by') }}
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
<a
:href="mergeRequest.author.webUrl"
class="issue-author-link"
>{{ mergeRequest.author.name }}</a>
</span>
<template v-if="mergeRequest.state === 'closed'">
<span class="merge-request-state">
<i class="fa fa-ban"></i>
<i
class="fa fa-ban"
aria-hidden="true"
>
</i>
{{ mergeRequest.state.toUpperCase() }}
</span>
</template>
<template v-else>
<span class="merge-request-branch" v-if="mergeRequest.branch">
<span
class="merge-request-branch"
v-if="mergeRequest.branch"
>
<icon
name="fork"
:size="16">
</icon>
<a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
:size="16"
/>
<a :href="mergeRequest.branch.url">
{{ mergeRequest.branch.name }}
</a>
</span>
</template>
</div>
<div class="item-time">
<total-time :time="mergeRequest.totalTime"/>
<total-time :time="mergeRequest.totalTime" />
</div>
</li>
</ul>
......
......@@ -6,16 +6,22 @@
import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
items: Array,
stage: Object,
},
components: {
userAvatarImage,
totalTime,
limitWarning,
icon,
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
},
computed: {
iconBranch() {
return iconBranch;
......@@ -33,30 +39,58 @@
<li
v-for="(build, i) in items"
class="stage-event-item item-build-component"
:key="i">
:key="i"
>
<div class="item-details">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="build.author.avatarUrl"/>
<h5 class="item-title">
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<a
:href="build.url"
class="pipeline-id"
>
#{{ build.id }}
</a>
<icon
name="fork"
:size="16">
</icon>
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch" v-html="iconBranch"></span>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
:size="16"
/>
<a
:href="build.branch.url"
class="ref-name"
>
{{ build.branch.name }}
</a>
<span
class="icon-branch"
v-html="iconBranch"
>
</span>
<a
:href="build.commitUrl"
class="commit-sha"
>
{{ build.shortSha }}
</a>
</h5>
<span>
<a :href="build.url" class="build-date">{{ build.date }}</a>
<a
:href="build.url"
class="build-date"
>
{{ build.date }}
</a>
{{ s__('ByAuthor|by') }}
<a :href="build.author.webUrl" class="issue-author-link">
<a
:href="build.author.webUrl"
class="issue-author-link"
>
{{ build.author.name }}
</a>
</span>
</div>
<div class="item-time">
<total-time :time="build.totalTime"/>
<total-time :time="build.totalTime" />
</div>
</li>
</ul>
......
......@@ -6,15 +6,21 @@
import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
items: Array,
stage: Object,
},
components: {
totalTime,
limitWarning,
icon,
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
},
computed: {
iconBuildStatus() {
return iconBuildStatus;
......@@ -35,29 +41,59 @@
<li
v-for="(build, i) in items"
:key="i"
class="stage-event-item item-build-component">
class="stage-event-item item-build-component"
>
<div class="item-details">
<h5 class="item-title">
<span class="icon-build-status" v-html="iconBuildStatus"></span>
<a :href="build.url" class="item-build-name">{{ build.name }}</a>
<span
class="icon-build-status"
v-html="iconBuildStatus"
>
</span>
<a
:href="build.url"
class="item-build-name"
>
{{ build.name }}
</a>
&middot;
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<a
:href="build.url"
class="pipeline-id"
>
#{{ build.id }}
</a>
<icon
name="fork"
:size="16">
</icon>
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch" v-html="iconBranch"></span>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
:size="16"
/>
<a
:href="build.branch.url"
class="ref-name"
>
{{ build.branch.name }}
</a>
<span
class="icon-branch"
v-html="iconBranch"
>
</span>
<a
:href="build.commitUrl"
class="commit-sha">
{{ build.shortSha }}
</a>
</h5>
<span>
<a :href="build.url" class="issue-date">
<a
:href="build.url"
class="issue-date">
{{ build.date }}
</a>
</span>
</div>
<div class="item-time">
<total-time :time="build.totalTime"/>
<total-time :time="build.totalTime" />
</div>
</li>
</ul>
......
......@@ -17,10 +17,30 @@
<template>
<span class="total-time">
<template v-if="hasData">
<template v-if="time.days">{{ time.days }} <span>{{ n__('day', 'days', time.days) }}</span></template>
<template v-if="time.hours">{{ time.hours }} <span>{{ n__('Time|hr', 'Time|hrs', time.hours) }}</span></template>
<template v-if="time.mins && !time.days">{{ time.mins }} <span>{{ n__('Time|min', 'Time|mins', time.mins) }}</span></template>
<template v-if="time.seconds && hasData === 1 || time.seconds === 0">{{ time.seconds }} <span>{{ s__('Time|s') }}</span></template>
<template v-if="time.days">
{{ time.days }}
<span>
{{ n__('day', 'days', time.days) }}
</span>
</template>
<template v-if="time.hours">
{{ time.hours }}
<span>
{{ n__('Time|hr', 'Time|hrs', time.hours) }}
</span>
</template>
<template v-if="time.mins && !time.days">
{{ time.mins }}
<span>
{{ n__('Time|min', 'Time|mins', time.mins) }}
</span>
</template>
<template v-if="time.seconds && hasData === 1 || time.seconds === 0">
{{ time.seconds }}
<span>
{{ s__('Time|s') }}
</span>
</template>
</template>
<template v-else>
--
......
......@@ -20,6 +20,16 @@ $(() => {
gl.cycleAnalyticsApp = new Vue({
el: '#cycle-analytics',
name: 'CycleAnalytics',
components: {
banner,
'stage-issue-component': stageComponent,
'stage-plan-component': stagePlanComponent,
'stage-code-component': stageCodeComponent,
'stage-test-component': stageTestComponent,
'stage-review-component': stageReviewComponent,
'stage-staging-component': stageStagingComponent,
'stage-production-component': stageComponent,
},
data() {
const cycleAnalyticsEl = document.querySelector('#cycle-analytics');
const cycleAnalyticsService = new CycleAnalyticsService({
......@@ -43,16 +53,6 @@ $(() => {
return this.store.currentActiveStage();
},
},
components: {
banner,
'stage-issue-component': stageComponent,
'stage-plan-component': stagePlanComponent,
'stage-code-component': stageCodeComponent,
'stage-test-component': stageTestComponent,
'stage-review-component': stageReviewComponent,
'stage-staging-component': stageStagingComponent,
'stage-production-component': stageComponent,
},
created() {
this.fetchCycleAnalyticsData();
},
......
......@@ -3,10 +3,8 @@
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
data() {
return {
isLoading: false,
};
components: {
loadingIcon,
},
props: {
deployKey: {
......@@ -23,11 +21,16 @@
default: 'btn-default',
},
},
components: {
loadingIcon,
data() {
return {
isLoading: false,
};
},
computed: {
text() {
return `${this.type.charAt(0).toUpperCase()}${this.type.slice(1)}`;
},
},
methods: {
doAction() {
this.isLoading = true;
......@@ -37,11 +40,6 @@
});
},
},
computed: {
text() {
return `${this.type.charAt(0).toUpperCase()}${this.type.slice(1)}`;
},
},
};
</script>
......
......@@ -7,11 +7,9 @@
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
data() {
return {
isLoading: false,
store: new DeployKeysStore(),
};
components: {
keysPanel,
loadingIcon,
},
props: {
endpoint: {
......@@ -19,6 +17,12 @@
required: true,
},
},
data() {
return {
isLoading: false,
store: new DeployKeysStore(),
};
},
computed: {
hasKeys() {
return Object.keys(this.keys).length;
......@@ -27,9 +31,20 @@
return this.store.keys;
},
},
components: {
keysPanel,
loadingIcon,
created() {
this.service = new DeployKeysService(this.endpoint);
eventHub.$on('enable.key', this.enableKey);
eventHub.$on('remove.key', this.disableKey);
eventHub.$on('disable.key', this.disableKey);
},
mounted() {
this.fetchKeys();
},
beforeDestroy() {
eventHub.$off('enable.key', this.enableKey);
eventHub.$off('remove.key', this.disableKey);
eventHub.$off('disable.key', this.disableKey);
},
methods: {
fetchKeys() {
......@@ -59,21 +74,6 @@
}
},
},
created() {
this.service = new DeployKeysService(this.endpoint);
eventHub.$on('enable.key', this.enableKey);
eventHub.$on('remove.key', this.disableKey);
eventHub.$on('disable.key', this.disableKey);
},
mounted() {
this.fetchKeys();
},
beforeDestroy() {
eventHub.$off('enable.key', this.enableKey);
eventHub.$off('remove.key', this.disableKey);
eventHub.$off('disable.key', this.disableKey);
},
};
</script>
......
......@@ -3,6 +3,9 @@
import { getTimeago } from '../../lib/utils/datetime_utility';
export default {
components: {
actionBtn,
},
props: {
deployKey: {
type: Object,
......@@ -17,9 +20,6 @@
required: true,
},
},
components: {
actionBtn,
},
computed: {
timeagoDate() {
return getTimeago().format(this.deployKey.created_at);
......@@ -61,9 +61,10 @@
</div>
<div class="deploy-key-content prepend-left-default deploy-key-projects">
<a
v-for="project in deployKey.projects"
v-for="(project, i) in deployKey.projects"
class="label deploy-project-label"
:href="project.full_path"
:key="i"
>
{{ project.full_name }}
</a>
......
......@@ -2,6 +2,9 @@
import key from './key.vue';
export default {
components: {
key,
},
props: {
title: {
type: String,
......@@ -25,9 +28,6 @@
required: true,
},
},
components: {
key,
},
};
</script>
......@@ -37,12 +37,14 @@
{{ title }}
({{ keys.length }})
</h5>
<ul class="well-list"
<ul
class="well-list"
v-if="keys.length"
>
<li
v-for="deployKey in keys"
:key="deployKey.id">
:key="deployKey.id"
>
<key
:deploy-key="deployKey"
:store="store"
......
......@@ -3,14 +3,14 @@ import deployKeysApp from './components/app.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({
el: document.getElementById('js-deploy-keys'),
components: {
deployKeysApp,
},
data() {
return {
endpoint: this.$options.el.dataset.endpoint,
};
},
components: {
deployKeysApp,
},
render(createElement) {
return createElement('deploy-keys-app', {
props: {
......
......@@ -4,6 +4,11 @@
import environmentTable from '../components/environments_table.vue';
export default {
components: {
environmentTable,
loadingIcon,
tablePagination,
},
props: {
isLoading: {
type: Boolean,
......@@ -26,12 +31,6 @@
required: true,
},
},
components: {
environmentTable,
loadingIcon,
tablePagination,
},
methods: {
onChangePage(page) {
this.$emit('onChangePage', page);
......@@ -64,7 +63,7 @@
<table-pagination
v-if="pagination && pagination.totalPages > 1"
:change="onChangePage"
:pageInfo="pagination"
:page-info="pagination"
/>
</div>
</div>
......
<script>
export default {
name: 'environmentsEmptyState',
name: 'EnvironmentsEmptyState',
props: {
newPath: {
type: String,
......@@ -21,21 +21,23 @@
<div class="blank-state-row">
<div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">
{{s__("Environments|You don't have any environments right now.")}}
{{ s__("Environments|You don't have any environments right now.") }}
</h2>
<p class="blank-state-text">
{{s__("Environments|Environments are places where code gets deployed, such as staging or production.")}}
{{ s__(`Environments|Environments are places where
code gets deployed, such as staging or production.`) }}
<br />
<a :href="helpPath">
{{s__("Environments|Read more about environments")}}
{{ s__("Environments|Read more about environments") }}
</a>
</p>
<a
v-if="canCreateEnvironment"
:href="newPath"
class="btn btn-create js-new-environment-button">
{{s__("Environments|New environment")}}
class="btn btn-create js-new-environment-button"
>
{{ s__("Environments|New environment") }}
</a>
</div>
</div>
......
<script>
import playIconSvg from 'icons/_icon_play.svg';
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
props: {
actions: {
type: Array,
required: false,
default: () => [],
},
},
import playIconSvg from 'icons/_icon_play.svg';
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
......@@ -20,6 +12,13 @@ export default {
components: {
loadingIcon,
},
props: {
actions: {
type: Array,
required: false,
default: () => [],
},
},
data() {
return {
......@@ -49,7 +48,7 @@ export default {
return !action.playable;
},
},
};
};
</script>
<template>
<div
......@@ -63,27 +62,33 @@ export default {
data-toggle="dropdown"
:title="title"
:aria-label="title"
:disabled="isLoading">
:disabled="isLoading"
>
<span>
<span v-html="playIconSvg"></span>
<i
class="fa fa-caret-down"
aria-hidden="true"/>
aria-hidden="true"
>
</i>
<loading-icon v-if="isLoading" />
</span>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions">
<li
v-for="(action, i) in actions"
:key="i">
<button
type="button"
class="js-manual-action-link no-btn btn"
@click="onClickAction(action.play_path)"
:class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)">
:disabled="isActionDisabled(action)"
>
<span v-html="playIconSvg"></span>
<span>
{{action.name}}
{{ action.name }}
</span>
</button>
</li>
......
<script>
import tooltip from '../../vue_shared/directives/tooltip';
import { s__ } from '../../locale';
import tooltip from '../../vue_shared/directives/tooltip';
import { s__ } from '../../locale';
/**
/**
* Renders the external url link in environments table.
*/
export default {
export default {
directives: {
tooltip,
},
props: {
externalUrl: {
type: String,
......@@ -13,16 +16,12 @@ export default {
},
},
directives: {
tooltip,
},
computed: {
title() {
return s__('Environments|Open');
},
},
};
};
</script>
<template>
<a
......@@ -33,9 +32,12 @@ export default {
rel="noopener noreferrer nofollow"
:title="title"
:aria-label="title"
:href="externalUrl">
:href="externalUrl"
>
<i
class="fa fa-external-link"
aria-hidden="true" />
aria-hidden="true"
>
</i>
</a>
</template>
<script>
import Timeago from 'timeago.js';
import _ from 'underscore';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import { humanize } from '../../lib/utils/text_utility';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
import RollbackComponent from './environment_rollback.vue';
import TerminalButtonComponent from './environment_terminal_button.vue';
import MonitoringButtonComponent from './environment_monitoring.vue';
import CommitComponent from '../../vue_shared/components/commit.vue';
import eventHub from '../event_hub';
/**
import Timeago from 'timeago.js';
import _ from 'underscore';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import { humanize } from '../../lib/utils/text_utility';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
import RollbackComponent from './environment_rollback.vue';
import TerminalButtonComponent from './environment_terminal_button.vue';
import MonitoringButtonComponent from './environment_monitoring.vue';
import CommitComponent from '../../vue_shared/components/commit.vue';
import eventHub from '../event_hub';
/**
* Envrionment Item Component
*
* Renders a table row for each environment.
*/
const timeagoInstance = new Timeago();
const timeagoInstance = new Timeago();
export default {
export default {
components: {
userAvatarLink,
'commit-component': CommitComponent,
......@@ -287,7 +287,8 @@ export default {
if (this.model &&
this.model.last_deployment &&
this.model.last_deployment.deployable) {
return `${this.model.last_deployment.deployable.name} #${this.model.last_deployment.deployable.id}`;
const deployable = this.model.last_deployment.deployable;
return `${deployable.name} #${deployable.id}`;
}
return '';
},
......@@ -417,7 +418,7 @@ export default {
eventHub.$emit('toggleFolder', this.model);
},
},
};
};
</script>
<template>
<div
......@@ -427,18 +428,22 @@ export default {
'folder-row': model.isFolder,
}"
role="row">
<div class="table-section section-10" role="gridcell">
<div
class="table-section section-10"
role="gridcell"
>
<div
v-if="!model.isFolder"
class="table-mobile-header"
role="rowheader">
{{s__("Environments|Environment")}}
role="rowheader"
>
{{ s__("Environments|Environment") }}
</div>
<a
v-if="!model.isFolder"
class="environment-name flex-truncate-parent table-mobile-content"
:href="environmentPath">
<span class="flex-truncate-child">{{model.name}}</span>
<span class="flex-truncate-child">{{ model.name }}</span>
</a>
<span
v-else
......@@ -450,32 +455,40 @@ export default {
<i
v-show="model.isOpen"
class="fa fa-caret-down"
aria-hidden="true" />
aria-hidden="true"
>
</i>
<i
v-show="!model.isOpen"
class="fa fa-caret-right"
aria-hidden="true"/>
aria-hidden="true"
>
</i>
</span>
<span class="folder-icon">
<i
class="fa fa-folder"
aria-hidden="true" />
aria-hidden="true">
</i>
</span>
<span>
{{model.folderName}}
{{ model.folderName }}
</span>
<span class="badge">
{{model.size}}
{{ model.size }}
</span>
</span>
</div>
<div class="table-section section-10 deployment-column hidden-xs hidden-sm" role="gridcell">
<div
class="table-section section-10 deployment-column hidden-xs hidden-sm"
role="gridcell"
>
<span v-if="shouldRenderDeploymentID">
{{deploymentInternalId}}
{{ deploymentInternalId }}
</span>
<span v-if="!model.isFolder && deploymentHasUser">
......@@ -490,22 +503,29 @@ export default {
</span>
</div>
<div class="table-section section-15 hidden-xs hidden-sm" role="gridcell">
<div
class="table-section section-15 hidden-xs hidden-sm"
role="gridcell"
>
<a
v-if="shouldRenderBuildName"
class="build-link flex-truncate-parent"
:href="buildPath">
<span class="flex-truncate-child">{{buildName}}</span>
:href="buildPath"
>
<span class="flex-truncate-child">{{ buildName }}</span>
</a>
</div>
<div
v-if="!model.isFolder"
class="table-section section-25" role="gridcell">
class="table-section section-25"
role="gridcell"
>
<div
role="rowheader"
class="table-mobile-header">
{{s__("Environments|Commit")}}
class="table-mobile-header"
>
{{ s__("Environments|Commit") }}
</div>
<div
v-if="hasLastDeploymentKey"
......@@ -521,22 +541,24 @@ export default {
<div
v-if="!hasLastDeploymentKey"
class="commit-title table-mobile-content">
{{s__("Environments|No deployments yet")}}
{{ s__("Environments|No deployments yet") }}
</div>
</div>
<div
v-if="!model.isFolder"
class="table-section section-10" role="gridcell">
class="table-section section-10"
role="gridcell"
>
<div
role="rowheader"
class="table-mobile-header">
{{s__("Environments|Updated")}}
{{ s__("Environments|Updated") }}
</div>
<span
v-if="canShowDate"
class="environment-created-date-timeago table-mobile-content">
{{createdDate}}
{{ createdDate }}
</span>
</div>
......
<script>
/**
/**
* Renders the Monitoring (Metrics) link in environments table.
*/
import tooltip from '../../vue_shared/directives/tooltip';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
export default {
props: {
monitoringUrl: {
type: String,
......@@ -12,16 +16,12 @@ export default {
},
},
directives: {
tooltip,
},
computed: {
title() {
return 'Monitoring';
},
},
};
};
</script>
<template>
<a
......@@ -31,10 +31,12 @@ export default {
rel="noopener noreferrer nofollow"
:href="monitoringUrl"
:title="title"
:aria-label="title">
:aria-label="title"
>
<i
class="fa fa-area-chart"
aria-hidden="true"
/>
>
</i>
</a>
</template>
<script>
/**
/**
* Renders Rollback or Re deploy button in environments table depending
* of the provided property `isLastDeployment`.
*
* Makes a post request when the button is clicked.
*/
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
components: {
loadingIcon,
},
export default {
props: {
retryUrl: {
type: String,
......@@ -21,10 +25,6 @@ export default {
},
},
components: {
loadingIcon,
},
data() {
return {
isLoading: false,
......@@ -38,20 +38,21 @@ export default {
eventHub.$emit('postAction', this.retryUrl);
},
},
};
};
</script>
<template>
<button
type="button"
class="btn hidden-xs hidden-sm"
@click="onClick"
:disabled="isLoading">
:disabled="isLoading"
>
<span v-if="isLastDeployment">
{{s__("Environments|Re-deploy")}}
{{ s__("Environments|Re-deploy") }}
</span>
<span v-else>
{{s__("Environments|Rollback")}}
{{ s__("Environments|Rollback") }}
</span>
<loading-icon v-if="isLoading" />
......
<script>
/**
/**
* Renders the stop "button" that allows stop an environment.
* Used in environments table.
*/
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
components: {
loadingIcon,
},
directives: {
tooltip,
},
export default {
props: {
stopUrl: {
type: String,
......@@ -15,20 +23,12 @@ export default {
},
},
directives: {
tooltip,
},
data() {
return {
isLoading: false,
};
},
components: {
loadingIcon,
},
computed: {
title() {
return 'Stop';
......@@ -47,7 +47,7 @@ export default {
}
},
},
};
};
</script>
<template>
<button
......@@ -58,10 +58,13 @@ export default {
@click="onClick"
:disabled="isLoading"
:title="title"
:aria-label="title">
:aria-label="title"
>
<i
class="fa fa-stop stop-env-icon"
aria-hidden="true" />
aria-hidden="true"
>
</i>
<loading-icon v-if="isLoading" />
</button>
</template>
<script>
/**
/**
* Renders a terminal button to open a web terminal.
* Used in environments table.
*/
import terminalIconSvg from 'icons/_icon_terminal.svg';
import tooltip from '../../vue_shared/directives/tooltip';
import terminalIconSvg from 'icons/_icon_terminal.svg';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
export default {
props: {
terminalPath: {
type: String,
......@@ -15,10 +19,6 @@ export default {
},
},
directives: {
tooltip,
},
data() {
return {
terminalIconSvg,
......@@ -30,7 +30,7 @@ export default {
return 'Terminal';
},
},
};
};
</script>
<template>
<a
......@@ -40,6 +40,7 @@ export default {
:title="title"
:aria-label="title"
:href="terminalPath"
v-html="terminalIconSvg">
v-html="terminalIconSvg"
>
</a>
</template>
......@@ -7,6 +7,15 @@
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default {
components: {
emptyState,
},
mixins: [
CIPaginationMixin,
environmentsMixin,
],
props: {
endpoint: {
type: String,
......@@ -37,14 +46,6 @@
required: true,
},
},
components: {
emptyState,
},
mixins: [
CIPaginationMixin,
environmentsMixin,
],
created() {
eventHub.$on('toggleFolder', this.toggleFolder);
......@@ -99,11 +100,13 @@
<div
v-if="canCreateEnvironment && !isLoading"
class="nav-controls">
class="nav-controls"
>
<a
:href="newEnvironmentPath"
class="btn btn-create">
{{s__("Environments|New environment")}}
class="btn btn-create"
>
{{ s__("Environments|New environment") }}
</a>
</div>
</div>
......
......@@ -30,63 +30,96 @@ export default {
default: false,
},
},
methods: {
folderUrl(model) {
return `${window.location.pathname}/folders/${model.folderName}`;
},
shouldRenderFolderContent(env) {
return env.isFolder &&
env.isOpen &&
env.children &&
env.children.length > 0;
},
},
};
</script>
<template>
<div class="ci-table" role="grid">
<div class="gl-responsive-table-row table-row-header" role="row">
<div class="table-section section-10 environments-name" role="columnheader">
{{s__("Environments|Environment")}}
<div
class="ci-table"
role="grid"
>
<div
class="gl-responsive-table-row table-row-header"
role="row"
>
<div
class="table-section section-10 environments-name"
role="columnheader"
>
{{ s__("Environments|Environment") }}
</div>
<div class="table-section section-10 environments-deploy" role="columnheader">
{{s__("Environments|Deployment")}}
<div
class="table-section section-10 environments-deploy"
role="columnheader"
>
{{ s__("Environments|Deployment") }}
</div>
<div class="table-section section-15 environments-build" role="columnheader">
{{s__("Environments|Job")}}
<div
class="table-section section-15 environments-build"
role="columnheader"
>
{{ s__("Environments|Job") }}
</div>
<div class="table-section section-25 environments-commit" role="columnheader">
{{s__("Environments|Commit")}}
<div
class="table-section section-25 environments-commit"
role="columnheader"
>
{{ s__("Environments|Commit") }}
</div>
<div class="table-section section-10 environments-date" role="columnheader">
{{s__("Environments|Updated")}}
<div
class="table-section section-10 environments-date"
role="columnheader"
>
{{ s__("Environments|Updated") }}
</div>
</div>
<template
v-for="model in environments"
v-bind:model="model">
v-for="(model, i) in environments"
:model="model">
<div
is="environment-item"
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:key="i"
/>
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
<div v-if="model.isLoadingFolderContent">
<template
v-if="shouldRenderFolderContent(model)"
>
<div
v-if="model.isLoadingFolderContent"
:key="i">
<loading-icon size="2" />
</div>
<template v-else>
<div
is="environment-item"
v-for="children in model.children"
v-for="(children, index) in model.children"
:model="children"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:key="index"
/>
<div>
<div :key="i">
<div class="text-center prepend-top-10">
<a
:href="folderUrl(model)"
class="btn btn-default">
{{s__("Environments|Show all")}}
class="btn btn-default"
>
{{ s__("Environments|Show all") }}
</a>
</div>
</div>
......
......@@ -3,6 +3,10 @@
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default {
mixins: [
environmentsMixin,
CIPaginationMixin,
],
props: {
endpoint: {
type: String,
......@@ -25,10 +29,6 @@
required: true,
},
},
mixins: [
environmentsMixin,
CIPaginationMixin,
],
methods: {
successCallback(resp) {
this.saveData(resp);
......@@ -40,10 +40,11 @@
<div :class="cssContainerClass">
<div
class="top-area"
v-if="!isLoading">
v-if="!isLoading"
>
<h4 class="js-folder-name environments-folder-name">
{{s__("Environments|Environments")}} / <b>{{folderName}}</b>
{{ s__("Environments|Environments") }} / <b>{{ folderName }}</b>
</h4>
<tabs
......
......@@ -32,6 +32,9 @@ class RecentSearchesRoot {
const state = this.store.state;
this.vm = new Vue({
el: this.wrapperElement,
components: {
'recent-searches-dropdown-content': RecentSearchesDropdownContent,
},
data() { return state; },
template: `
<recent-searches-dropdown-content
......@@ -40,9 +43,6 @@ class RecentSearchesRoot {
:allowed-keys="allowedKeys"
/>
`,
components: {
'recent-searches-dropdown-content': RecentSearchesDropdownContent,
},
});
}
......
......@@ -42,6 +42,26 @@ export default {
return this.store.getPaginationInfo();
},
},
created() {
this.searchEmptyMessage = this.hideProjects ?
COMMON_STR.GROUP_SEARCH_EMPTY : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
eventHub.$on('fetchPage', this.fetchPage);
eventHub.$on('toggleChildren', this.toggleChildren);
eventHub.$on('leaveGroup', this.leaveGroup);
eventHub.$on('updatePagination', this.updatePagination);
eventHub.$on('updateGroups', this.updateGroups);
},
mounted() {
this.fetchAllGroups();
},
beforeDestroy() {
eventHub.$off('fetchPage', this.fetchPage);
eventHub.$off('toggleChildren', this.toggleChildren);
eventHub.$off('leaveGroup', this.leaveGroup);
eventHub.$off('updatePagination', this.updatePagination);
eventHub.$off('updateGroups', this.updateGroups);
},
methods: {
fetchGroups({ parentId, page, filterGroupsBy, sortBy, archived, updatePagination }) {
return this.service.getGroups(parentId, page, filterGroupsBy, sortBy, archived)
......@@ -152,26 +172,6 @@ export default {
}
},
},
created() {
this.searchEmptyMessage = this.hideProjects ?
COMMON_STR.GROUP_SEARCH_EMPTY : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
eventHub.$on('fetchPage', this.fetchPage);
eventHub.$on('toggleChildren', this.toggleChildren);
eventHub.$on('leaveGroup', this.leaveGroup);
eventHub.$on('updatePagination', this.updatePagination);
eventHub.$on('updateGroups', this.updateGroups);
},
mounted() {
this.fetchAllGroups();
},
beforeDestroy() {
eventHub.$off('fetchPage', this.fetchPage);
eventHub.$off('toggleChildren', this.toggleChildren);
eventHub.$off('leaveGroup', this.leaveGroup);
eventHub.$off('updatePagination', this.updatePagination);
eventHub.$off('updateGroups', this.updateGroups);
},
};
</script>
......
......@@ -20,7 +20,11 @@ export default {
return this.parentGroup.childrenCount > MAX_CHILDREN_COUNT;
},
moreChildrenStats() {
return n__('One more item', '%d more items', this.parentGroup.childrenCount - this.parentGroup.children.length);
return n__(
'One more item',
'%d more items',
this.parentGroup.childrenCount - this.parentGroup.children.length,
);
},
},
};
......@@ -43,8 +47,9 @@ export default {
<i
class="fa fa-external-link"
aria-hidden="true"
/>
{{moreChildrenStats}}
>
</i>
{{ moreChildrenStats }}
</a>
</li>
</ul>
......
......@@ -88,7 +88,8 @@ export default {
:item="group"
/>
<div
class="folder-toggle-wrap">
class="folder-toggle-wrap"
>
<item-caret
:is-group-open="group.isOpen"
/>
......@@ -113,13 +114,14 @@ export default {
<identicon
v-else
size-class="s24"
:entity-id=group.id
:entity-id="group.id"
:entity-name="group.name"
/>
</a>
</div>
<div
class="title namespace-title">
class="title namespace-title"
>
<a
v-tooltip
:href="group.relativePath"
......@@ -135,7 +137,7 @@ export default {
v-if="group.permission"
class="user-access-role"
>
{{group.permission}}
{{ group.permission }}
</span>
</div>
<div
......
<script>
import tablePagination from '~/vue_shared/components/table_pagination.vue';
import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils';
import tablePagination from '~/vue_shared/components/table_pagination.vue';
import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils';
export default {
export default {
components: {
tablePagination,
},
......@@ -33,15 +33,16 @@ export default {
eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam, archivedParam);
},
},
};
};
</script>
<template>
<div class="groups-list-tree-container">
<div
v-if="searchEmpty"
class="has-no-search-results">
{{searchEmptyMessage}}
class="has-no-search-results"
>
{{ searchEmptyMessage }}
</div>
<group-folder
v-if="!searchEmpty"
......@@ -50,7 +51,7 @@ export default {
<table-pagination
v-if="!searchEmpty"
:change="change"
:pageInfo="pageInfo"
:page-info="pageInfo"
/>
</div>
</template>
<script>
import { s__ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
import modal from '~/vue_shared/components/modal.vue';
import eventHub from '../event_hub';
import { COMMON_STR } from '../constants';
import { s__ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
import modal from '~/vue_shared/components/modal.vue';
import eventHub from '../event_hub';
import { COMMON_STR } from '../constants';
export default {
export default {
components: {
icon,
modal,
......@@ -50,7 +50,7 @@ export default {
eventHub.$emit('leaveGroup', this.group, this.parentGroup);
},
},
};
};
</script>
<template>
......
......@@ -2,6 +2,9 @@
import icon from '~/vue_shared/components/icon.vue';
export default {
components: {
icon,
},
props: {
isGroupOpen: {
type: Boolean,
......@@ -9,9 +12,6 @@ export default {
default: false,
},
},
components: {
icon,
},
computed: {
iconClass() {
return this.isGroupOpen ? 'angle-down' : 'angle-right';
......
<script>
import icon from '~/vue_shared/components/icon.vue';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE } from '../constants';
import itemStatsValue from './item_stats_value.vue';
import icon from '~/vue_shared/components/icon.vue';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import {
ITEM_TYPE,
VISIBILITY_TYPE_ICON,
GROUP_VISIBILITY_TYPE,
PROJECT_VISIBILITY_TYPE,
} from '../constants';
import itemStatsValue from './item_stats_value.vue';
export default {
export default {
components: {
icon,
timeAgoTooltip,
......@@ -33,7 +38,7 @@ export default {
return this.item.type === ITEM_TYPE.GROUP;
},
},
};
};
</script>
<template>
......
<script>
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
export default {
export default {
components: {
icon,
},
directives: {
tooltip,
},
props: {
title: {
type: String,
......@@ -35,18 +41,12 @@ export default {
default: '',
},
},
directives: {
tooltip,
},
components: {
icon,
},
computed: {
isValuePresent() {
return this.value !== '';
},
},
};
};
</script>
<template>
......@@ -57,12 +57,12 @@ export default {
:class="cssClass"
:title="title"
>
<icon :name="iconName"/>
<icon :name="iconName" />
<span
v-if="isValuePresent"
class="stat-value"
>
{{value}}
{{ value }}
</span>
</span>
</template>
......@@ -32,7 +32,6 @@
this.$emit('toggleCollapsed');
},
},
};
</script>
......
<script>
import { mapState, mapGetters } from 'vuex';
import ideSidebar from './ide_side_bar.vue';
import ideContextbar from './ide_context_bar.vue';
import repoTabs from './repo_tabs.vue';
import repoFileButtons from './repo_file_buttons.vue';
import ideStatusBar from './ide_status_bar.vue';
import repoPreview from './repo_preview.vue';
import repoEditor from './repo_editor.vue';
import { mapState, mapGetters } from 'vuex';
import ideSidebar from './ide_side_bar.vue';
import ideContextbar from './ide_context_bar.vue';
import repoTabs from './repo_tabs.vue';
import repoFileButtons from './repo_file_buttons.vue';
import ideStatusBar from './ide_status_bar.vue';
import repoPreview from './repo_preview.vue';
import repoEditor from './repo_editor.vue';
export default {
export default {
components: {
ideSidebar,
ideContextbar,
repoTabs,
repoFileButtons,
ideStatusBar,
repoEditor,
repoPreview,
},
props: {
emptyStateSvgPath: {
type: String,
......@@ -25,15 +34,6 @@ export default {
'activeFile',
]),
},
components: {
ideSidebar,
ideContextbar,
repoTabs,
repoFileButtons,
ideStatusBar,
repoEditor,
repoPreview,
},
mounted() {
const returnValue = 'Are you sure you want to lose unsaved changes?';
window.onbeforeunload = (e) => {
......@@ -45,35 +45,38 @@ export default {
return returnValue;
};
},
};
};
</script>
<template>
<div
class="ide-view"
>
<ide-sidebar/>
<ide-sidebar />
<div
class="multi-file-edit-pane"
>
<template
v-if="activeFile">
v-if="activeFile"
>
<repo-tabs/>
<component
class="multi-file-edit-pane-content"
:is="currentBlobView"
/>
<repo-file-buttons/>
<repo-file-buttons />
<ide-status-bar
:file="selectedFile"/>
:file="selectedFile"
/>
</template>
<template
v-else>
v-else
>
<div class="ide-empty-state">
<div class="row js-empty-state">
<div class="col-xs-12">
<div class="svg-content svg-250">
<img :src="emptyStateSvgPath">
<img :src="emptyStateSvgPath" />
</div>
</div>
<div class="col-xs-12">
......@@ -82,7 +85,8 @@ export default {
Welcome to the GitLab IDE
</h4>
<p>
You can select a file in the left sidebar to begin editing and use the right sidebar to commit your changes.
You can select a file in the left sidebar to begin
editing and use the right sidebar to commit your changes.
</p>
</div>
</div>
......
<script>
import { mapGetters, mapState, mapActions } from 'vuex';
import repoCommitSection from './repo_commit_section.vue';
import icon from '../../vue_shared/components/icon.vue';
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
import { mapGetters, mapState, mapActions } from 'vuex';
import repoCommitSection from './repo_commit_section.vue';
import icon from '../../vue_shared/components/icon.vue';
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
export default {
data() {
return {
width: 290,
};
},
export default {
components: {
repoCommitSection,
icon,
panelResizer,
},
data() {
return {
width: 290,
};
},
computed: {
...mapState([
'rightPanelCollapsed',
......@@ -53,7 +53,7 @@ export default {
this.setResizingStatus(false);
},
},
};
};
</script>
<template>
......@@ -64,8 +64,7 @@ export default {
}"
:style="panelStyle"
>
<div
class="multi-file-commit-panel-section">
<div class="multi-file-commit-panel-section">
<header
class="multi-file-commit-panel-header"
:class="{
......@@ -74,7 +73,8 @@ export default {
>
<div
class="multi-file-commit-panel-header-title"
v-if="!rightPanelCollapsed">
v-if="!rightPanelCollapsed"
>
<icon
name="list-bulleted"
:size="18"
......@@ -92,8 +92,7 @@ export default {
/>
</button>
</header>
<repo-commit-section
class=""/>
<repo-commit-section />
</div>
<panel-resizer
:size.sync="width"
......@@ -103,6 +102,7 @@ export default {
:max-size="maxSize"
@resize-start="resizingStarted"
@resize-end="resizingEnded"
side="left"/>
side="left"
/>
</div>
</template>
......@@ -28,20 +28,20 @@ export default {
<div class="branch-header-title">
<icon
name="branch"
:size="12">
</icon>
:size="12"
/>
{{ branch.name }}
</div>
<div class="branch-header-btns">
<new-dropdown
:project-id="projectId"
:branch="branch.name"
path=""/>
path=""
/>
</div>
</div>
<div>
<repo-tree
:treeId="branch.treeId"/>
<repo-tree :tree-id="branch.treeId" />
</div>
</div>
</template>
......@@ -21,7 +21,8 @@ export default {
<div class="context-header">
<a
:title="project.name"
:href="project.web_url">
:href="project.web_url"
>
<div class="avatar-container s40 project-avatar">
<project-avatar-image
class="avatar-container project-avatar"
......@@ -38,10 +39,11 @@ export default {
</div>
<div class="multi-file-commit-panel-inner-scroll">
<branches-tree
v-for="(branch, index) in project.branches"
v-for="branch in project.branches"
:key="branch.name"
:project-id="project.path_with_namespace"
:branch="branch"/>
:branch="branch"
/>
</div>
</div>
</template>
......@@ -44,21 +44,24 @@ export default {
</script>
<template>
<div>
<div>
<div class="ide-file-list">
<table class="table">
<tbody
v-if="treeId">
v-if="treeId"
>
<repo-previous-directory
v-if="hasPreviousDirectory"
/>
<template v-if="showLoading">
<div
class="multi-file-loading-container"
v-if="showLoading"
v-for="n in 3"
:key="n">
<skeleton-loading-container/>
:key="n"
>
<skeleton-loading-container />
</div>
</template>
<repo-file
v-for="file in fetchedList"
:key="file.key"
......@@ -67,5 +70,5 @@ export default {
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import projectTree from './ide_project_tree.vue';
import icon from '../../vue_shared/components/icon.vue';
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
import { mapState, mapActions } from 'vuex';
import projectTree from './ide_project_tree.vue';
import icon from '../../vue_shared/components/icon.vue';
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
export default {
data() {
return {
width: 290,
};
},
export default {
components: {
projectTree,
icon,
panelResizer,
skeletonLoadingContainer,
},
data() {
return {
width: 290,
};
},
computed: {
...mapState([
'loading',
......@@ -57,7 +57,7 @@ export default {
this.setResizingStatus(false);
},
},
};
};
</script>
<template>
......@@ -69,17 +69,20 @@ export default {
:style="panelStyle"
>
<div class="multi-file-commit-panel-inner">
<template v-if="showLoading">
<div
class="multi-file-loading-container"
v-if="showLoading"
v-for="n in 3"
:key="n">
<skeleton-loading-container/>
:key="n"
>
<skeleton-loading-container />
</div>
</template>
<project-tree
v-for="(project, index) in projects"
v-for="project in projects"
:key="project.id"
:project="project"/>
:project="project"
/>
</div>
<button
type="button"
......@@ -93,7 +96,9 @@ export default {
<span
v-if="!leftPanelCollapsed"
class="collapse-text"
>Collapse sidebar</span>
>
Collapse sidebar
</span>
</button>
<panel-resizer
:size.sync="width"
......@@ -103,6 +108,7 @@ export default {
:max-size="maxSize"
@resize-start="resizingStarted"
@resize-end="resizingEnded"
side="right"/>
side="right"
/>
</div>
</template>
<script>
import { mapState } from 'vuex';
import icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import timeAgoMixin from '../../vue_shared/mixins/timeago';
import { mapState } from 'vuex';
import icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import timeAgoMixin from '../../vue_shared/mixins/timeago';
export default {
props: {
file: {
type: Object,
required: true,
},
},
export default {
components: {
icon,
},
......@@ -20,51 +14,52 @@ export default {
mixins: [
timeAgoMixin,
],
props: {
file: {
type: Object,
required: true,
},
},
computed: {
...mapState([
'selectedFile',
]),
},
};
};
</script>
<template>
<div
class="ide-status-bar">
<div class="ide-status-bar">
<div>
<icon
name="branch"
:size="12">
</icon>
:size="12"
/>
{{ selectedFile.branchId }}
</div>
<div>
<div
v-if="selectedFile.lastCommit && selectedFile.lastCommit.id">
<div v-if="selectedFile.lastCommit && selectedFile.lastCommit.id">
Last commit:
<a
v-tooltip
:title="selectedFile.lastCommit.message"
:href="selectedFile.lastCommit.url">
:href="selectedFile.lastCommit.url"
>
{{ timeFormated(selectedFile.lastCommit.updatedAt) }} by
{{ selectedFile.lastCommit.author }}
</a>
</div>
</div>
<div
class="text-right">
<div class="text-right">
{{ selectedFile.name }}
</div>
<div
class="text-right">
<div class="text-right">
{{ selectedFile.eol }}
</div>
<div
class="text-right">
<div class="text-right">
{{ file.editorRow }}:{{ file.editorColumn }}
</div>
<div
class="text-right">
<div class="text-right">
{{ selectedFile.fileLanguage }}
</div>
</div>
......
......@@ -21,6 +21,13 @@
return this.loading || this.branchName === '';
},
},
created() {
// Dropdown is outside of Vue instance & is controlled by Bootstrap
this.$dropdown = $('.git-revision-dropdown');
// text element is outside Vue app
this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
},
methods: {
...mapActions([
'createNewBranch',
......@@ -55,13 +62,6 @@
}));
},
},
created() {
// Dropdown is outside of Vue instance & is controlled by Bootstrap
this.$dropdown = $('.git-revision-dropdown');
// text element is outside Vue app
this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
},
};
</script>
......
......@@ -4,6 +4,11 @@
import icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
icon,
newModal,
upload,
},
props: {
branch: {
type: String,
......@@ -18,11 +23,6 @@
default: null,
},
},
components: {
icon,
newModal,
upload,
},
data() {
return {
openModal: false,
......
......@@ -4,6 +4,9 @@
import modal from '../../../vue_shared/components/modal.vue';
export default {
components: {
modal,
},
props: {
branchId: {
type: String,
......@@ -27,28 +30,6 @@
entryName: this.path !== '' ? `${this.path}/` : '',
};
},
components: {
modal,
},
methods: {
...mapActions([
'createTempEntry',
]),
createEntryInStore() {
this.createTempEntry({
projectId: this.currentProjectId,
branchId: this.branchId,
parent: this.parent,
name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
type: this.type,
});
this.hideModal();
},
hideModal() {
this.$emit('hide');
},
},
computed: {
...mapState([
'currentProjectId',
......@@ -78,6 +59,25 @@
mounted() {
this.$refs.fieldName.focus();
},
methods: {
...mapActions([
'createTempEntry',
]),
createEntryInStore() {
this.createTempEntry({
projectId: this.currentProjectId,
branchId: this.branchId,
parent: this.parent,
name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
type: this.type,
});
this.hideModal();
},
hideModal() {
this.$emit('hide');
},
},
};
</script>
......
......@@ -18,6 +18,12 @@
'currentProjectId',
]),
},
mounted() {
this.$refs.fileUpload.addEventListener('change', this.openFile);
},
beforeDestroy() {
this.$refs.fileUpload.removeEventListener('change', this.openFile);
},
methods: {
...mapActions([
'createTempEntry',
......@@ -59,12 +65,6 @@
this.$refs.fileUpload.click();
},
},
mounted() {
this.$refs.fileUpload.addEventListener('change', this.openFile);
},
beforeDestroy() {
this.$refs.fileUpload.removeEventListener('change', this.openFile);
},
};
</script>
......
......@@ -49,7 +49,9 @@ export default {
const createNewBranch = newBranch || this.startNewMR;
const payload = {
branch: createNewBranch ? `${this.currentBranchId}-${new Date().getTime().toString()}` : this.currentBranchId,
branch: createNewBranch ?
`${this.currentBranchId}-${new Date().getTime().toString()}` :
this.currentBranchId,
commit_message: this.commitMessage,
actions: this.changedFiles.map(f => ({
action: f.tempFile ? 'create' : 'update',
......@@ -103,13 +105,14 @@ export default {
</script>
<template>
<div class="multi-file-commit-panel-section">
<div class="multi-file-commit-panel-section">
<modal
v-if="showNewBranchModal"
:primary-button-label="__('Create new branch')"
kind="primary"
:title="__('Branch has changed')"
:text="__('This branch has changed since you started editing. Would you like to create a new branch?')"
:text="__(`This branch has changed since
you started editing. Would you like to create a new branch?`)"
@cancel="showNewBranchModal = false"
@submit="makeCommit(true)"
/>
......@@ -167,5 +170,5 @@ export default {
</div>
</div>
</form>
</div>
</div>
</template>
......@@ -40,7 +40,7 @@ export default {
aria-hidden="true">
</i>
<span>
{{buttonLabel}}
{{ buttonLabel }}
</span>
</button>
<modal
......
......@@ -6,6 +6,38 @@ import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor';
export default {
computed: {
...mapGetters([
'activeFile',
'activeFileExtension',
]),
...mapState([
'leftPanelCollapsed',
'rightPanelCollapsed',
'panelResizing',
]),
shouldHideEditor() {
return this.activeFile.binary && !this.activeFile.raw;
},
},
watch: {
activeFile(oldVal, newVal) {
if (newVal && !newVal.active) {
this.initMonaco();
}
},
leftPanelCollapsed() {
this.editor.updateDimensions();
},
rightPanelCollapsed() {
this.editor.updateDimensions();
},
panelResizing(isResizing) {
if (isResizing === false) {
this.editor.updateDimensions();
}
},
},
beforeDestroy() {
this.editor.dispose();
},
......@@ -78,38 +110,6 @@ export default {
});
},
},
watch: {
activeFile(oldVal, newVal) {
if (newVal && !newVal.active) {
this.initMonaco();
}
},
leftPanelCollapsed() {
this.editor.updateDimensions();
},
rightPanelCollapsed() {
this.editor.updateDimensions();
},
panelResizing(isResizing) {
if (isResizing === false) {
this.editor.updateDimensions();
}
},
},
computed: {
...mapGetters([
'activeFile',
'activeFileExtension',
]),
...mapState([
'leftPanelCollapsed',
'rightPanelCollapsed',
'panelResizing',
]),
shouldHideEditor() {
return this.activeFile.binary && !this.activeFile.raw;
},
},
};
</script>
......
......@@ -6,14 +6,14 @@
import fileIcon from '../../vue_shared/components/file_icon.vue';
export default {
mixins: [
timeAgoMixin,
],
components: {
skeletonLoadingContainer,
newDropdown,
fileIcon,
},
mixins: [
timeAgoMixin,
],
props: {
file: {
type: Object,
......@@ -60,6 +60,11 @@
};
},
},
updated() {
if (this.file.type === 'blob' && this.file.active) {
this.$el.scrollIntoView();
}
},
methods: {
clickFile(row) {
// Manual Action if a tree is selected/opened
......@@ -72,11 +77,6 @@
this.$router.push(`/project${row.url}`);
},
},
updated() {
if (this.file.type === 'blob' && this.file.active) {
this.$el.scrollIntoView();
}
},
};
</script>
......@@ -99,8 +99,7 @@
:opened="file.opened"
:style="levelIndentation"
:size="16"
>
</file-icon>
/>
{{ file.name }}
</a>
<new-dropdown
......@@ -108,7 +107,8 @@
:project-id="file.projectId"
:branch="file.branchId"
:path="file.path"
:parent="file"/>
:parent="file"
/>
<i
class="fa"
v-if="changedClass"
......
......@@ -35,20 +35,24 @@ export default {
<div
class="btn-group"
role="group"
aria-label="File actions">
aria-label="File actions"
>
<a
:href="activeFile.blamePath"
class="btn btn-default btn-sm blame">
class="btn btn-default btn-sm blame"
>
Blame
</a>
<a
:href="activeFile.commitsPath"
class="btn btn-default btn-sm history">
class="btn btn-default btn-sm history"
>
History
</a>
<a
:href="activeFile.permalink"
class="btn btn-default btn-sm permalink">
class="btn btn-default btn-sm permalink"
>
Permalink
</a>
</div>
......
......@@ -25,15 +25,13 @@
/>
</td>
<template v-if="!leftPanelCollapsed">
<td
class="hidden-sm hidden-xs">
<td class="hidden-sm hidden-xs">
<skeleton-loading-container
:small="true"
/>
</td>
<td
class="hidden-xs">
<td class="hidden-xs">
<skeleton-loading-container
class="animation-container-right"
:small="true"
......
<script>
import { mapGetters } from 'vuex';
import LineHighlighter from '../../line_highlighter';
import syntaxHighlight from '../../syntax_highlight';
import { mapGetters } from 'vuex';
import LineHighlighter from '../../line_highlighter';
import syntaxHighlight from '../../syntax_highlight';
export default {
export default {
computed: {
...mapGetters([
'activeFile',
......@@ -12,11 +12,6 @@ export default {
return this.activeFile.renderError === 'too_large';
},
},
methods: {
highlightFile() {
syntaxHighlight($(this.$el).find('.file-content'));
},
},
mounted() {
this.highlightFile();
this.lineHighlighter = new LineHighlighter({
......@@ -29,11 +24,16 @@ export default {
this.highlightFile();
});
},
};
methods: {
highlightFile() {
syntaxHighlight($(this.$el).find('.file-content'));
},
},
};
</script>
<template>
<div>
<div>
<div
v-if="!activeFile.renderError"
v-html="activeFile.html"
......@@ -51,15 +51,21 @@ export default {
v-else-if="renderErrorTooLarge"
class="vertical-center render-error">
<p class="text-center">
The source could not be displayed because it is too large. You can <a :href="activeFile.rawPath" download>download</a> it instead.
The source could not be displayed because it is too large.
You can <a
:href="activeFile.rawPath"
download>download</a> it instead.
</p>
</div>
<div
v-else
class="vertical-center render-error">
<p class="text-center">
The source could not be displayed because a rendering error occurred. You can <a :href="activeFile.rawPath" download>download</a> it instead.
The source could not be displayed because a rendering error occurred.
You can <a
:href="activeFile.rawPath"
download>download</a> it instead.
</p>
</div>
</div>
</div>
</template>
<script>
import { mapActions } from 'vuex';
import fileIcon from '../../vue_shared/components/file_icon.vue';
import { mapActions } from 'vuex';
import fileIcon from '../../vue_shared/components/file_icon.vue';
export default {
export default {
components: {
fileIcon,
},
props: {
tab: {
type: Object,
required: true,
},
},
components: {
fileIcon,
},
computed: {
closeLabel() {
if (this.tab.changed || this.tab.tempFile) {
......@@ -36,13 +36,11 @@ export default {
this.$router.push(`/project${tab.url}`);
},
},
};
};
</script>
<template>
<li
@click="clickFile(tab)"
>
<li @click="clickFile(tab)">
<button
type="button"
class="multi-file-tab-close"
......@@ -69,8 +67,7 @@ export default {
<file-icon
:file-name="tab.name"
:size="16"
>
</file-icon>
/>
{{ tab.name }}
</div>
</li>
......
<script>
import Visibility from 'visibilityjs';
import { visitUrl } from '../../lib/utils/url_utility';
import Poll from '../../lib/utils/poll';
import eventHub from '../event_hub';
import Service from '../services/index';
import Store from '../stores';
import titleComponent from './title.vue';
import descriptionComponent from './description.vue';
import editedComponent from './edited.vue';
import formComponent from './form.vue';
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
import Visibility from 'visibilityjs';
import { visitUrl } from '../../lib/utils/url_utility';
import Poll from '../../lib/utils/poll';
import eventHub from '../event_hub';
import Service from '../services/index';
import Store from '../stores';
import titleComponent from './title.vue';
import descriptionComponent from './description.vue';
import editedComponent from './edited.vue';
import formComponent from './form.vue';
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
export default {
export default {
components: {
descriptionComponent,
titleComponent,
editedComponent,
formComponent,
},
mixins: [
recaptchaModalImplementor,
],
props: {
endpoint: {
required: true,
......@@ -144,17 +153,40 @@ export default {
return !!this.state.updatedAt;
},
},
components: {
descriptionComponent,
titleComponent,
editedComponent,
formComponent,
created() {
this.service = new Service(this.endpoint);
this.poll = new Poll({
resource: this.service,
method: 'getData',
successCallback: res => this.store.updateState(res.data),
errorCallback(err) {
throw new Error(err);
},
});
mixins: [
recaptchaModalImplementor,
],
if (!Visibility.hidden()) {
this.poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
eventHub.$on('delete.issuable', this.deleteIssuable);
eventHub.$on('update.issuable', this.updateIssuable);
eventHub.$on('close.form', this.closeForm);
eventHub.$on('open.form', this.openForm);
},
beforeDestroy() {
eventHub.$off('delete.issuable', this.deleteIssuable);
eventHub.$off('update.issuable', this.updateIssuable);
eventHub.$off('close.form', this.closeForm);
eventHub.$off('open.form', this.openForm);
},
methods: {
openForm() {
if (!this.showForm) {
......@@ -220,45 +252,11 @@ export default {
});
},
},
created() {
this.service = new Service(this.endpoint);
this.poll = new Poll({
resource: this.service,
method: 'getData',
successCallback: res => this.store.updateState(res.data),
errorCallback(err) {
throw new Error(err);
},
});
if (!Visibility.hidden()) {
this.poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
eventHub.$on('delete.issuable', this.deleteIssuable);
eventHub.$on('update.issuable', this.updateIssuable);
eventHub.$on('close.form', this.closeForm);
eventHub.$on('open.form', this.openForm);
},
beforeDestroy() {
eventHub.$off('delete.issuable', this.deleteIssuable);
eventHub.$off('update.issuable', this.updateIssuable);
eventHub.$off('close.form', this.closeForm);
eventHub.$off('open.form', this.openForm);
},
};
};
</script>
<template>
<div>
<div>
<div v-if="canUpdate && showForm">
<form-component
:form-state="formState"
......@@ -304,5 +302,5 @@ export default {
:updated-by-path="state.updatedByPath"
/>
</div>
</div>
</div>
</template>
......@@ -56,7 +56,10 @@
this.updateTaskStatusText();
},
},
mounted() {
this.renderGFM();
this.updateTaskStatusText();
},
methods: {
renderGFM() {
$(this.$refs['gfm-content']).renderGFM();
......@@ -88,17 +91,17 @@
if (taskRegexMatches) {
$tasks.text(this.taskStatus);
$tasksShort.text(`${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? 's' : ''}`);
$tasksShort.text(
`${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ?
's' :
''}`,
);
} else {
$tasks.text('');
$tasksShort.text('');
}
},
},
mounted() {
this.renderGFM();
this.updateTaskStatusText();
},
};
</script>
......@@ -108,7 +111,8 @@
class="description"
:class="{
'js-task-list-container': canUpdate
}">
}"
>
<div
class="wiki"
:class="{
......
<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: {
timeAgoTooltip,
},
props: {
updatedAt: {
type: String,
......@@ -19,15 +22,12 @@ export default {
default: '',
},
},
components: {
timeAgoTooltip,
},
computed: {
hasUpdatedBy() {
return this.updatedByName && this.updatedByPath;
},
},
};
};
</script>
<template>
......@@ -48,7 +48,7 @@ export default {
class="author_link"
:href="updatedByPath"
>
<span>{{updatedByName}}</span>
<span>{{ updatedByName }}</span>
</a>
</span>
</small>
......
......@@ -3,6 +3,9 @@
import markdownField from '../../../vue_shared/components/markdown/field.vue';
export default {
components: {
markdownField,
},
mixins: [updateMixin],
props: {
formState: {
......@@ -28,9 +31,6 @@
default: true,
},
},
components: {
markdownField,
},
mounted() {
this.$refs.textarea.focus();
},
......
......@@ -6,6 +6,13 @@
import descriptionTemplate from './fields/description_template.vue';
export default {
components: {
lockedWarning,
titleField,
descriptionField,
descriptionTemplate,
editActions,
},
props: {
canDestroy: {
type: Boolean,
......@@ -52,13 +59,6 @@
default: true,
},
},
components: {
lockedWarning,
titleField,
descriptionField,
descriptionTemplate,
editActions,
},
computed: {
hasIssuableTemplates() {
return this.issuableTemplates.length;
......@@ -78,16 +78,19 @@
:form-state="formState"
:issuable-templates="issuableTemplates"
:project-path="projectPath"
:project-namespace="projectNamespace" />
:project-namespace="projectNamespace"
/>
</div>
<div
:class="{
'col-sm-8 col-lg-9': hasIssuableTemplates,
'col-xs-12': !hasIssuableTemplates,
}">
}"
>
<title-field
:form-state="formState"
:issuable-templates="issuableTemplates" />
:issuable-templates="issuableTemplates"
/>
</div>
</div>
<description-field
......@@ -100,6 +103,7 @@
<edit-actions
:form-state="formState"
:can-destroy="canDestroy"
:show-delete-button="showDeleteButton" />
:show-delete-button="showDeleteButton"
/>
</form>
</template>
......@@ -5,14 +5,10 @@
import { spriteIcon } from '../../lib/utils/common_utils';
export default {
mixins: [animateMixin],
data() {
return {
preAnimation: false,
pulseAnimation: false,
titleEl: document.querySelector('title'),
};
directives: {
tooltip,
},
mixins: [animateMixin],
props: {
issuableRef: {
type: String,
......@@ -37,8 +33,17 @@
default: false,
},
},
directives: {
tooltip,
data() {
return {
preAnimation: false,
pulseAnimation: false,
titleEl: document.querySelector('title'),
};
},
computed: {
pencilIcon() {
return spriteIcon('pencil', 'link-highlight');
},
},
watch: {
titleHtml() {
......@@ -46,11 +51,6 @@
this.animateChange();
},
},
computed: {
pencilIcon() {
return spriteIcon('pencil', 'link-highlight');
},
},
methods: {
setPageTitle() {
const currentPageTitleScope = this.titleEl.innerText.split('·');
......
......@@ -3,7 +3,11 @@
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
name: 'jobHeaderSection',
name: 'JobHeaderSection',
components: {
ciHeader,
loadingIcon,
},
props: {
job: {
type: Object,
......@@ -14,10 +18,6 @@
required: true,
},
},
components: {
ciHeader,
loadingIcon,
},
data() {
return {
actions: this.getActions(),
......@@ -34,6 +34,11 @@
return this.job.started;
},
},
watch: {
job() {
this.actions = this.getActions();
},
},
methods: {
getActions() {
const actions = [];
......@@ -49,11 +54,6 @@
return actions;
},
},
watch: {
job() {
this.actions = this.getActions();
},
},
};
</script>
<template>
......
......@@ -23,9 +23,10 @@
<p class="build-detail-row">
<span
v-if="hasTitle"
class="build-light-text">
{{title}}:
class="build-light-text"
>
{{ title }}:
</span>
{{value}}
{{ value }}
</p>
</template>
......@@ -6,6 +6,13 @@
export default {
name: 'SidebarDetailsBlock',
components: {
detailRow,
loadingIcon,
},
mixins: [
timeagoMixin,
],
props: {
job: {
type: Object,
......@@ -16,13 +23,6 @@
required: true,
},
},
mixins: [
timeagoMixin,
],
components: {
detailRow,
loadingIcon,
},
computed: {
shouldRenderContent() {
return !this.isLoading && Object.keys(this.job).length > 0;
......@@ -58,11 +58,13 @@
<template v-if="shouldRenderContent">
<div
class="block retry-link"
v-if="job.retry_path || job.new_issue_path">
v-if="job.retry_path || job.new_issue_path"
>
<a
v-if="job.new_issue_path"
class="js-new-issue btn btn-new btn-inverted"
:href="job.new_issue_path">
:href="job.new_issue_path"
>
New issue
</a>
<a
......@@ -70,20 +72,21 @@
class="js-retry-job btn btn-inverted-secondary"
:href="job.retry_path"
data-method="post"
rel="nofollow">
rel="nofollow"
>
Retry
</a>
</div>
<div :class="{block : renderBlock }">
<p
class="build-detail-row js-job-mr"
v-if="job.merge_request">
<span
class="build-light-text">
v-if="job.merge_request"
>
<span class="build-light-text">
Merge Request:
</span>
<a :href="job.merge_request.path">
!{{job.merge_request.iid}}
!{{ job.merge_request.iid }}
</a>
</p>
......@@ -125,16 +128,16 @@
/>
<p
class="build-detail-row js-job-tags"
v-if="job.tags.length">
<span
class="build-light-text">
v-if="job.tags.length"
>
<span class="build-light-text">
Tags:
</span>
<span
v-for="tag in job.tags"
key="tag"
v-for="(tag, i) in job.tags"
:key="i"
class="label label-primary">
{{tag}}
{{ tag }}
</span>
</p>
......@@ -146,7 +149,8 @@
class="js-cancel-job btn btn-sm btn-default"
:href="job.cancel_path"
data-method="post"
rel="nofollow">
rel="nofollow"
>
Cancel
</a>
</div>
......
......@@ -13,14 +13,14 @@ document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line no-new
new Vue({
el: '#js-build-header-vue',
components: {
jobHeader,
},
data() {
return {
mediator,
};
},
components: {
jobHeader,
},
mounted() {
this.mediator.initBuildClass();
},
......@@ -38,14 +38,14 @@ document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line
new Vue({
el: '#js-details-block-vue',
components: {
detailsBlock,
},
data() {
return {
mediator,
};
},
components: {
detailsBlock,
},
render(createElement) {
return createElement('details-block', {
props: {
......
......@@ -25,12 +25,12 @@ $(() => {
gl.MergeConflictsResolverApp = new Vue({
el: '#conflicts',
data: mergeConflictsStore.state,
components: {
'diff-file-editor': gl.mergeConflicts.diffFileEditor,
'inline-conflict-lines': gl.mergeConflicts.inlineConflictLines,
'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines
},
data: mergeConflictsStore.state,
computed: {
conflictsCountText() { return mergeConflictsStore.getConflictsCountText(); },
readyToCommit() { return mergeConflictsStore.isReadyToCommit(); },
......
......@@ -11,6 +11,12 @@
export default {
components: {
Graph,
GraphGroup,
EmptyState,
},
data() {
const metricsData = document.querySelector('#prometheus-graphs').dataset;
const store = new MonitoringStore();
......@@ -36,12 +42,30 @@
};
},
components: {
Graph,
GraphGroup,
EmptyState,
created() {
this.service = new MonitoringService({
metricsEndpoint: this.metricsEndpoint,
deploymentEndpoint: this.deploymentEndpoint,
});
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$on('hoverChanged', this.hoverChanged);
},
beforeDestroy() {
eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$off('hoverChanged', this.hoverChanged);
window.removeEventListener('resize', this.resizeThrottled, false);
},
mounted() {
this.resizeThrottled = _.throttle(this.resize, 600);
if (!this.hasMetrics) {
this.state = 'gettingStarted';
} else {
this.getGraphsData();
window.addEventListener('resize', this.resizeThrottled, false);
}
},
methods: {
getGraphsData() {
this.state = 'loading';
......@@ -72,36 +96,14 @@
this.hoverData = data;
},
},
created() {
this.service = new MonitoringService({
metricsEndpoint: this.metricsEndpoint,
deploymentEndpoint: this.deploymentEndpoint,
});
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$on('hoverChanged', this.hoverChanged);
},
beforeDestroy() {
eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$off('hoverChanged', this.hoverChanged);
window.removeEventListener('resize', this.resizeThrottled, false);
},
mounted() {
this.resizeThrottled = _.throttle(this.resize, 600);
if (!this.hasMetrics) {
this.state = 'gettingStarted';
} else {
this.getGraphsData();
window.addEventListener('resize', this.resizeThrottled, false);
}
},
};
</script>
<template>
<div v-if="!showEmptyState" class="prometheus-graphs">
<div
v-if="!showEmptyState"
class="prometheus-graphs"
>
<graph-group
v-for="(groupData, index) in store.groups"
:key="index"
......
......@@ -33,13 +33,15 @@
gettingStarted: {
svgUrl: this.emptyGettingStartedSvgPath,
title: 'Get started with performance monitoring',
description: 'Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments.',
description: `Stay updated about the performance and health
of your environment by configuring Prometheus to monitor your deployments.`,
buttonText: 'Configure Prometheus',
},
loading: {
svgUrl: this.emptyLoadingSvgPath,
title: 'Waiting for performance data',
description: 'Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.',
description: `Creating graphs uses the data from the Prometheus server.
If this takes a long time, ensure that data is available.`,
buttonText: 'View documentation',
},
unableToConnect: {
......@@ -74,20 +76,26 @@
<template>
<div class="prometheus-state">
<div class="state-svg svg-content">
<img :src="currentState.svgUrl"/>
<img :src="currentState.svgUrl" />
</div>
<h4 class="state-title">
{{currentState.title}}
{{ currentState.title }}
</h4>
<p class="state-description">
{{currentState.description}}
<a v-if="showButtonDescription" :href="settingsPath">
{{ currentState.description }}
<a
v-if="showButtonDescription"
:href="settingsPath"
>
Prometheus server
</a>
</p>
<div class="state-button">
<a class="btn btn-success" :href="buttonPath">
{{currentState.buttonText}}
<a
class="btn btn-success"
:href="buttonPath"
>
{{ currentState.buttonText }}
</a>
</div>
</div>
......
......@@ -17,6 +17,15 @@
const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select };
export default {
components: {
GraphLegend,
GraphFlag,
GraphDeployment,
GraphPath,
},
mixins: [MonitoringMixin],
props: {
graphData: {
type: Object,
......@@ -45,8 +54,6 @@
},
},
mixins: [MonitoringMixin],
data() {
return {
baseGraphHeight: 450,
......@@ -74,13 +81,6 @@
};
},
components: {
GraphLegend,
GraphFlag,
GraphDeployment,
GraphPath,
},
computed: {
outerViewBox() {
return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
......@@ -105,6 +105,26 @@
},
},
watch: {
updateAspectRatio() {
if (this.updateAspectRatio) {
this.graphHeight = 450;
this.graphWidth = 600;
this.measurements = measurements.large;
this.draw();
eventHub.$emit('toggleAspectRatio');
}
},
hoverData() {
this.positionFlag();
},
},
mounted() {
this.draw();
},
methods: {
draw() {
const breakpointSize = bp.getBreakpointSize();
......@@ -197,26 +217,6 @@
}); // This will select all of the ticks once they're rendered
},
},
watch: {
updateAspectRatio() {
if (this.updateAspectRatio) {
this.graphHeight = 450;
this.graphWidth = 600;
this.measurements = measurements.large;
this.draw();
eventHub.$emit('toggleAspectRatio');
}
},
hoverData() {
this.positionFlag();
},
},
mounted() {
this.draw();
},
};
</script>
......@@ -224,24 +224,27 @@
<div
class="prometheus-graph"
@mouseover="showFlagContent = true"
@mouseleave="showFlagContent = false">
@mouseleave="showFlagContent = false"
>
<h5 class="text-center graph-title">
{{graphData.title}}
{{ graphData.title }}
</h5>
<div
class="prometheus-svg-container"
:style="paddingBottomRootSvg">
:style="paddingBottomRootSvg"
>
<svg
:viewBox="outerViewBox"
ref="baseSvg">
ref="baseSvg"
>
<g
class="x-axis"
:transform="axisTransform">
</g>
:transform="axisTransform"
/>
<g
class="y-axis"
transform="translate(70, 20)">
</g>
transform="translate(70, 20)"
/>
<graph-legend
:graph-width="graphWidth"
:graph-height="graphHeight"
......@@ -256,7 +259,8 @@
<svg
class="graph-data"
:viewBox="innerViewBox"
ref="graphData">
ref="graphData"
>
<graph-path
v-for="(path, index) in timeSeries"
:key="index"
......@@ -277,8 +281,8 @@
:height="(graphHeight - 100)"
transform="translate(-5, 20)"
ref="graphOverlay"
@mousemove="handleMouseOverGraph($event)">
</rect>
@mousemove="handleMouseOverGraph($event)"
/>
</svg>
</svg>
<graph-flag
......
......@@ -39,33 +39,35 @@
y="0"
:height="calculatedHeight"
width="3"
fill="url(#shadow-gradient)">
</rect>
fill="url(#shadow-gradient)"
/>
<line
class="deployment-line"
x1="0"
y1="0"
x2="0"
:y2="calculatedHeight"
stroke="#000">
</line>
stroke="#000"
/>
</g>
<svg
height="0"
width="0">
width="0"
>
<defs>
<linearGradient
id="shadow-gradient">
id="shadow-gradient"
>
<stop
offset="0%"
stop-color="#000"
stop-opacity="0.4">
</stop>
stop-opacity="0.4"
/>
<stop
offset="100%"
stop-color="#000"
stop-opacity="0">
</stop>
stop-opacity="0"
/>
</linearGradient>
</defs>
</svg>
......
<script>
import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
import { formatRelevantDigits } from '../../../lib/utils/number_utils';
import Icon from '../../../vue_shared/components/icon.vue';
import icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
icon,
},
props: {
currentXCoordinate: {
type: Number,
......@@ -52,10 +55,6 @@
},
},
components: {
Icon,
},
computed: {
formatTime() {
return this.deploymentFlagData ?
......@@ -137,33 +136,34 @@
>
<div class="arrow"></div>
<div class="popover-title">
<h5 v-if="this.deploymentFlagData">
<h5 v-if="deploymentFlagData">
Deployed
</h5>
{{formatDate}} at
<strong>{{formatTime}}</strong>
{{ formatDate }} at
<strong>{{ formatTime }}</strong>
</div>
<div
v-if="this.deploymentFlagData"
v-if="deploymentFlagData"
class="popover-content deploy-meta-content"
>
<div>
<icon
name="commit"
:size="12">
</icon>
:size="12"
/>
<a :href="deploymentFlagData.commitUrl">
{{deploymentFlagData.sha.slice(0, 8)}}
{{ deploymentFlagData.sha.slice(0, 8) }}
</a>
</div>
<div
v-if="deploymentFlagData.tag">
v-if="deploymentFlagData.tag"
>
<icon
name="label"
:size="12">
</icon>
:size="12"
/>
<a :href="deploymentFlagData.tagUrl">
{{deploymentFlagData.ref}}
{{ deploymentFlagData.ref }}
</a>
</div>
</div>
......@@ -174,7 +174,10 @@
:key="index"
>
<td>
<svg width="15" height="6">
<svg
width="15"
height="6"
>
<line
:stroke="series.lineColor"
:stroke-dasharray="strokeDashArray(series.lineStyle)"
......@@ -182,13 +185,13 @@
x1="0"
x2="15"
y1="2"
y2="2">
</line>
y2="2"
/>
</svg>
</td>
<td>{{seriesMetricLabel(index, series)}}</td>
<td>{{ seriesMetricLabel(index, series) }}</td>
<td>
<strong>{{seriesMetricValue(series)}}</strong>
<strong>{{ seriesMetricValue(series) }}</strong>
</td>
</tr>
</table>
......
......@@ -73,6 +73,21 @@
},
},
mounted() {
this.$nextTick(() => {
const bbox = this.$refs.ylabel.getBBox();
this.metricUsageXPosition = 0;
this.seriesXPosition = 0;
if (this.$refs.legendTitleSvg != null) {
this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width;
}
if (this.$refs.seriesTitleSvg != null) {
this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width;
}
this.yLabelWidth = bbox.width + 10; // Added some padding
this.yLabelHeight = bbox.height + 5;
});
},
methods: {
translateLegendGroup(index) {
return `translate(0, ${12 * (index)})`;
......@@ -100,26 +115,10 @@
return null;
},
},
mounted() {
this.$nextTick(() => {
const bbox = this.$refs.ylabel.getBBox();
this.metricUsageXPosition = 0;
this.seriesXPosition = 0;
if (this.$refs.legendTitleSvg != null) {
this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width;
}
if (this.$refs.seriesTitleSvg != null) {
this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width;
}
this.yLabelWidth = bbox.width + 10; // Added some padding
this.yLabelHeight = bbox.height + 5;
});
},
};
</script>
<template>
<g
class="axis-label-container">
<g class="axis-label-container">
<line
class="label-x-axis-line"
stroke="#000000"
......@@ -127,8 +126,8 @@
x1="10"
:y1="yPosition"
:x2="graphWidth + 20"
:y2="yPosition">
</line>
:y2="yPosition"
/>
<line
class="label-y-axis-line"
stroke="#000000"
......@@ -136,39 +135,43 @@
x1="10"
y1="0"
:x2="10"
:y2="yPosition">
</line>
:y2="yPosition"
/>
<rect
class="rect-axis-text"
:transform="rectTransform"
:width="yLabelWidth"
:height="yLabelHeight">
</rect>
:height="yLabelHeight"
/>
<text
class="label-axis-text y-label-text"
text-anchor="middle"
:transform="textTransform"
ref="ylabel">
{{yAxisLabel}}
ref="ylabel"
>
{{ yAxisLabel }}
</text>
<rect
class="rect-axis-text"
:x="xPosition + 60"
:y="graphHeight - 80"
width="35"
height="50">
</rect>
height="50"
/>
<text
class="label-axis-text x-label-text"
:x="xPosition + 60"
:y="yPosition"
dy=".35em">
dy=".35em"
>
Time
</text>
<g class="legend-group"
<g
class="legend-group"
v-for="(series, index) in timeSeries"
:key="index"
:transform="translateLegendGroup(index)">
:transform="translateLegendGroup(index)"
>
<line
:stroke="series.lineColor"
:stroke-width="measurements.legends.height"
......@@ -176,23 +179,25 @@
:x1="measurements.legends.offsetX"
:x2="measurements.legends.offsetX + measurements.legends.width"
:y1="graphHeight - measurements.legends.offsetY"
:y2="graphHeight - measurements.legends.offsetY">
</line>
:y2="graphHeight - measurements.legends.offsetY"
/>
<text
v-if="timeSeries.length > 1"
class="legend-metric-title"
ref="legendTitleSvg"
x="38"
:y="graphHeight - 30">
{{createSeriesString(index, series)}}
:y="graphHeight - 30"
>
{{ createSeriesString(index, series) }}
</text>
<text
v-else
class="legend-metric-title"
ref="legendTitleSvg"
x="38"
:y="graphHeight - 30">
{{legendTitle}} {{formatMetricUsage(series)}}
:y="graphHeight - 30"
>
{{ legendTitle }} {{ formatMetricUsage(series) }}
</text>
</g>
</g>
......
......@@ -12,6 +12,7 @@
lineStyle: {
type: String,
required: false,
default: '',
},
lineColor: {
type: String,
......@@ -37,8 +38,8 @@
class="metric-area"
:d="generatedAreaPath"
:fill="areaColor"
transform="translate(-5, 20)">
</path>
transform="translate(-5, 20)"
/>
<path
class="metric-line"
:d="generatedLinePath"
......@@ -46,7 +47,7 @@
fill="none"
stroke-width="1"
:stroke-dasharray="strokeDashArray"
transform="translate(-5, 20)">
</path>
transform="translate(-5, 20)"
/>
</g>
</template>
<script>
export default {
export default {
props: {
name: {
type: String,
required: true,
},
},
};
};
</script>
<template>
<div class="panel panel-default prometheus-panel">
<div class="panel-heading">
<h4>{{name}}</h4>
<h4>{{ name }}</h4>
</div>
<div class="panel-body prometheus-graph-group">
<slot />
<slot></slot>
</div>
</div>
</template>
......@@ -91,18 +91,21 @@
<template>
<div class="cell text-cell">
<prompt />
<div class="markdown" v-html="markdown"></div>
<div
class="markdown"
v-html="markdown">
</div>
</div>
</template>
<style>
.markdown .katex {
.markdown .katex {
display: block;
text-align: center;
}
}
.markdown .inline-katex .katex {
.markdown .inline-katex .katex {
display: inline;
text-align: initial;
}
}
</style>
<script>
import Prompt from '../prompt.vue';
import Prompt from '../prompt.vue';
export default {
export default {
components: {
prompt: Prompt,
},
props: {
rawCode: {
type: String,
required: true,
},
},
components: {
prompt: Prompt,
},
};
};
</script>
<template>
......
<script>
import Prompt from '../prompt.vue';
import Prompt from '../prompt.vue';
export default {
export default {
components: {
prompt: Prompt,
},
props: {
outputType: {
type: String,
......@@ -12,16 +15,12 @@ export default {
required: true,
},
},
components: {
prompt: Prompt,
},
};
};
</script>
<template>
<div class="output">
<prompt />
<img
:src="'data:' + outputType + ';base64,' + rawCode" />
<img :src="'data:' + outputType + ';base64,' + rawCode" />
</div>
</template>
<script>
import CodeCell from '../code/index.vue';
import Html from './html.vue';
import Image from './image.vue';
import CodeCell from '../code/index.vue';
import Html from './html.vue';
import Image from './image.vue';
export default {
export default {
components: {
'code-cell': CodeCell,
'html-output': Html,
'image-output': Image,
},
props: {
codeCssClass: {
type: String,
......@@ -18,37 +23,21 @@ export default {
output: {
type: Object,
requred: true,
default: () => ({}),
},
},
components: {
'code-cell': CodeCell,
'html-output': Html,
'image-output': Image,
},
data() {
return {
outputType: '',
};
},
computed: {
componentName() {
if (this.output.text) {
return 'code-cell';
} else if (this.output.data['image/png']) {
this.outputType = 'image/png';
return 'image-output';
} else if (this.output.data['text/html']) {
this.outputType = 'text/html';
return 'html-output';
} else if (this.output.data['image/svg+xml']) {
this.outputType = 'image/svg+xml';
return 'html-output';
}
this.outputType = 'text/plain';
return 'code-cell';
},
rawCode() {
......@@ -58,6 +47,19 @@ export default {
return this.dataForType(this.outputType);
},
outputType() {
if (this.output.text) {
return '';
} else if (this.output.data['image/png']) {
return 'image/png';
} else if (this.output.data['text/html']) {
return 'text/html';
} else if (this.output.data['image/svg+xml']) {
return 'image/svg+xml';
}
return 'text/plain';
},
},
methods: {
dataForType(type) {
......@@ -70,14 +72,16 @@ export default {
return data;
},
},
};
};
</script>
<template>
<component :is="componentName"
<component
:is="componentName"
type="output"
:outputType="outputType"
:output-type="outputType"
:count="count"
:raw-code="rawCode"
:code-css-class="codeCssClass" />
:code-css-class="codeCssClass"
/>
</template>
......@@ -4,10 +4,17 @@
type: {
type: String,
required: false,
default: '',
},
count: {
type: Number,
required: false,
default: 0,
},
},
computed: {
hasKeys() {
return this.type !== '' && this.count;
},
},
};
......@@ -15,16 +22,16 @@
<template>
<div class="prompt">
<span v-if="type && count">
<span v-if="hasKeys">
{{ type }} [{{ count }}]:
</span>
</div>
</template>
<style scoped>
.prompt {
.prompt {
padding: 0 10px;
min-width: 7em;
font-family: monospace;
}
}
</style>
......@@ -20,11 +20,6 @@
default: '',
},
},
methods: {
cellType(type) {
return `${type}-cell`;
},
},
computed: {
cells() {
if (this.notebook.worksheets) {
......@@ -45,6 +40,11 @@
return Object.keys(this.notebook).length;
},
},
methods: {
cellType(type) {
return `${type}-cell`;
},
},
};
</script>
......
......@@ -15,7 +15,17 @@
import issuableStateMixin from '../mixins/issuable_state';
export default {
name: 'commentForm',
name: 'CommentForm',
components: {
issueWarning,
noteSignedOutWidget,
discussionLockedWidget,
markdownField,
userAvatarLink,
},
mixins: [
issuableStateMixin,
],
data() {
return {
note: '',
......@@ -27,21 +37,6 @@
isSubmitButtonDisabled: true,
};
},
components: {
issueWarning,
noteSignedOutWidget,
discussionLockedWidget,
markdownField,
userAvatarLink,
},
watch: {
note(newNote) {
this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
},
isSubmitting(newValue) {
this.setIsSubmitButtonDisabled(this.note, newValue);
},
},
computed: {
...mapGetters([
'getCurrentUserLastNote',
......@@ -65,7 +60,9 @@
if (this.note.length) {
const actionText = this.isIssueOpen ? 'close' : 'reopen';
return this.noteType === constants.COMMENT ? `Comment & ${actionText} issue` : `Start discussion & ${actionText} issue`;
return this.noteType === constants.COMMENT ?
`Comment & ${actionText} issue` :
`Start discussion & ${actionText} issue`;
}
return this.isIssueOpen ? 'Close issue' : 'Reopen issue';
......@@ -97,6 +94,23 @@
return this.getNoteableData.create_note_path;
},
},
watch: {
note(newNote) {
this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
},
isSubmitting(newValue) {
this.setIsSubmitButtonDisabled(this.note, newValue);
},
},
mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery.
$(document).on('issuable:change', (e, isClosed) => {
this.issueState = isClosed ? constants.CLOSED : constants.REOPENED;
});
this.initAutoSave();
this.initTaskList();
},
methods: {
...mapActions([
'saveNote',
......@@ -159,7 +173,9 @@
.catch(() => {
this.isSubmitting = false;
this.discard(false);
const msg = 'Your comment could not be submitted! Please check your network connection and try again.';
const msg =
`Your comment could not be submitted!
Please check your network connection and try again.`;
Flash(msg, 'alert', this.$el);
this.note = noteData.data.note.note; // Restore textarea content.
this.removePlaceholderNotes();
......@@ -207,7 +223,11 @@
},
initAutoSave() {
if (this.isLoggedIn) {
this.autosave = new Autosave($(this.$refs.textarea), ['Note', 'Issue', this.getNoteableData.id], 'issue');
this.autosave = new Autosave(
$(this.$refs.textarea),
['Note', 'Issue', this.getNoteableData.id],
'issue',
);
}
},
initTaskList() {
......@@ -223,18 +243,6 @@
});
},
},
mixins: [
issuableStateMixin,
],
mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery.
$(document).on('issuable:change', (e, isClosed) => {
this.issueState = isClosed ? constants.CLOSED : constants.REOPENED;
});
this.initAutoSave();
this.initTaskList();
},
};
</script>
......@@ -283,7 +291,8 @@
<textarea
id="note-body"
name="note[note]"
class="note-textarea js-vue-comment-form js-gfm-input js-autosize markdown-area js-vue-textarea"
class="note-textarea js-vue-comment-form
js-gfm-input js-autosize markdown-area js-vue-textarea"
data-supports-quick-actions="true"
aria-label="Description"
v-model="note"
......@@ -296,13 +305,15 @@
</textarea>
</markdown-field>
<div class="note-form-actions">
<div class="pull-left btn-group append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown">
<div
class="pull-left btn-group
append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown">
<button
@click.prevent="handleSave()"
:disabled="isSubmitButtonDisabled"
class="btn btn-create comment-btn js-comment-button js-comment-submit-button"
type="submit">
{{commentButtonTitle}}
{{ commentButtonTitle }}
</button>
<button
:disabled="isSubmitButtonDisabled"
......@@ -362,7 +373,7 @@
:class="actionButtonClassNames"
:disabled="isSubmitting"
class="btn btn-comment btn-comment-and-close js-action-button">
{{issueActionButtonTitle}}
{{ issueActionButtonTitle }}
</button>
<button
type="button"
......
......@@ -3,12 +3,12 @@
import Issuable from '~/vue_shared/mixins/issuable';
export default {
mixins: [
Issuable,
],
components: {
Icon,
},
mixins: [
Issuable,
],
};
</script>
......@@ -18,9 +18,11 @@
<icon
name="lock"
:size="16"
class="icon">
</icon>
<span>This {{ issuableDisplayName }} is locked. Only <b>project members</b> can comment.</span>
class="icon"
/>
<span>
This {{ issuableDisplayName }} is locked. Only <b>project members</b> can comment.
</span>
</span>
</div>
</template>
......@@ -9,7 +9,13 @@
import tooltip from '~/vue_shared/directives/tooltip';
export default {
name: 'noteActions',
name: 'NoteActions',
directives: {
tooltip,
},
components: {
loadingIcon,
},
props: {
authorId: {
type: Number,
......@@ -41,12 +47,6 @@
required: true,
},
},
directives: {
tooltip,
},
components: {
loadingIcon,
},
computed: {
...mapGetters([
'getUserDataByProp',
......@@ -64,6 +64,13 @@
return this.getUserDataByProp('id');
},
},
created() {
this.emojiSmiling = emojiSmiling;
this.emojiSmile = emojiSmile;
this.emojiSmiley = emojiSmiley;
this.editSvg = editSvg;
this.ellipsisSvg = ellipsisSvg;
},
methods: {
onEdit() {
this.$emit('handleEdit');
......@@ -72,13 +79,6 @@
this.$emit('handleDelete');
},
},
created() {
this.emojiSmiling = emojiSmiling;
this.emojiSmile = emojiSmile;
this.emojiSmiley = emojiSmiley;
this.editSvg = editSvg;
this.ellipsisSvg = ellipsisSvg;
},
};
</script>
......@@ -86,7 +86,9 @@
<div class="note-actions">
<span
v-if="accessLevel"
class="note-role user-access-role">{{accessLevel}}</span>
class="note-role user-access-role">
{{ accessLevel }}
</span>
<div
v-if="canAddAwardEmoji"
class="note-actions-item">
......@@ -98,7 +100,8 @@
data-placement="bottom"
data-container="body"
href="#"
title="Add reaction">
title="Add reaction"
>
<loading-icon :inline="true" />
<span
v-html="emojiSmiling"
......@@ -127,7 +130,8 @@
data-placement="bottom">
<span
v-html="editSvg"
class="link-highlight"></span>
class="link-highlight">
</span>
</button>
</div>
<div
......@@ -143,7 +147,8 @@
data-placement="bottom">
<span
class="icon"
v-html="ellipsisSvg"></span>
v-html="ellipsisSvg">
</span>
</button>
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
<li v-if="canReportAsAbuse">
......
<script>
export default {
name: 'noteAttachment',
name: 'NoteAttachment',
props: {
attachment: {
type: Object,
......@@ -19,7 +19,8 @@
rel="noopener noreferrer">
<img
:src="attachment.url"
class="note-image-attach" />
class="note-image-attach"
/>
</a>
<div class="attachment">
<a
......@@ -29,8 +30,9 @@
rel="noopener noreferrer">
<i
class="fa fa-paperclip"
aria-hidden="true"></i>
{{attachment.filename}}
aria-hidden="true">
</i>
{{ attachment.filename }}
</a>
</div>
</div>
......
......@@ -2,7 +2,10 @@
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
export default {
name: 'editedNoteText',
name: 'EditedNoteText',
components: {
timeAgoTooltip,
},
props: {
actionText: {
type: String,
......@@ -15,6 +18,7 @@
editedBy: {
type: Object,
required: false,
default: () => ({}),
},
className: {
type: String,
......@@ -22,15 +26,12 @@
default: 'edited-text',
},
},
components: {
timeAgoTooltip,
},
};
</script>
<template>
<div :class="className">
{{actionText}}
{{ actionText }}
<time-ago-tooltip
:time="editedAt"
tooltip-placement="bottom"
......@@ -40,7 +41,7 @@
<a
:href="editedBy.path"
class="js-vue-author author_link">
{{editedBy.name}}
{{ editedBy.name }}
</a>
</template>
</div>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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