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