Commit f09735d3 authored by Luke Bennett's avatar Luke Bennett

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents 47b04690 215fd04e
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
"plugins": [ "plugins": [
["istanbul", { ["istanbul", {
"exclude": [ "exclude": [
"spec/javascripts/**/*" "spec/javascripts/**/*",
"app/assets/javascripts/locale/**/app.js"
] ]
}], }],
["transform-define", { ["transform-define", {
......
...@@ -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"
}]
} }
} }
...@@ -604,6 +604,7 @@ codequality: ...@@ -604,6 +604,7 @@ codequality:
paths: [codeclimate.json] paths: [codeclimate.json]
sast: sast:
<<: *except-docs
image: registry.gitlab.com/gitlab-org/gl-sast:latest image: registry.gitlab.com/gitlab-org/gl-sast:latest
before_script: [] before_script: []
script: script:
...@@ -623,6 +624,18 @@ qa:internal: ...@@ -623,6 +624,18 @@ qa:internal:
- bundle install - bundle install
- bundle exec rspec - bundle exec rspec
qa:selectors:
<<: *dedicated-runner
<<: *except-docs
stage: test
variables:
SETUP_DB: "false"
services: []
script:
- cd qa/
- bundle install
- bundle exec bin/qa Test::Sanity::Selectors
coverage: coverage:
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs-and-qa <<: *except-docs-and-qa
......
...@@ -385,9 +385,6 @@ gem 'ruby-prof', '~> 0.16.2' ...@@ -385,9 +385,6 @@ gem 'ruby-prof', '~> 0.16.2'
# OAuth # OAuth
gem 'oauth2', '~> 1.4' gem 'oauth2', '~> 1.4'
# Soft deletion
gem 'paranoia', '~> 2.3.1'
# Health check # Health check
gem 'health_check', '~> 2.6.0' gem 'health_check', '~> 2.6.0'
......
...@@ -580,8 +580,6 @@ GEM ...@@ -580,8 +580,6 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (0.9.6) os (0.9.6)
parallel (1.12.0) parallel (1.12.0)
paranoia (2.3.1)
activerecord (>= 4.0, < 5.2)
parser (2.4.0.2) parser (2.4.0.2)
ast (~> 2.3) ast (~> 2.3)
parslet (1.5.0) parslet (1.5.0)
...@@ -1117,7 +1115,6 @@ DEPENDENCIES ...@@ -1117,7 +1115,6 @@ DEPENDENCIES
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
paranoia (~> 2.3.1)
peek (~> 1.0.1) peek (~> 1.0.1)
peek-gc (~> 0.0.2) peek-gc (~> 0.0.2)
peek-host (~> 1.0.0) peek-host (~> 1.0.0)
......
app/assets/images/multi-editor-on.png

5.34 KB | W: | H:

app/assets/images/multi-editor-on.png

3.88 KB | W: | H:

app/assets/images/multi-editor-on.png
app/assets/images/multi-editor-on.png
app/assets/images/multi-editor-on.png
app/assets/images/multi-editor-on.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -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: {
......
This diff is collapsed.
...@@ -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,
},
}); });
} }
......
<script> <script>
/* global Flash */ /* global Flash */
import { s__ } from '~/locale';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import modal from '~/vue_shared/components/modal.vue';
import { getParameterByName } from '~/lib/utils/common_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import { COMMON_STR } from '../constants'; import { COMMON_STR } from '../constants';
import { mergeUrlParams } from '../../lib/utils/url_utility';
import groupsComponent from './groups.vue'; import groupsComponent from './groups.vue';
export default { export default {
components: { components: {
loadingIcon, loadingIcon,
modal,
groupsComponent, groupsComponent,
}, },
props: { props: {
...@@ -32,6 +36,10 @@ export default { ...@@ -32,6 +36,10 @@ export default {
isLoading: true, isLoading: true,
isSearchEmpty: false, isSearchEmpty: false,
searchEmptyMessage: '', searchEmptyMessage: '',
showModal: false,
groupLeaveConfirmationMessage: '',
targetGroup: null,
targetParentGroup: null,
}; };
}, },
computed: { computed: {
...@@ -42,6 +50,26 @@ export default { ...@@ -42,6 +50,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('showLeaveGroupModal', this.showLeaveGroupModal);
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('showLeaveGroupModal', this.showLeaveGroupModal);
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)
...@@ -121,14 +149,23 @@ export default { ...@@ -121,14 +149,23 @@ export default {
parentGroup.isOpen = false; parentGroup.isOpen = false;
} }
}, },
leaveGroup(group, parentGroup) { showLeaveGroupModal(group, parentGroup) {
const targetGroup = group; this.targetGroup = group;
targetGroup.isBeingRemoved = true; this.targetParentGroup = parentGroup;
this.service.leaveGroup(targetGroup.leavePath) this.showModal = true;
this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`);
},
hideLeaveGroupModal() {
this.showModal = false;
},
leaveGroup() {
this.showModal = false;
this.targetGroup.isBeingRemoved = true;
this.service.leaveGroup(this.targetGroup.leavePath)
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then((res) => {
$.scrollTo(0); $.scrollTo(0);
this.store.removeGroup(targetGroup, parentGroup); this.store.removeGroup(this.targetGroup, this.targetParentGroup);
Flash(res.notice, 'notice'); Flash(res.notice, 'notice');
}) })
.catch((err) => { .catch((err) => {
...@@ -137,7 +174,7 @@ export default { ...@@ -137,7 +174,7 @@ export default {
message = COMMON_STR.LEAVE_FORBIDDEN; message = COMMON_STR.LEAVE_FORBIDDEN;
} }
Flash(message); Flash(message);
targetGroup.isBeingRemoved = false; this.targetGroup.isBeingRemoved = false;
}); });
}, },
updatePagination(headers) { updatePagination(headers) {
...@@ -152,26 +189,6 @@ export default { ...@@ -152,26 +189,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>
...@@ -190,5 +207,14 @@ export default { ...@@ -190,5 +207,14 @@ export default {
:search-empty-message="searchEmptyMessage" :search-empty-message="searchEmptyMessage"
:page-info="pageInfo" :page-info="pageInfo"
/> />
<modal
v-show="showModal"
:primary-button-label="__('Leave')"
kind="warning"
:title="__('Are you sure?')"
:text="groupLeaveConfirmationMessage"
@cancel="hideLeaveGroupModal"
@submit="leaveGroup"
/>
</div> </div>
</template> </template>
...@@ -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 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 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,
}, },
directives: { directives: {
tooltip, tooltip,
...@@ -25,11 +22,6 @@ export default { ...@@ -25,11 +22,6 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
modalStatus: false,
};
},
computed: { computed: {
leaveBtnTitle() { leaveBtnTitle() {
return COMMON_STR.LEAVE_BTN_TITLE; return COMMON_STR.LEAVE_BTN_TITLE;
...@@ -37,17 +29,10 @@ export default { ...@@ -37,17 +29,10 @@ export default {
editBtnTitle() { editBtnTitle() {
return COMMON_STR.EDIT_BTN_TITLE; return COMMON_STR.EDIT_BTN_TITLE;
}, },
leaveConfirmationMessage() {
return s__(`GroupsTree|Are you sure you want to leave the "${this.group.fullName}" group?`);
},
}, },
methods: { methods: {
onLeaveGroup() { onLeaveGroup() {
this.modalStatus = true; eventHub.$emit('showLeaveGroupModal', this.group, this.parentGroup);
},
leaveGroup() {
this.modalStatus = false;
eventHub.$emit('leaveGroup', this.group, this.parentGroup);
}, },
}, },
}; };
...@@ -78,14 +63,5 @@ export default { ...@@ -78,14 +63,5 @@ export default {
class="leave-group btn no-expand"> class="leave-group btn no-expand">
<icon name="leave"/> <icon name="leave"/>
</a> </a>
<modal
v-show="modalStatus"
:primary-button-label="__('Leave')"
kind="warning"
:title="__('Are you sure?')"
:text="__('Are you sure you want to leave this group?')"
:body="leaveConfirmationMessage"
@submit="leaveGroup"
/>
</div> </div>
</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>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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