From f46acfc7abd72342bbd07cd9e87b0d5a3740a8b6 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 25 Oct 2016 16:00:38 -0600 Subject: [PATCH 001/277] integrate vue components, use intance variables to pass data to vue --- app/assets/javascripts/pipeline.vue.js.es6 | 14 ++++++ app/assets/javascripts/pipelines.vue.js.es6 | 31 ++++++++++++ app/views/projects/pipelines/index.html.haml | 52 ++++++++++++++------ db/schema.rb | 10 ++-- 4 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 app/assets/javascripts/pipeline.vue.js.es6 create mode 100644 app/assets/javascripts/pipelines.vue.js.es6 diff --git a/app/assets/javascripts/pipeline.vue.js.es6 b/app/assets/javascripts/pipeline.vue.js.es6 new file mode 100644 index 00000000000..959d7be32fa --- /dev/null +++ b/app/assets/javascripts/pipeline.vue.js.es6 @@ -0,0 +1,14 @@ +//= require vue + +(gl => { + gl.VuePipeLine = Vue.extend({ + props: ['pipeline'], + template: ` +
+ + {{ pipeline.status }} + +
+ ` + }) +})(window.gl || (window.gl = {})) diff --git a/app/assets/javascripts/pipelines.vue.js.es6 b/app/assets/javascripts/pipelines.vue.js.es6 new file mode 100644 index 00000000000..8cf0558d94b --- /dev/null +++ b/app/assets/javascripts/pipelines.vue.js.es6 @@ -0,0 +1,31 @@ +//= require vue + +(gl => { + gl.VuePipeLines = Vue.extend({ + props: ['pipelines', 'count'], + template: ` +
+ + + + + + + + + + +
+ +
+ +
StatusPipelineCommitStages
+
+ `, + computed: { + pipes() { + return this.pipelines + } + } + }) +})(window.gl || (window.gl = {})) diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 4bc49072f35..8bd9afd05ce 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -36,21 +36,41 @@ = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint - %div.content-list.pipelines - - stages = @pipelines.stages - - if @pipelines.blank? - %div - .nothing-here-block No pipelines to show - - else - .table-holder - %table.table.ci-table - %thead - %th Status - %th Pipeline - %th Commit - %th Stages - %th - %th.hidden-xs - = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages + .app + -# %div.content-list.pipelines + -# - stages = @pipelines.stages + -# - if @pipelines.blank? + -# %div + -# .nothing-here-block No pipelines to show + -# - else + -# .table-holder + -# %table.table.ci-table + -# %thead + -# %th Status + -# %th Pipeline + -# %th Commit + -# %th Stages + -# %th + -# %th.hidden-xs + -# = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages = paginate @pipelines, theme: 'gitlab' + +:javascript + var VuePipeLines = gl.VuePipeLines + var VuePipeLine = gl.VuePipeLine + + Vue.component('vue-pipelines', VuePipeLines) + Vue.component('vue-pipeline', VuePipeLine) + + new Vue({ + el: ".app", + data:{ + pipelines: JSON.parse('#{@pipelines.to_json}'), + count: JSON.parse('#{@pipeline_count.to_json}') + }, + template: "" + + "
" + + "" + + "
" + }) diff --git a/db/schema.rb b/db/schema.rb index 02282b0f666..d4064948920 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -86,8 +86,8 @@ ActiveRecord::Schema.define(version: 20161024042317) do t.string "health_check_access_token" t.boolean "send_user_confirmation_email", default: false t.integer "container_registry_token_expire_delay", default: 5 - t.text "after_sign_up_text" t.boolean "user_default_external", default: false, null: false + t.text "after_sign_up_text" t.string "repository_storage", default: "default" t.string "enabled_git_access_protocol" t.boolean "domain_blacklist_enabled", default: false @@ -182,8 +182,8 @@ ActiveRecord::Schema.define(version: 20161024042317) do t.text "artifacts_metadata" t.integer "erased_by_id" t.datetime "erased_at" - t.datetime "artifacts_expire_at" t.string "environment" + t.datetime "artifacts_expire_at" t.integer "artifacts_size", limit: 8 t.string "when" t.text "yaml_variables" @@ -825,10 +825,10 @@ ActiveRecord::Schema.define(version: 20161024042317) do t.integer "user_id", null: false t.string "token", null: false t.string "name", null: false - t.boolean "revoked", default: false - t.datetime "expires_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.boolean "revoked", default: false + t.datetime "expires_at" end add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree @@ -899,8 +899,8 @@ ActiveRecord::Schema.define(version: 20161024042317) do t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false t.boolean "has_external_issue_tracker" t.string "repository_storage", default: "default", null: false - t.boolean "request_access_enabled", default: true, null: false t.boolean "has_external_wiki" + t.boolean "request_access_enabled", default: true, null: false t.boolean "lfs_enabled" t.text "description_html" end From 243717c0cc3172d50e4ca5c0be567bfd599b4d90 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 25 Oct 2016 16:42:55 -0600 Subject: [PATCH 002/277] conditional show --- app/assets/javascripts/pipelines.vue.js.es6 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/pipelines.vue.js.es6 b/app/assets/javascripts/pipelines.vue.js.es6 index 8cf0558d94b..e98c8816dfd 100644 --- a/app/assets/javascripts/pipelines.vue.js.es6 +++ b/app/assets/javascripts/pipelines.vue.js.es6 @@ -4,7 +4,12 @@ gl.VuePipeLines = Vue.extend({ props: ['pipelines', 'count'], template: ` -
+
+
+ No pipelines to show +
+
+
From d858c54879c61afde2df07e96e31c96b8a9f9586 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 26 Oct 2016 12:43:58 -0600 Subject: [PATCH 003/277] conform to eslint --- app/assets/javascripts/pipeline.vue.js.es6 | 10 ++++++---- app/assets/javascripts/pipelines.vue.js.es6 | 14 ++++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/pipeline.vue.js.es6 b/app/assets/javascripts/pipeline.vue.js.es6 index 959d7be32fa..7e9da71b6ab 100644 --- a/app/assets/javascripts/pipeline.vue.js.es6 +++ b/app/assets/javascripts/pipeline.vue.js.es6 @@ -1,6 +1,8 @@ //= require vue +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ -(gl => { +((gl) => { gl.VuePipeLine = Vue.extend({ props: ['pipeline'], template: ` @@ -9,6 +11,6 @@ {{ pipeline.status }} - ` - }) -})(window.gl || (window.gl = {})) + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/pipelines.vue.js.es6 b/app/assets/javascripts/pipelines.vue.js.es6 index e98c8816dfd..50ecf28394d 100644 --- a/app/assets/javascripts/pipelines.vue.js.es6 +++ b/app/assets/javascripts/pipelines.vue.js.es6 @@ -1,6 +1,8 @@ //= require vue +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ -(gl => { +((gl) => { gl.VuePipeLines = Vue.extend({ props: ['pipelines', 'count'], template: ` @@ -29,8 +31,8 @@ `, computed: { pipes() { - return this.pipelines - } - } - }) -})(window.gl || (window.gl = {})) + return this.pipelines; + }, + }, + }); +})(window.gl || (window.gl = {})); From e1ad87c70d10c6a329586c8553442e69e258fff2 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 26 Oct 2016 12:47:35 -0600 Subject: [PATCH 004/277] follow eslint in HAML file as well --- app/views/projects/pipelines/index.html.haml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 8bd9afd05ce..c6f0ab87f8d 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -57,20 +57,17 @@ = paginate @pipelines, theme: 'gitlab' :javascript - var VuePipeLines = gl.VuePipeLines - var VuePipeLine = gl.VuePipeLine - - Vue.component('vue-pipelines', VuePipeLines) - Vue.component('vue-pipeline', VuePipeLine) + Vue.component('vue-pipelines', gl.VuePipeLines); + Vue.component('vue-pipeline', gl.VuePipeLine); new Vue({ el: ".app", data:{ pipelines: JSON.parse('#{@pipelines.to_json}'), - count: JSON.parse('#{@pipeline_count.to_json}') + count: JSON.parse('#{@pipeline_count.to_json}'), }, template: "" + "
" + "" - + "
" + + "", }) From 50b4c2dc0f2844c7017a352a1df0b919f2bcc51d Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 27 Oct 2016 15:53:29 -0600 Subject: [PATCH 005/277] getting somewhere - now need to fix loop --- app/assets/javascripts/pipelines.vue.js.es6 | 182 +++++++++++++++++-- app/views/projects/pipelines/index.html.haml | 26 +-- 2 files changed, 172 insertions(+), 36 deletions(-) diff --git a/app/assets/javascripts/pipelines.vue.js.es6 b/app/assets/javascripts/pipelines.vue.js.es6 index 50ecf28394d..cf3f7a729eb 100644 --- a/app/assets/javascripts/pipelines.vue.js.es6 +++ b/app/assets/javascripts/pipelines.vue.js.es6 @@ -6,28 +6,170 @@ gl.VuePipeLines = Vue.extend({ props: ['pipelines', 'count'], template: ` -
-
- No pipelines to show -
-
-
-
Status
- - - - - - - - - -
- +
+ + + + + + + + +
+
+ + + + + + -
StatusPipelineCommitStages
StatusPipelineCommitStages
+ #30 + by + API + +
+ +
+ master +
+ + + +
+ 29141ed3 +

+ + fix broken repo 500 errors in UI and added relevant specs +

+
+ + + + + + + + + +
-
+ + `, computed: { pipes() { diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index c6f0ab87f8d..085bef11338 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -36,22 +36,16 @@ = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint - .app - -# %div.content-list.pipelines - -# - stages = @pipelines.stages - -# - if @pipelines.blank? - -# %div - -# .nothing-here-block No pipelines to show - -# - else - -# .table-holder - -# %table.table.ci-table - -# %thead - -# %th Status - -# %th Pipeline - -# %th Commit - -# %th Stages - -# %th - -# %th.hidden-xs + %div.content-list.pipelines + - stages = @pipelines.stages + - if @pipelines.blank? + %div + .nothing-here-block No pipelines to show + - else + .table-holder + %table.table.ci-table + %thead + %tbody.app -# = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages = paginate @pipelines, theme: 'gitlab' From 02c2ef89e9bb5f86e1fd1c3f188befea480908c1 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 27 Oct 2016 16:18:55 -0600 Subject: [PATCH 006/277] fixed loop - move on to make components --- app/assets/javascripts/pipelines.vue.js.es6 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/javascripts/pipelines.vue.js.es6 b/app/assets/javascripts/pipelines.vue.js.es6 index cf3f7a729eb..4d6b00553cf 100644 --- a/app/assets/javascripts/pipelines.vue.js.es6 +++ b/app/assets/javascripts/pipelines.vue.js.es6 @@ -14,8 +14,7 @@ - -
+ @@ -168,7 +167,6 @@
- `, computed: { From 89823100b7cf37944ab8061a68e08afd46dcfc59 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 27 Oct 2016 16:24:33 -0600 Subject: [PATCH 007/277] formatting --- app/assets/javascripts/pipelines.vue.js.es6 | 314 ++++++++++---------- 1 file changed, 158 insertions(+), 156 deletions(-) diff --git a/app/assets/javascripts/pipelines.vue.js.es6 b/app/assets/javascripts/pipelines.vue.js.es6 index 4d6b00553cf..e20bdf8e992 100644 --- a/app/assets/javascripts/pipelines.vue.js.es6 +++ b/app/assets/javascripts/pipelines.vue.js.es6 @@ -7,166 +7,168 @@ props: ['pipelines', 'count'], template: ` - Status - Pipeline - Commit - Stages - - - + + Status + Pipeline + Commit + Stages + + + + - - - - - - - - - -  failed - - - - - #30 - by - API - - -
- -
- master -
- - - + + + + + + + + + +  {{pipeline.status}} + + + + + #30 + by + API + + +
+
- 29141ed3 -

- - fix broken repo 500 errors in UI and added relevant specs -

- - - - - - - - - - - - - - -
@@ -173,6 +173,7 @@ `, computed: { pipes() { + console.log(this.pipelines); return this.pipelines; }, }, From dc476fe6ff1c55a992fce8483b9f8a4762e302fa Mon Sep 17 00:00:00 2001 From: Regis Date: Sat, 29 Oct 2016 13:50:08 -0600 Subject: [PATCH 009/277] stashing --- 1p | 0 app/assets/javascripts/pipeline.vue.js.es6 | 16 ----- app/assets/javascripts/pipelines.vue.js.es6 | 59 ++++++++++--------- .../javascripts/runnerstats/index.js.es6 | 56 ++++++++++++++++++ app/assets/javascripts/status_svg.vue.js.es6 | 18 ++++++ app/views/projects/pipelines/index.html.haml | 6 +- 6 files changed, 107 insertions(+), 48 deletions(-) create mode 100644 1p delete mode 100644 app/assets/javascripts/pipeline.vue.js.es6 create mode 100644 app/assets/javascripts/runnerstats/index.js.es6 create mode 100644 app/assets/javascripts/status_svg.vue.js.es6 diff --git a/1p b/1p new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/assets/javascripts/pipeline.vue.js.es6 b/app/assets/javascripts/pipeline.vue.js.es6 deleted file mode 100644 index 7e9da71b6ab..00000000000 --- a/app/assets/javascripts/pipeline.vue.js.es6 +++ /dev/null @@ -1,16 +0,0 @@ -//= require vue -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VuePipeLine = Vue.extend({ - props: ['pipeline'], - template: ` -
- - {{ pipeline.status }} - -
- `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/pipelines.vue.js.es6 b/app/assets/javascripts/pipelines.vue.js.es6 index 26a3bc2d65f..1bd30feaf9e 100644 --- a/app/assets/javascripts/pipelines.vue.js.es6 +++ b/app/assets/javascripts/pipelines.vue.js.es6 @@ -1,9 +1,15 @@ //= require vue +//= require_tree ./runnerstats /* global Vue, gl */ /* eslint-disable no-param-reassign */ ((gl) => { gl.VuePipeLines = Vue.extend({ + data() { + return { + runnerStats: new gl.RunnerStats(), + }; + }, props: ['pipelines', 'count'], template: ` @@ -19,21 +25,19 @@ - - - - - - - -  {{pipeline.status}} - + + + + + - #{{pipeline.id}} + #{{pipeline.id}} by - API + {{pipeline.user}} + + + + `, - computed: { - pipes() { - console.log(this.pipelines); - return this.pipelines; - }, - }, }); })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/runnerstats/index.js.es6 b/app/assets/javascripts/runnerstats/index.js.es6 new file mode 100644 index 00000000000..35f95f3a811 --- /dev/null +++ b/app/assets/javascripts/runnerstats/index.js.es6 @@ -0,0 +1,56 @@ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.RunnerStats = class { + constructor() { + return { + running: { + text: 'pending', + color: '#E75E40', + pathOne: 'M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z', + pathTwo: 'M4.69999981,5.30065012 C4.69999981,5.13460564 4.83842754,5 5.00354719,5 L5.89645243,5 C6.06409702,5 6.19999981,5.13308716 6.19999981,5.30065012 L6.19999981,8.69934988 C6.19999981,8.86539436 6.06157207,9 5.89645243,9 L5.00354719,9 C4.8359026,9 4.69999981,8.86691284 4.69999981,8.69934988 L4.69999981,5.30065012 Z M7.69999981,5.30065012 C7.69999981,5.13460564 7.83842754,5 8.00354719,5 L8.89645243,5 C9.06409702,5 9.19999981,5.13308716 9.19999981,5.30065012 L9.19999981,8.69934988 C9.19999981,8.86539436 9.06157207,9 8.89645243,9 L8.00354719,9 C7.8359026,9 7.69999981,8.86691284 7.69999981,8.69934988 L7.69999981,5.30065012 Z', + }, + passed: ` + + + + + + + + passed + `, + failed: ` + + + + + + +  failed + `, + created: ` +  created + `, + skipped: ` + + + + + + +  skipped + `, + pending: ` + + + + + + +  pending + `, + }; + } + }; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/status_svg.vue.js.es6 b/app/assets/javascripts/status_svg.vue.js.es6 new file mode 100644 index 00000000000..0af35ba6f3a --- /dev/null +++ b/app/assets/javascripts/status_svg.vue.js.es6 @@ -0,0 +1,18 @@ +//= require vue +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueRunnerStatus = Vue.extend({ + props: ['status'], + template: ` + + + + + + +  {{status.text}} + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 085bef11338..30625e399b0 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -44,15 +44,11 @@ - else .table-holder %table.table.ci-table - %thead %tbody.app - -# = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages - - = paginate @pipelines, theme: 'gitlab' :javascript Vue.component('vue-pipelines', gl.VuePipeLines); - Vue.component('vue-pipeline', gl.VuePipeLine); + Vue.component('vue-runner-status', gl.VueRunnerStatus); new Vue({ el: ".app", From 66727f74b806a978c4bf76a8e9bd4b86c6443651 Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 30 Oct 2016 01:28:57 -0600 Subject: [PATCH 010/277] change architecture - add store - make api call by default --- app/assets/javascripts/status_svg.vue.js.es6 | 18 ------- .../vue_pipelines_index/index.js.es6 | 7 +++ .../pipelines.vue.js.es6 | 54 +++++++++++-------- .../status_data_icons.js.es6} | 2 +- .../vue_pipelines_index/store.js.es6 | 15 ++++++ .../vue_commit_link.vue.js.es6 | 50 +++++++++++++++++ .../javascripts/vue_pipelines_loader.js.es6 | 1 + app/views/projects/pipelines/index.html.haml | 15 +++--- 8 files changed, 116 insertions(+), 46 deletions(-) delete mode 100644 app/assets/javascripts/status_svg.vue.js.es6 create mode 100644 app/assets/javascripts/vue_pipelines_index/index.js.es6 rename app/assets/javascripts/{ => vue_pipelines_index}/pipelines.vue.js.es6 (87%) rename app/assets/javascripts/{runnerstats/index.js.es6 => vue_pipelines_index/status_data_icons.js.es6} (99%) create mode 100644 app/assets/javascripts/vue_pipelines_index/store.js.es6 create mode 100644 app/assets/javascripts/vue_pipelines_index/vue_commit_link.vue.js.es6 create mode 100644 app/assets/javascripts/vue_pipelines_loader.js.es6 diff --git a/app/assets/javascripts/status_svg.vue.js.es6 b/app/assets/javascripts/status_svg.vue.js.es6 deleted file mode 100644 index 0af35ba6f3a..00000000000 --- a/app/assets/javascripts/status_svg.vue.js.es6 +++ /dev/null @@ -1,18 +0,0 @@ -//= require vue -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueRunnerStatus = Vue.extend({ - props: ['status'], - template: ` - - - - - - -  {{status.text}} - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 new file mode 100644 index 00000000000..3657d9c5b0d --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -0,0 +1,7 @@ +//= require vue +//= require vue-resource + +//= require ./store.js.es6 +//= require ./status_data_icons.js.es6 +//= require ./vue_commit_link.vue.js.es6 +//= require ./pipelines.vue.js.es6 diff --git a/app/assets/javascripts/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 similarity index 87% rename from app/assets/javascripts/pipelines.vue.js.es6 rename to app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index 1bd30feaf9e..424b29ae527 100644 --- a/app/assets/javascripts/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -1,5 +1,3 @@ -//= require vue -//= require_tree ./runnerstats /* global Vue, gl */ /* eslint-disable no-param-reassign */ @@ -7,10 +5,20 @@ gl.VuePipeLines = Vue.extend({ data() { return { + endpoint: `/api/v3/projects/${this.scopeId}/pipelines`, + pipelines: [], runnerStats: new gl.RunnerStats(), }; }, - props: ['pipelines', 'count'], + props: ['scope', 'store'], + created() { + this.store.fetchData.call(this, Vue); + }, + methods: { + shortSha(pipeline) { + return pipeline.sha.slice(0, 8); + }, + }, template: ` @@ -25,17 +33,21 @@ - - - - - + +
+ + running + +
+
+ + passed + +
- #{{pipeline.id}} + #{{pipeline.id}} by {{pipeline.user}} @@ -49,7 +61,7 @@ - 29141ed3 + {{shortSha(pipeline)}}

fix broken repo 500 errors in UI and added relevant specs @@ -57,7 +69,7 @@

diff --git a/app/assets/javascripts/runnerstats/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/status_data_icons.js.es6 similarity index 99% rename from app/assets/javascripts/runnerstats/index.js.es6 rename to app/assets/javascripts/vue_pipelines_index/status_data_icons.js.es6 index 35f95f3a811..b4e0c07a665 100644 --- a/app/assets/javascripts/runnerstats/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/status_data_icons.js.es6 @@ -5,7 +5,7 @@ constructor() { return { running: { - text: 'pending', + text: 'running', color: '#E75E40', pathOne: 'M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z', pathTwo: 'M4.69999981,5.30065012 C4.69999981,5.13460564 4.83842754,5 5.00354719,5 L5.89645243,5 C6.06409702,5 6.19999981,5.13308716 6.19999981,5.30065012 L6.19999981,8.69934988 C6.19999981,8.86539436 6.06157207,9 5.89645243,9 L5.00354719,9 C4.8359026,9 4.69999981,8.86691284 4.69999981,8.69934988 L4.69999981,5.30065012 Z M7.69999981,5.30065012 C7.69999981,5.13460564 7.83842754,5 8.00354719,5 L8.89645243,5 C9.06409702,5 9.19999981,5.13308716 9.19999981,5.30065012 L9.19999981,8.69934988 C9.19999981,8.86539436 9.06157207,9 8.89645243,9 L8.00354719,9 C7.8359026,9 7.69999981,8.86691284 7.69999981,8.69934988 L7.69999981,5.30065012 Z', diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 new file mode 100644 index 00000000000..3f03bb31068 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -0,0 +1,15 @@ +/* global gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.PipelineStore = class { + fetchData(Vue) { + this.$http.get(`/api/v3/projects/${this.scope}/pipelines`) + .then((response) => { + Vue.set(this, 'pipelines', JSON.parse(response.body)); + }, () => { + Vue.set(this, 'pipelines', []); + }); + } + }; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/vue_commit_link.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/vue_commit_link.vue.js.es6 new file mode 100644 index 00000000000..9e2ef8993a2 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/vue_commit_link.vue.js.es6 @@ -0,0 +1,50 @@ + +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueCommitLink = Vue.extend({ + props: ['pipeline'], + template: ` + + +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_loader.js.es6 b/app/assets/javascripts/vue_pipelines_loader.js.es6 new file mode 100644 index 00000000000..1b8ba72b697 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_loader.js.es6 @@ -0,0 +1 @@ +//= require_tree ./vue_pipelines_index diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 30625e399b0..7cb77f2034b 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -47,17 +47,20 @@ %tbody.app :javascript - Vue.component('vue-pipelines', gl.VuePipeLines); - Vue.component('vue-runner-status', gl.VueRunnerStatus); + Vue.use(VueResource); - new Vue({ + var vm = new Vue({ el: ".app", data:{ - pipelines: JSON.parse('#{@pipelines.to_json}'), - count: JSON.parse('#{@pipeline_count.to_json}'), + scope: "#{@project.id}", + store: new gl.PipelineStore(), + }, + components: { + 'vue-commit-link': gl.VueCommitLink, + 'vue-pipelines': gl.VuePipeLines, }, template: "" + "
" - + "" + + "" + "
", }) From 8658c9788370f1d7ab63270e3dcac423c27d38c5 Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 30 Oct 2016 01:31:59 -0600 Subject: [PATCH 011/277] remove uneeded endpoint data key --- app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index 424b29ae527..2847fa024d9 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -5,7 +5,6 @@ gl.VuePipeLines = Vue.extend({ data() { return { - endpoint: `/api/v3/projects/${this.scopeId}/pipelines`, pipelines: [], runnerStats: new gl.RunnerStats(), }; From 308e0eb554cbd271cbcbc13e8f795ae08c2af62c Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 30 Oct 2016 01:45:55 -0600 Subject: [PATCH 012/277] have polling working --- .../javascripts/vue_pipelines_index/store.js.es6 | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 3f03bb31068..aca603cf756 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -4,12 +4,17 @@ ((gl) => { gl.PipelineStore = class { fetchData(Vue) { - this.$http.get(`/api/v3/projects/${this.scope}/pipelines`) - .then((response) => { - Vue.set(this, 'pipelines', JSON.parse(response.body)); + const goFetch = vue => + this.$http.get(`/api/v3/projects/${this.scope}/pipelines`) + .then((response) => { + vue.set(this, 'pipelines', JSON.parse(response.body)); }, () => { - Vue.set(this, 'pipelines', []); + vue.set(this, 'pipelines', []); }); + + goFetch(Vue); + + setInterval(() => { console.log('DID IT'); goFetch(Vue) }, 3000); } }; })(window.gl || (window.gl = {})); From b4d8cb0cf031d6817952709ab90d27d9f23c04a5 Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 30 Oct 2016 01:48:26 -0600 Subject: [PATCH 013/277] changed fetchData to fetchDataLoop - ensured '#created' is part of Vue1 and Vue2 --- app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 | 2 +- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index 2847fa024d9..8036febd593 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -11,7 +11,7 @@ }, props: ['scope', 'store'], created() { - this.store.fetchData.call(this, Vue); + this.store.fetchDataLoop.call(this, Vue); }, methods: { shortSha(pipeline) { diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index aca603cf756..6abd160f9a4 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -3,7 +3,7 @@ ((gl) => { gl.PipelineStore = class { - fetchData(Vue) { + fetchDataLoop(Vue) { const goFetch = vue => this.$http.get(`/api/v3/projects/${this.scope}/pipelines`) .then((response) => { From ae90118e1dfaaf6db890b4bfa00bc45cf287fa21 Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 30 Oct 2016 01:50:11 -0600 Subject: [PATCH 014/277] formatting - eslint --- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 6abd160f9a4..7479caf107d 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -14,7 +14,10 @@ goFetch(Vue); - setInterval(() => { console.log('DID IT'); goFetch(Vue) }, 3000); + setInterval(() => { + console.log('DID IT'); + goFetch(Vue); + }, 3000); } }; })(window.gl || (window.gl = {})); From 384ea58f72d73c858769b22c81e5345e9e3e09bc Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 30 Oct 2016 14:46:31 -0600 Subject: [PATCH 015/277] change to more component like structure for reusable components --- .../vue_pipelines_index/index.js.es6 | 3 +- .../vue_pipelines_index/pipelines.vue.js.es6 | 31 ++++++------ .../vue_pipelines_index/running.vue.js.es6 | 25 ++++++++++ .../running_icon.vue.js.es6 | 13 +++++ .../vue_pipelines_index/store.js.es6 | 1 - .../vue_commit_link.vue.js.es6 | 50 ------------------- app/views/projects/pipelines/index.html.haml | 1 - 7 files changed, 54 insertions(+), 70 deletions(-) create mode 100644 app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 create mode 100644 app/assets/javascripts/vue_pipelines_index/running_icon.vue.js.es6 delete mode 100644 app/assets/javascripts/vue_pipelines_index/vue_commit_link.vue.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 3657d9c5b0d..36661553444 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -2,6 +2,7 @@ //= require vue-resource //= require ./store.js.es6 +//= require ./running_icon.vue.js.es6 +//= require ./running.vue.js.es6 //= require ./status_data_icons.js.es6 -//= require ./vue_commit_link.vue.js.es6 //= require ./pipelines.vue.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index 8036febd593..ba6ef3011ab 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -3,13 +3,19 @@ ((gl) => { gl.VuePipeLines = Vue.extend({ + components: { + 'vue-running-pipeline': gl.VueRunningPipeline, + }, data() { return { pipelines: [], runnerStats: new gl.RunnerStats(), }; }, - props: ['scope', 'store'], + props: [ + 'scope', + 'store', + ], created() { this.store.fetchDataLoop.call(this, Vue); }, @@ -31,24 +37,15 @@ - - -
- - running - -
-
- - passed - -
-
+ + - #{{pipeline.id}} - by - {{pipeline.user}} + + #{{pipeline.id}} + + by + {{pipeline.user}}
diff --git a/app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 new file mode 100644 index 00000000000..5edf8403bd9 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 @@ -0,0 +1,25 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueRunningPipeline = Vue.extend({ + components: { + 'vue-running-icon': gl.VueRunningIcon, + }, + props: [ + 'pipe', + ], + template: ` + + + + + + +  running + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/running_icon.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/running_icon.vue.js.es6 new file mode 100644 index 00000000000..c37f213525c --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/running_icon.vue.js.es6 @@ -0,0 +1,13 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueRunningIcon = Vue.extend({ + template: ` + + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 7479caf107d..38797f85f0a 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -13,7 +13,6 @@ }); goFetch(Vue); - setInterval(() => { console.log('DID IT'); goFetch(Vue); diff --git a/app/assets/javascripts/vue_pipelines_index/vue_commit_link.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/vue_commit_link.vue.js.es6 deleted file mode 100644 index 9e2ef8993a2..00000000000 --- a/app/assets/javascripts/vue_pipelines_index/vue_commit_link.vue.js.es6 +++ /dev/null @@ -1,50 +0,0 @@ - -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueCommitLink = Vue.extend({ - props: ['pipeline'], - template: ` - - -
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 7cb77f2034b..6309de51409 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -56,7 +56,6 @@ store: new gl.PipelineStore(), }, components: { - 'vue-commit-link': gl.VueCommitLink, 'vue-pipelines': gl.VuePipeLines, }, template: "" From 1bed34445eca330682929d0af801624ff59ddf40 Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 30 Oct 2016 15:13:16 -0600 Subject: [PATCH 016/277] trying to fix schema file --- db/schema.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index f44711096c8..a036bd8222e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -86,8 +86,8 @@ ActiveRecord::Schema.define(version: 20161025231710) do t.string "health_check_access_token" t.boolean "send_user_confirmation_email", default: false t.integer "container_registry_token_expire_delay", default: 5 - t.boolean "user_default_external", default: false, null: false t.text "after_sign_up_text" + t.boolean "user_default_external", default: false, null: false t.string "repository_storage", default: "default" t.string "enabled_git_access_protocol" t.boolean "domain_blacklist_enabled", default: false @@ -182,8 +182,8 @@ ActiveRecord::Schema.define(version: 20161025231710) do t.text "artifacts_metadata" t.integer "erased_by_id" t.datetime "erased_at" - t.string "environment" t.datetime "artifacts_expire_at" + t.string "environment" t.integer "artifacts_size", limit: 8 t.string "when" t.text "yaml_variables" @@ -827,10 +827,10 @@ ActiveRecord::Schema.define(version: 20161025231710) do t.integer "user_id", null: false t.string "token", null: false t.string "name", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.boolean "revoked", default: false t.datetime "expires_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree @@ -901,8 +901,8 @@ ActiveRecord::Schema.define(version: 20161025231710) do t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false t.boolean "has_external_issue_tracker" t.string "repository_storage", default: "default", null: false - t.boolean "has_external_wiki" t.boolean "request_access_enabled", default: true, null: false + t.boolean "has_external_wiki" t.boolean "lfs_enabled" t.text "description_html" end @@ -1239,4 +1239,4 @@ ActiveRecord::Schema.define(version: 20161025231710) do add_foreign_key "protected_branch_push_access_levels", "protected_branches" add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "u2f_registrations", "users" -end +end \ No newline at end of file From c373706f2f188672ba24b2a545b6a50e374dd316 Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 30 Oct 2016 15:16:42 -0600 Subject: [PATCH 017/277] remove singleton in favor of components --- .../status_data_icons.js.es6 | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 app/assets/javascripts/vue_pipelines_index/status_data_icons.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/status_data_icons.js.es6 b/app/assets/javascripts/vue_pipelines_index/status_data_icons.js.es6 deleted file mode 100644 index b4e0c07a665..00000000000 --- a/app/assets/javascripts/vue_pipelines_index/status_data_icons.js.es6 +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.RunnerStats = class { - constructor() { - return { - running: { - text: 'running', - color: '#E75E40', - pathOne: 'M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z', - pathTwo: 'M4.69999981,5.30065012 C4.69999981,5.13460564 4.83842754,5 5.00354719,5 L5.89645243,5 C6.06409702,5 6.19999981,5.13308716 6.19999981,5.30065012 L6.19999981,8.69934988 C6.19999981,8.86539436 6.06157207,9 5.89645243,9 L5.00354719,9 C4.8359026,9 4.69999981,8.86691284 4.69999981,8.69934988 L4.69999981,5.30065012 Z M7.69999981,5.30065012 C7.69999981,5.13460564 7.83842754,5 8.00354719,5 L8.89645243,5 C9.06409702,5 9.19999981,5.13308716 9.19999981,5.30065012 L9.19999981,8.69934988 C9.19999981,8.86539436 9.06157207,9 8.89645243,9 L8.00354719,9 C7.8359026,9 7.69999981,8.86691284 7.69999981,8.69934988 L7.69999981,5.30065012 Z', - }, - passed: ` - - - - - - - - passed - `, - failed: ` - - - - - - -  failed - `, - created: ` -  created - `, - skipped: ` - - - - - - -  skipped - `, - pending: ` - - - - - - -  pending - `, - }; - } - }; -})(window.gl || (window.gl = {})); From 23b9a99fccd956a3bdc5adbd299b277fdd727d9b Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 30 Oct 2016 15:23:13 -0600 Subject: [PATCH 018/277] will discuss what to do on error at a later time --- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 38797f85f0a..f1cd991994d 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -9,7 +9,7 @@ .then((response) => { vue.set(this, 'pipelines', JSON.parse(response.body)); }, () => { - vue.set(this, 'pipelines', []); + console.error('API Error for Pipelines'); }); goFetch(Vue); From 1439b9b8d688e5650be565bd6ffe890ba4393c82 Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 30 Oct 2016 22:11:57 -0600 Subject: [PATCH 019/277] can grab commits prior to pipelines - might need more custom API --- .../vue_pipelines_index/index.js.es6 | 1 - .../vue_pipelines_index/pipelines.vue.js.es6 | 27 ++++++++++++++++--- .../vue_pipelines_index/store.js.es6 | 21 +++++++++++++-- app/views/projects/pipelines/index.html.haml | 1 + 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 36661553444..fcf91107da5 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -4,5 +4,4 @@ //= require ./store.js.es6 //= require ./running_icon.vue.js.es6 //= require ./running.vue.js.es6 -//= require ./status_data_icons.js.es6 //= require ./pipelines.vue.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index ba6ef3011ab..1ff7b117223 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -9,7 +9,7 @@ data() { return { pipelines: [], - runnerStats: new gl.RunnerStats(), + commits: [], }; }, props: [ @@ -17,6 +17,7 @@ 'store', ], created() { + this.store.fetchCommits.call(this, Vue); this.store.fetchDataLoop.call(this, Vue); }, methods: { @@ -57,10 +58,28 @@
- {{shortSha(pipeline)}} + {{shortSha(pipeline)}} +

- - fix broken repo 500 errors in UI and added relevant specs + + + + + fix broken repo 500 errors in UI and added relevant specs +

diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index f1cd991994d..f9e802714df 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -12,11 +12,28 @@ console.error('API Error for Pipelines'); }); - goFetch(Vue); setInterval(() => { console.log('DID IT'); goFetch(Vue); - }, 3000); + }, 30000); + } + + fetchCommits(vue) { + const goFetch = vueSet => + this.$http.get(`/api/v3/projects/${this.scope}/pipelines`) + .then((response) => { + vueSet.set(this, 'pipelines', JSON.parse(response.body)); + }, () => { + console.error('API Error for Pipelines'); + }); + + this.$http.get(`/api/v3/projects/${this.scope}/repository/commits`) + .then((response) => { + vue.set(this, 'commits', JSON.parse(response.body)); + }, () => { + console.error('API Error for Pipelines'); + }) + .then(() => goFetch(vue)); } }; })(window.gl || (window.gl = {})); diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 6309de51409..718e6b73c93 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -53,6 +53,7 @@ el: ".app", data:{ scope: "#{@project.id}", + commits: "#{@commit}", store: new gl.PipelineStore(), }, components: { From 5ed18942685d70d556f035d6eb00d374cf0a7606 Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 30 Oct 2016 22:14:15 -0600 Subject: [PATCH 020/277] remove non existant commit value --- app/views/projects/pipelines/index.html.haml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 718e6b73c93..6309de51409 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -53,7 +53,6 @@ el: ".app", data:{ scope: "#{@project.id}", - commits: "#{@commit}", store: new gl.PipelineStore(), }, components: { From 2caf62d1a22d39f312c7306c408e40120ec362db Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 30 Oct 2016 22:22:08 -0600 Subject: [PATCH 021/277] refactor --- .../vue_pipelines_index/store.js.es6 | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index f9e802714df..046ec342478 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -2,38 +2,30 @@ /* eslint-disable no-param-reassign */ ((gl) => { + const goFetch = (that, vue) => + that.$http.get(`/api/v3/projects/${that.scope}/pipelines`) + .then((response) => { + vue.set(that, 'pipelines', JSON.parse(response.body)); + }, () => { + console.error('API Error for Pipelines'); + }); + gl.PipelineStore = class { fetchDataLoop(Vue) { - const goFetch = vue => - this.$http.get(`/api/v3/projects/${this.scope}/pipelines`) - .then((response) => { - vue.set(this, 'pipelines', JSON.parse(response.body)); - }, () => { - console.error('API Error for Pipelines'); - }); - setInterval(() => { console.log('DID IT'); - goFetch(Vue); + goFetch(this, Vue); }, 30000); } fetchCommits(vue) { - const goFetch = vueSet => - this.$http.get(`/api/v3/projects/${this.scope}/pipelines`) - .then((response) => { - vueSet.set(this, 'pipelines', JSON.parse(response.body)); - }, () => { - console.error('API Error for Pipelines'); - }); - this.$http.get(`/api/v3/projects/${this.scope}/repository/commits`) .then((response) => { vue.set(this, 'commits', JSON.parse(response.body)); }, () => { console.error('API Error for Pipelines'); }) - .then(() => goFetch(vue)); + .then(() => goFetch(this, vue)); } }; })(window.gl || (window.gl = {})); From cbd1303bec800dbacf2769ffb4f7ec63f4035eaf Mon Sep 17 00:00:00 2001 From: selfup Date: Mon, 31 Oct 2016 00:55:10 -0600 Subject: [PATCH 022/277] relative sha paths for commits --- .../javascripts/vue_pipelines_index/pipelines.vue.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index 1ff7b117223..7bfd49344ca 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -60,7 +60,7 @@ {{shortSha(pipeline)}} + href="./commit/{{pipeline.sha}}">{{shortSha(pipeline)}}

fix broken repo 500 errors in UI and added relevant specs From c32c77a095a4fb5cebb6db47fa031e1f8752031e Mon Sep 17 00:00:00 2001 From: selfup Date: Mon, 31 Oct 2016 01:15:29 -0600 Subject: [PATCH 023/277] pagination exists - will become dynamic soon --- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 046ec342478..6a909f74e53 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -3,7 +3,7 @@ ((gl) => { const goFetch = (that, vue) => - that.$http.get(`/api/v3/projects/${that.scope}/pipelines`) + that.$http.get(`/api/v3/projects/${that.scope}/pipelines?per_page=5&page=1`) .then((response) => { vue.set(that, 'pipelines', JSON.parse(response.body)); }, () => { @@ -19,7 +19,7 @@ } fetchCommits(vue) { - this.$http.get(`/api/v3/projects/${this.scope}/repository/commits`) + this.$http.get(`/api/v3/projects/${this.scope}/repository/commits?per_page=5&page=1`) .then((response) => { vue.set(this, 'commits', JSON.parse(response.body)); }, () => { From a42140d08fea03acf406c64db5de67beee4b8107 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 31 Oct 2016 13:02:34 -0600 Subject: [PATCH 024/277] page specific js - remove inline js - refactor --- .../vue_pipelines_index/index.js.es6 | 23 +++++++++++++++++++ .../vue_pipelines_index/pipelines.vue.js.es6 | 8 +++++++ .../vue_pipelines_index/store.js.es6 | 13 ++++++++--- .../javascripts/vue_pipelines_loader.js.es6 | 1 - app/views/projects/pipelines/index.html.haml | 22 +++--------------- config/application.rb | 1 + 6 files changed, 45 insertions(+), 23 deletions(-) delete mode 100644 app/assets/javascripts/vue_pipelines_loader.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index fcf91107da5..4b69c4d4c7d 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -1,3 +1,5 @@ +/* global Vue, VueResource, gl */ + //= require vue //= require vue-resource @@ -5,3 +7,24 @@ //= require ./running_icon.vue.js.es6 //= require ./running.vue.js.es6 //= require ./pipelines.vue.js.es6 + +(() => { + const project = document.querySelector('.table-holder'); + + Vue.use(VueResource); + + new Vue({ + el: '.vue-pipelines-index', + data: { + scope: project.dataset.projectId, + store: new gl.PipelineStore(), + }, + components: { + 'vue-pipelines': gl.VuePipeLines, + }, + template: '' + + '

' + + "" + + '
', + }); +})(); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index 7bfd49344ca..021a5ca8743 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -10,6 +10,8 @@ return { pipelines: [], commits: [], + currentPage: '', + intervalId: '', }; }, props: [ @@ -24,6 +26,11 @@ shortSha(pipeline) { return pipeline.sha.slice(0, 8); }, + changePage() { + // clearInterval(this.intervalId); + // this.store.fetchCommits.call(this, Vue); + // this.store.fetchDataLoop.call(this, Vue); + }, }, template: ` @@ -52,6 +59,7 @@
+ master
diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 6a909f74e53..bdba9575013 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -2,8 +2,12 @@ /* eslint-disable no-param-reassign */ ((gl) => { + const api = '/api/v3/projects'; + const goFetch = (that, vue) => - that.$http.get(`/api/v3/projects/${that.scope}/pipelines?per_page=5&page=1`) + that.$http.get( + `${api}/${that.scope}/pipelines?per_page=5&page=1` + ) .then((response) => { vue.set(that, 'pipelines', JSON.parse(response.body)); }, () => { @@ -12,14 +16,17 @@ gl.PipelineStore = class { fetchDataLoop(Vue) { - setInterval(() => { + // eventually clearInterval(this.intervalId) + this.intervalId = setInterval(() => { console.log('DID IT'); goFetch(this, Vue); }, 30000); } fetchCommits(vue) { - this.$http.get(`/api/v3/projects/${this.scope}/repository/commits?per_page=5&page=1`) + this.$http.get( + `${api}/${this.scope}/repository/commits?per_page=5&page=1` + ) .then((response) => { vue.set(this, 'commits', JSON.parse(response.body)); }, () => { diff --git a/app/assets/javascripts/vue_pipelines_loader.js.es6 b/app/assets/javascripts/vue_pipelines_loader.js.es6 deleted file mode 100644 index 1b8ba72b697..00000000000 --- a/app/assets/javascripts/vue_pipelines_loader.js.es6 +++ /dev/null @@ -1 +0,0 @@ -//= require_tree ./vue_pipelines_index diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 6309de51409..d7aebe584f8 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -42,24 +42,8 @@ %div .nothing-here-block No pipelines to show - else - .table-holder + .table-holder{"data-project-id": "#{@project.id}"} %table.table.ci-table - %tbody.app + %tbody.vue-pipelines-index -:javascript - Vue.use(VueResource); - - var vm = new Vue({ - el: ".app", - data:{ - scope: "#{@project.id}", - store: new gl.PipelineStore(), - }, - components: { - 'vue-pipelines': gl.VuePipeLines, - }, - template: "" - + "
" - + "" - + "
", - }) += page_specific_javascript_tag('vue_pipelines_index/index.js') \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 946b632b0e8..12415452653 100644 --- a/config/application.rb +++ b/config/application.rb @@ -99,6 +99,7 @@ module Gitlab config.assets.precompile << "lib/utils/*.js" config.assets.precompile << "lib/*.js" config.assets.precompile << "u2f.js" + config.assets.precompile << "vue_pipelines_index/index.js" # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' From 87d4d235683f5a40acec6c73116f1217caece5c2 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 31 Oct 2016 15:06:25 -0600 Subject: [PATCH 025/277] remove commit endpoint callprep for backend api to be updated or new endpoint to be built --- .../vue_pipelines_index/pipelines.vue.js.es6 | 2 -- .../vue_pipelines_index/store.js.es6 | 36 +++++++------------ 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index 021a5ca8743..c1a2ac3292e 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -9,7 +9,6 @@ data() { return { pipelines: [], - commits: [], currentPage: '', intervalId: '', }; @@ -19,7 +18,6 @@ 'store', ], created() { - this.store.fetchCommits.call(this, Vue); this.store.fetchDataLoop.call(this, Vue); }, methods: { diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index bdba9575013..06282744d8b 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -4,35 +4,25 @@ ((gl) => { const api = '/api/v3/projects'; - const goFetch = (that, vue) => - that.$http.get( - `${api}/${that.scope}/pipelines?per_page=5&page=1` - ) - .then((response) => { - vue.set(that, 'pipelines', JSON.parse(response.body)); - }, () => { - console.error('API Error for Pipelines'); - }); - gl.PipelineStore = class { fetchDataLoop(Vue) { + const goFetch = () => + this.$http.get( + `${api}/${this.scope}/pipelines?per_page=5&page=1` + ) + .then((response) => { + Vue.set(this, 'pipelines', JSON.parse(response.body)); + }, () => { + console.error('API Error for Pipelines'); + }); + + goFetch(); + // eventually clearInterval(this.intervalId) this.intervalId = setInterval(() => { console.log('DID IT'); - goFetch(this, Vue); + goFetch(); }, 30000); } - - fetchCommits(vue) { - this.$http.get( - `${api}/${this.scope}/repository/commits?per_page=5&page=1` - ) - .then((response) => { - vue.set(this, 'commits', JSON.parse(response.body)); - }, () => { - console.error('API Error for Pipelines'); - }) - .then(() => goFetch(this, vue)); - } }; })(window.gl || (window.gl = {})); From 257146032e1340701772097cfe7e4b29b7b56c97 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 31 Oct 2016 15:51:24 -0600 Subject: [PATCH 026/277] begin pagination logic - add Flash to error - refactor --- .../javascripts/vue_pipelines_index/index.js.es6 | 2 +- .../vue_pipelines_index/pipelines.vue.js.es6 | 9 ++++++--- .../javascripts/vue_pipelines_index/store.js.es6 | 16 ++++++---------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 4b69c4d4c7d..015716b0540 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -13,7 +13,7 @@ Vue.use(VueResource); - new Vue({ + return new Vue({ el: '.vue-pipelines-index', data: { scope: project.dataset.projectId, diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index c1a2ac3292e..a209b9d0532 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -11,6 +11,7 @@ pipelines: [], currentPage: '', intervalId: '', + pageNum: 'page=1', }; }, props: [ @@ -18,7 +19,10 @@ 'store', ], created() { - this.store.fetchDataLoop.call(this, Vue); + const url = window.location.href; + if (url.includes('?')) this.pageNum = url.split('?')[1]; + // now fetch page appropriate data + this.store.fetchDataLoop.call(this, Vue, this.pageNum); }, methods: { shortSha(pipeline) { @@ -26,8 +30,7 @@ }, changePage() { // clearInterval(this.intervalId); - // this.store.fetchCommits.call(this, Vue); - // this.store.fetchDataLoop.call(this, Vue); + // this.store.fetchDataLoop.call(this, Vue, this.pageNum); }, }, template: ` diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 06282744d8b..17f530a8ca2 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -1,28 +1,24 @@ -/* global gl */ +/* global gl, Flash */ /* eslint-disable no-param-reassign */ ((gl) => { const api = '/api/v3/projects'; gl.PipelineStore = class { - fetchDataLoop(Vue) { + fetchDataLoop(Vue, pageNum) { const goFetch = () => - this.$http.get( - `${api}/${this.scope}/pipelines?per_page=5&page=1` - ) + this.$http.get(`${api}/${this.scope}/pipelines?per_page=30&${pageNum}`) .then((response) => { Vue.set(this, 'pipelines', JSON.parse(response.body)); - }, () => { - console.error('API Error for Pipelines'); - }); + }, () => new Flash('Something went wrong on our end.')); + // inital fetch and then start timeout loop goFetch(); // eventually clearInterval(this.intervalId) this.intervalId = setInterval(() => { - console.log('DID IT'); goFetch(); - }, 30000); + }, 60000); } }; })(window.gl || (window.gl = {})); From c2d6969c97bf4c524c58722c2c5f0e4dba139e45 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 31 Oct 2016 16:10:50 -0600 Subject: [PATCH 027/277] formatting --- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 17f530a8ca2..789e8e791b6 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -10,7 +10,9 @@ this.$http.get(`${api}/${this.scope}/pipelines?per_page=30&${pageNum}`) .then((response) => { Vue.set(this, 'pipelines', JSON.parse(response.body)); - }, () => new Flash('Something went wrong on our end.')); + }, () => new Flash( + 'Something went wrong on our end.' + )); // inital fetch and then start timeout loop goFetch(); From ae4eacadf35fec8db8d7c316dcc8fe8d0f57535b Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 31 Oct 2016 16:45:25 -0600 Subject: [PATCH 028/277] fix styling by removing main div --- app/assets/javascripts/vue_pipelines_index/index.js.es6 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 015716b0540..35f78656856 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -22,9 +22,8 @@ components: { 'vue-pipelines': gl.VuePipeLines, }, - template: '' - + '
' - + "" - + '
', + template: ` + + `, }); })(); From c659ffcbc52c433879c4108276175f28f1105e6f Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 31 Oct 2016 17:17:29 -0600 Subject: [PATCH 029/277] breaking more HTML out of template --- .../vue_pipelines_index/index.js.es6 | 4 +- .../vue_pipelines_index/pipelines.vue.js.es6 | 167 +++++------------- .../vue_pipelines_index/stages.vue.js.es6 | 83 +++++++++ app/views/projects/pipelines/index.html.haml | 2 +- 4 files changed, 135 insertions(+), 121 deletions(-) create mode 100644 app/assets/javascripts/vue_pipelines_index/stages.vue.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 35f78656856..c7a5204c7d6 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -6,15 +6,17 @@ //= require ./store.js.es6 //= require ./running_icon.vue.js.es6 //= require ./running.vue.js.es6 +//= require ./stages.vue.js.es6 //= require ./pipelines.vue.js.es6 (() => { const project = document.querySelector('.table-holder'); Vue.use(VueResource); + // Vue.config.silent = true; return new Vue({ - el: '.vue-pipelines-index', + el: '#vue-pipelines-index', data: { scope: project.dataset.projectId, store: new gl.PipelineStore(), diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index a209b9d0532..34b8ceecd29 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -5,6 +5,7 @@ gl.VuePipeLines = Vue.extend({ components: { 'vue-running-pipeline': gl.VueRunningPipeline, + 'vue-stages': gl.VueStages, }, data() { return { @@ -92,128 +93,56 @@

- - - - - - - - - - + + - diff --git a/app/assets/javascripts/vue_pipelines_index/stages.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/stages.vue.js.es6 new file mode 100644 index 00000000000..0baf90dcf60 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/stages.vue.js.es6 @@ -0,0 +1,83 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueStages = Vue.extend({ + template: ` + + + + + + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index d7aebe584f8..8bd718749e4 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -44,6 +44,6 @@ - else .table-holder{"data-project-id": "#{@project.id}"} %table.table.ci-table - %tbody.vue-pipelines-index + %tbody#vue-pipelines-index = page_specific_javascript_tag('vue_pipelines_index/index.js') \ No newline at end of file From 13f11b470e0b94da082099ca32f8850dbce01b19 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 1 Nov 2016 10:38:17 -0600 Subject: [PATCH 030/277] add comments on data needed from API to help extract into components --- .../vue_pipelines_index/pipelines.vue.js.es6 | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index 34b8ceecd29..6d6c866c449 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -61,8 +61,10 @@
- - master + + master
@@ -76,6 +78,12 @@ + + fix broken repo 500 errors in UI and added relevant specs

+ @@ -108,7 +135,10 @@
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `, }); diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 8bd718749e4..a7d837227d5 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -36,14 +36,12 @@ = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint - %div.content-list.pipelines + %div.content-list.pipelines{"data-project-id": "#{@project.id}"} - stages = @pipelines.stages - if @pipelines.blank? %div .nothing-here-block No pipelines to show - else - .table-holder{"data-project-id": "#{@project.id}"} - %table.table.ci-table - %tbody#vue-pipelines-index + .vue-pipelines-index = page_specific_javascript_tag('vue_pipelines_index/index.js') \ No newline at end of file From 5ba83d961a7cf8bebea9f9e9814354be769041e9 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 1 Nov 2016 18:48:45 -0600 Subject: [PATCH 035/277] extracted Commit Column to vue component --- .../branch_commit.vue.js.es6 | 54 +++++++++++++++++++ .../vue_pipelines_index/index.js.es6 | 1 + .../vue_pipelines_index/pipelines.vue.js.es6 | 52 +++--------------- app/views/projects/pipelines/index.html.haml | 2 +- 4 files changed, 63 insertions(+), 46 deletions(-) create mode 100644 app/assets/javascripts/vue_pipelines_index/branch_commit.vue.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/branch_commit.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/branch_commit.vue.js.es6 new file mode 100644 index 00000000000..46cbf706d97 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/branch_commit.vue.js.es6 @@ -0,0 +1,54 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueBranchCommit = Vue.extend({ + props: ['pipeline', 'shortsha'], + template: ` +
+ +
+ + master +
+ + + +
+ {{shortsha(pipeline)}} + +

+ + + + + + + fix broken repo 500 errors in UI and added relevant specs + +

+ `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 4450fde4103..14a4a5d9b1d 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -8,6 +8,7 @@ //= require ./running.vue.js.es6 //= require ./stages.vue.js.es6 //= require ./pipeline_actions.vue.js.es6 +//= require ./branch_commit.vue.js.es6 //= require ./pipelines.vue.js.es6 (() => { diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index 16da8b8f423..0b1c2c33ca0 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -7,6 +7,7 @@ 'vue-running-pipeline': gl.VueRunningPipeline, 'vue-stages': gl.VueStages, 'vue-pipeline-actions': gl.VuePipelineActions, + 'vue-branch-commit': gl.VueBranchCommit, }, data() { return { @@ -27,7 +28,7 @@ this.store.fetchDataLoop.call(this, Vue, this.pageNum); }, methods: { - shortSha(pipeline) { + shortsha(pipeline) { return pipeline.sha.slice(0, 8); }, changePage() { @@ -61,50 +62,11 @@ {{pipeline.user}} -
- -
- - master -
- - - -
- {{shortSha(pipeline)}} - -

- - - - - - - fix broken repo 500 errors in UI and added relevant specs - -

+ + + master +
+ + +
- - master -
- - - -
- {{shortsha(pipeline)}} - -

- - +

+ - - - - fix broken repo 500 errors in UI and added relevant specs - -

+ + + + + + fix broken repo 500 errors in UI and added relevant specs + +

+ `, }); })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 14a4a5d9b1d..b8bcfdb30c3 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -4,6 +4,8 @@ //= require vue-resource //= require ./store.js.es6 +//= require ./pipeline_url.vue.js.es6 +//= require ./pipeline_head.vue.js.es6 //= require ./running_icon.vue.js.es6 //= require ./running.vue.js.es6 //= require ./stages.vue.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.vue.js.es6 index aa1069cff9f..a6ff2f10ccf 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.vue.js.es6 @@ -5,6 +5,7 @@ gl.VuePipelineActions = Vue.extend({ // props: ['builds'], template: ` +
@@ -71,6 +72,7 @@
+ `, }); })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_head.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_head.vue.js.es6 new file mode 100644 index 00000000000..2ead571aa9f --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_head.vue.js.es6 @@ -0,0 +1,26 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VuePipelineHead = Vue.extend({ + components: { + 'vue-running-icon': gl.VueRunningIcon, + }, + props: [ + 'pipeline', + 'pipelineurl', + ], + template: ` + + + Status + Pipeline + Commit + Stages + + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.vue.js.es6 new file mode 100644 index 00000000000..148fe58d9ce --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.vue.js.es6 @@ -0,0 +1,23 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VuePipelineUrl = Vue.extend({ + components: { + 'vue-running-icon': gl.VueRunningIcon, + }, + props: [ + 'pipeline', + 'pipelineurl', + ], + template: ` + + + #{{pipeline.id}} + + by + {{pipeline.user}} + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index bdb52b21b20..86c18ace09e 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -1,5 +1,5 @@ /* global Vue, gl */ -/* eslint-disable no-param-reassign */ +/* eslint-disable no-param-reassign, no-bitwise*/ ((gl) => { gl.VuePipeLines = Vue.extend({ @@ -8,6 +8,8 @@ 'vue-stages': gl.VueStages, 'vue-pipeline-actions': gl.VuePipelineActions, 'vue-branch-commit': gl.VueBranchCommit, + 'vue-pipeline-url': gl.VuePipelineUrl, + 'vue-pipeline-head': gl.VuePipelineHead, }, data() { return { @@ -22,11 +24,8 @@ 'store', ], created() { - // ** `.includes` does not work in PhantomJS ** - - // const url = window.location.toString(); - // if (url.includes('?')) this.pageNum = url.split('?')[1]; - // now fetch page appropriate data + const url = window.location.toString(); + if (~url.indexOf('?')) this.pageNum = url.split('?')[1]; this.store.fetchDataLoop.call(this, Vue, this.pageNum); }, methods: { @@ -37,66 +36,36 @@ // clearInterval(this.intervalId); // this.store.fetchDataLoop.call(this, Vue, this.pageNum); }, + pipelineurl(id) { + return `pipelines/${id}`; + }, }, template: `
- - - - - - - - - - - - - - - + + - + + + + + - +
StatusPipelineCommitStages
- - #{{pipeline.id}} - - by - {{pipeline.user}} - - +
- - +
diff --git a/app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 index 5edf8403bd9..8d18f5f3aa3 100644 --- a/app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 @@ -7,11 +7,12 @@ 'vue-running-icon': gl.VueRunningIcon, }, props: [ - 'pipe', + 'pipeline', + 'pipelineurl', ], template: ` - + diff --git a/app/assets/javascripts/vue_pipelines_index/stages.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/stages.vue.js.es6 index 400eb0e4d3f..da8c9280486 100644 --- a/app/assets/javascripts/vue_pipelines_index/stages.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stages.vue.js.es6 @@ -4,86 +4,107 @@ ((gl) => { gl.VueStages = Vue.extend({ template: ` - - - - - - - - + + + + + + + + + + + `, }); })(window.gl || (window.gl = {})); From 25e60f879fc21e55902ad5c069a5de0a3e340db7 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 2 Nov 2016 18:54:04 -0600 Subject: [PATCH 039/277] basic pagination ^_^ - will make component dynamic soon --- .../vue_pipelines_index/index.js.es6 | 1 + .../vue_pipelines_index/pipelines.vue.js.es6 | 71 +++++++++++-------- .../vue_pipelines_index/store.js.es6 | 5 +- .../vue_gl_pagination.vue.js.es6 | 54 ++++++++++++++ 4 files changed, 99 insertions(+), 32 deletions(-) create mode 100644 app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index b8bcfdb30c3..6ecbc1ba8ff 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -5,6 +5,7 @@ //= require ./store.js.es6 //= require ./pipeline_url.vue.js.es6 +//= require ./vue_gl_pagination.vue.js.es6 //= require ./pipeline_head.vue.js.es6 //= require ./running_icon.vue.js.es6 //= require ./running.vue.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index 86c18ace09e..4706ea61c8e 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -10,13 +10,14 @@ 'vue-branch-commit': gl.VueBranchCommit, 'vue-pipeline-url': gl.VuePipelineUrl, 'vue-pipeline-head': gl.VuePipelineHead, + 'vue-gl-pagination': gl.VueGlPagination, }, data() { return { pipelines: [], currentPage: '', intervalId: '', - pageNum: 'page=1', + pageNum: 1, }; }, props: [ @@ -25,50 +26,60 @@ ], created() { const url = window.location.toString(); - if (~url.indexOf('?')) this.pageNum = url.split('?')[1]; + if (~url.indexOf('?')) this.pageNum = url.split('?')[1].split('=')[1]; this.store.fetchDataLoop.call(this, Vue, this.pageNum); }, methods: { shortsha(pipeline) { return pipeline.sha.slice(0, 8); }, - changePage() { - // clearInterval(this.intervalId); - // this.store.fetchDataLoop.call(this, Vue, this.pageNum); + changepage(event) { + this.pageNum = +event.target.innerText; + // use p instead of page to avoid rails tyring to make an actual request + window.history.pushState({}, null, `?p=${this.pageNum}`); + clearInterval(this.intervalId); + this.store.fetchDataLoop.call(this, Vue, this.pageNum); }, pipelineurl(id) { return `pipelines/${id}`; }, }, template: ` -
- - - - - + + + +
+
+ +
`, }); diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 66cb989030a..bf5fba45194 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -3,11 +3,12 @@ ((gl) => { const api = '/api/v3/projects'; + const paginate = '?per_page=5&page='; gl.PipelineStore = class { fetchDataLoop(Vue, pageNum) { const goFetch = () => - this.$http.get(`${api}/${this.scope}/pipelines?per_page=30&${pageNum}`) + this.$http.get(`${api}/${this.scope}/pipelines${paginate}${pageNum}`) .then((response) => { Vue.set(this, 'pipelines', JSON.parse(response.body)); }, () => new Flash( @@ -20,7 +21,7 @@ // eventually clearInterval(this.intervalId) this.intervalId = setInterval(() => { goFetch(); - }, 3000); + }, 30000); } }; })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 new file mode 100644 index 00000000000..29d4a4df511 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 @@ -0,0 +1,54 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueGlPagination = Vue.extend({ + props: [ + 'changepage', + 'pages', + ], + template: ` +
+ +
+ `, + }); +})(window.gl || (window.gl = {})); From c249fb2ee9b522d02f49eee9734a1d6665d01e14 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 2 Nov 2016 22:14:46 -0600 Subject: [PATCH 040/277] formatting --- .../javascripts/vue_pipelines_index/running.vue.js.es6 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 index 8d18f5f3aa3..e363cb3c14e 100644 --- a/app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 @@ -14,7 +14,12 @@ - +  running From 77b0d6058ead2f42200c8a69dcd0e31561deae9e Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 2 Nov 2016 22:41:51 -0600 Subject: [PATCH 041/277] count passing through - last page known --- app/assets/javascripts/vue_pipelines_index/index.js.es6 | 9 +++++++-- .../javascripts/vue_pipelines_index/pipelines.vue.js.es6 | 2 ++ .../vue_pipelines_index/vue_gl_pagination.vue.js.es6 | 9 ++++++++- app/views/projects/pipelines/index.html.haml | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 6ecbc1ba8ff..4e335ca1d13 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -18,12 +18,12 @@ const project = document.querySelector('.pipelines'); Vue.use(VueResource); - // Vue.config.silent = true; return new Vue({ el: '.vue-pipelines-index', data: { scope: project.dataset.projectId, + count: project.dataset.count, store: new gl.PipelineStore(), }, components: { @@ -31,7 +31,12 @@ }, template: `
- + +
`, }); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index 4706ea61c8e..2f3bdc7a2a9 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -23,6 +23,7 @@ props: [ 'scope', 'store', + 'count', ], created() { const url = window.location.toString(); @@ -78,6 +79,7 @@
diff --git a/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 index 29d4a4df511..977d0e2b240 100644 --- a/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 @@ -6,7 +6,14 @@ props: [ 'changepage', 'pages', + 'count', ], + computed: { + lastpage() { + const lastPage = Math.ceil(+this.count / 5); + return `pipelines?page=${lastPage}`; + }, + }, template: ` diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index bd649a4fa2e..7bf727be930 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -36,7 +36,7 @@ = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint - %div.content-list.pipelines{"data-project-id": "#{@project.id}"} + %div.content-list.pipelines{"data-project-id": "#{@project.id}", "data-count": "#{@pipelines_count}"} - stages = @pipelines.stages - if @pipelines.blank? %div From 64e8ab95be436ccd22e087f39774b93ce6e4eeba Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 3 Nov 2016 11:20:15 -0600 Subject: [PATCH 042/277] pagination logic almost complete - active and inactive elements next - page gap not working --- .../vue_pipelines_index/pipelines.vue.js.es6 | 13 ++-- .../vue_gl_pagination.vue.js.es6 | 74 ++++++++++--------- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 index 2f3bdc7a2a9..77923e68004 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 @@ -17,7 +17,7 @@ pipelines: [], currentPage: '', intervalId: '', - pageNum: 1, + pagenum: 1, }; }, props: [ @@ -27,19 +27,19 @@ ], created() { const url = window.location.toString(); - if (~url.indexOf('?')) this.pageNum = url.split('?')[1].split('=')[1]; - this.store.fetchDataLoop.call(this, Vue, this.pageNum); + if (~url.indexOf('?')) this.pagenum = url.split('?')[1].split('=')[1]; + this.store.fetchDataLoop.call(this, Vue, this.pagenum); }, methods: { shortsha(pipeline) { return pipeline.sha.slice(0, 8); }, changepage(event) { - this.pageNum = +event.target.innerText; + this.pagenum = +event.target.innerText; // use p instead of page to avoid rails tyring to make an actual request - window.history.pushState({}, null, `?p=${this.pageNum}`); + window.history.pushState({}, null, `?p=${this.pagenum}`); clearInterval(this.intervalId); - this.store.fetchDataLoop.call(this, Vue, this.pageNum); + this.store.fetchDataLoop.call(this, Vue, this.pagenum); }, pipelineurl(id) { return `pipelines/${id}`; @@ -77,6 +77,7 @@ { gl.VueGlPagination = Vue.extend({ + data() { + return { + last: Math.ceil(+this.count / 5), + }; + }, props: [ 'changepage', 'pages', 'count', + 'pagenum', ], + methods: { + pagenumberstatus(n) { + if (n - 1 === +this.pagenum) return 'page active'; + return ''; + }, + }, computed: { lastpage() { - const lastPage = Math.ceil(+this.count / 5); - return `pipelines?page=${lastPage}`; + return `pipelines?p=${this.last}`; + }, + upcount() { + return +this.last + 1; + }, + prev() { + if (this.pagenum === 1) return 1; + return this.pagenum - 1; + }, + next() { + if (this.pagenum === this.last) return `pipelines?p=${this.pagenum}`; + return `pipelines?p=${this.pagenum + 1}`; }, }, template: `
-
    -
  • {{(n - 1)}}
  • - +
  • + … +
  • -
  • - Last » +
  • + Last »
From 4d8314ae0f07549b4e77f3aca8a2e3a84f80cb9b Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 3 Nov 2016 11:34:18 -0600 Subject: [PATCH 044/277] move last to computed --- .../vue_gl_pagination.vue.js.es6 | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 index 48efa4bd57e..5f9e47bf3b2 100644 --- a/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 @@ -3,11 +3,6 @@ ((gl) => { gl.VueGlPagination = Vue.extend({ - data() { - return { - last: Math.ceil(+this.count / 5), - }; - }, props: [ 'changepage', 'pages', @@ -21,6 +16,9 @@ }, }, computed: { + last() { + return Math.ceil(+this.count / 5) + }, lastpage() { return `pipelines?p=${this.last}`; }, @@ -38,10 +36,7 @@ }, template: `
-
    +
      From ea71e327f17c57e42d86717479a34af160d73d8e Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 3 Nov 2016 11:42:12 -0600 Subject: [PATCH 045/277] page gap works --- .../vue_pipelines_index/vue_gl_pagination.vue.js.es6 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 index 5f9e47bf3b2..84b75b469f0 100644 --- a/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 @@ -11,7 +11,7 @@ ], methods: { pagenumberstatus(n) { - if (n - 1 === +this.pagenum) return 'page active'; + if (n - 1 === +this.pagenum) return 'active'; return ''; }, }, @@ -43,8 +43,8 @@
    • {{(n - 1)}}
    • -
    • - … +
    • + …
    • {{(n - 1)}}
    • +
    • …
    • diff --git a/app/assets/javascripts/vue_pipelinesindex/store.js.es6 b/app/assets/javascripts/vue_pipelinesindex/store.js.es6 new file mode 100644 index 00000000000..bf5fba45194 --- /dev/null +++ b/app/assets/javascripts/vue_pipelinesindex/store.js.es6 @@ -0,0 +1,27 @@ +/* global gl, Flash */ +/* eslint-disable no-param-reassign */ + +((gl) => { + const api = '/api/v3/projects'; + const paginate = '?per_page=5&page='; + + gl.PipelineStore = class { + fetchDataLoop(Vue, pageNum) { + const goFetch = () => + this.$http.get(`${api}/${this.scope}/pipelines${paginate}${pageNum}`) + .then((response) => { + Vue.set(this, 'pipelines', JSON.parse(response.body)); + }, () => new Flash( + 'Something went wrong on our end.' + )); + + // inital fetch and then start timeout loop + goFetch(); + + // eventually clearInterval(this.intervalId) + this.intervalId = setInterval(() => { + goFetch(); + }, 30000); + } + }; +})(window.gl || (window.gl = {})); diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 7bf727be930..0e3cec4aa07 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -41,6 +41,19 @@ - if @pipelines.blank? %div .nothing-here-block No pipelines to show + - elsif @scope == 'branches' || @scope == 'tags' || @scope == 'running' + .table-holder + %table.table.ci-table + %thead + %th Status + %th Pipeline + %th Commit + %th Stages + %th + %th.hidden-xs + + = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages + = paginate @pipelines, theme: 'gitlab' - else .vue-pipelines-index From 18ff82c6a6949aa4c4b4efa77fc6e08e856eb79a Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 4 Nov 2016 09:56:45 -0600 Subject: [PATCH 047/277] remove extra dir when debugging eslint --- .../vue_pipelinesindex/store.js.es6 | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 app/assets/javascripts/vue_pipelinesindex/store.js.es6 diff --git a/app/assets/javascripts/vue_pipelinesindex/store.js.es6 b/app/assets/javascripts/vue_pipelinesindex/store.js.es6 deleted file mode 100644 index bf5fba45194..00000000000 --- a/app/assets/javascripts/vue_pipelinesindex/store.js.es6 +++ /dev/null @@ -1,27 +0,0 @@ -/* global gl, Flash */ -/* eslint-disable no-param-reassign */ - -((gl) => { - const api = '/api/v3/projects'; - const paginate = '?per_page=5&page='; - - gl.PipelineStore = class { - fetchDataLoop(Vue, pageNum) { - const goFetch = () => - this.$http.get(`${api}/${this.scope}/pipelines${paginate}${pageNum}`) - .then((response) => { - Vue.set(this, 'pipelines', JSON.parse(response.body)); - }, () => new Flash( - 'Something went wrong on our end.' - )); - - // inital fetch and then start timeout loop - goFetch(); - - // eventually clearInterval(this.intervalId) - this.intervalId = setInterval(() => { - goFetch(); - }, 30000); - } - }; -})(window.gl || (window.gl = {})); From 45992dc98c027701b8c187ef50ad9afaf6e8e052 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 4 Nov 2016 11:25:50 -0600 Subject: [PATCH 048/277] rename files for eslint --- .../{branch_commit.vue.js.es6 => branch_commit.js.es6} | 0 .../{vue_gl_pagination.vue.js.es6 => gl_pagination.js.es6} | 0 .../{pipeline_actions.vue.js.es6 => pipeline_actions.js.es6} | 0 .../{pipeline_head.vue.js.es6 => pipeline_head.js.es6} | 0 .../{pipeline_url.vue.js.es6 => pipeline_url.js.es6} | 0 .../{pipelines.vue.js.es6 => pipelines.js.es6} | 0 .../vue_pipelines_index/{running.vue.js.es6 => running.js.es6} | 0 .../{running_icon.vue.js.es6 => running_icon.js.es6} | 0 .../vue_pipelines_index/{stages.vue.js.es6 => stages.js.es6} | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename app/assets/javascripts/vue_pipelines_index/{branch_commit.vue.js.es6 => branch_commit.js.es6} (100%) rename app/assets/javascripts/vue_pipelines_index/{vue_gl_pagination.vue.js.es6 => gl_pagination.js.es6} (100%) rename app/assets/javascripts/vue_pipelines_index/{pipeline_actions.vue.js.es6 => pipeline_actions.js.es6} (100%) rename app/assets/javascripts/vue_pipelines_index/{pipeline_head.vue.js.es6 => pipeline_head.js.es6} (100%) rename app/assets/javascripts/vue_pipelines_index/{pipeline_url.vue.js.es6 => pipeline_url.js.es6} (100%) rename app/assets/javascripts/vue_pipelines_index/{pipelines.vue.js.es6 => pipelines.js.es6} (100%) rename app/assets/javascripts/vue_pipelines_index/{running.vue.js.es6 => running.js.es6} (100%) rename app/assets/javascripts/vue_pipelines_index/{running_icon.vue.js.es6 => running_icon.js.es6} (100%) rename app/assets/javascripts/vue_pipelines_index/{stages.vue.js.es6 => stages.js.es6} (100%) diff --git a/app/assets/javascripts/vue_pipelines_index/branch_commit.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 similarity index 100% rename from app/assets/javascripts/vue_pipelines_index/branch_commit.vue.js.es6 rename to app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/gl_pagination.js.es6 similarity index 100% rename from app/assets/javascripts/vue_pipelines_index/vue_gl_pagination.vue.js.es6 rename to app/assets/javascripts/vue_pipelines_index/gl_pagination.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 similarity index 100% rename from app/assets/javascripts/vue_pipelines_index/pipeline_actions.vue.js.es6 rename to app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_head.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_head.js.es6 similarity index 100% rename from app/assets/javascripts/vue_pipelines_index/pipeline_head.vue.js.es6 rename to app/assets/javascripts/vue_pipelines_index/pipeline_head.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 similarity index 100% rename from app/assets/javascripts/vue_pipelines_index/pipeline_url.vue.js.es6 rename to app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 similarity index 100% rename from app/assets/javascripts/vue_pipelines_index/pipelines.vue.js.es6 rename to app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/running.js.es6 similarity index 100% rename from app/assets/javascripts/vue_pipelines_index/running.vue.js.es6 rename to app/assets/javascripts/vue_pipelines_index/running.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/running_icon.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/running_icon.js.es6 similarity index 100% rename from app/assets/javascripts/vue_pipelines_index/running_icon.vue.js.es6 rename to app/assets/javascripts/vue_pipelines_index/running_icon.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/stages.vue.js.es6 b/app/assets/javascripts/vue_pipelines_index/stages.js.es6 similarity index 100% rename from app/assets/javascripts/vue_pipelines_index/stages.vue.js.es6 rename to app/assets/javascripts/vue_pipelines_index/stages.js.es6 From 35066c8558398e5d2dc096801b93e5c693e080fd Mon Sep 17 00:00:00 2001 From: Regis Date: Sat, 5 Nov 2016 16:36:33 -0600 Subject: [PATCH 049/277] making reusable status and status icons components --- app/assets/javascripts/svg_icons/index.js.es6 | 38 ++++++++++++++ app/assets/javascripts/vue_icons/index.js.es6 | 49 +++++++++++++++++++ .../vue_pipelines_index/index.js.es6 | 22 ++++----- .../vue_pipelines_index/pipelines.js.es6 | 13 +++-- .../vue_pipelines_index/running_icon.js.es6 | 13 ----- .../vue_pipelines_index/store.js.es6 | 9 ++++ .../vue_pipelines_status/failed.js.es6 | 24 +++++++++ .../vue_pipelines_status/index.js.es6 | 4 ++ .../vue_pipelines_status/pending.js.es6 | 24 +++++++++ .../running.js.es6 | 9 +--- .../vue_pipelines_status/status.js.es6 | 38 ++++++++++++++ app/views/projects/pipelines/index.html.haml | 2 + config/application.rb | 2 + 13 files changed, 208 insertions(+), 39 deletions(-) create mode 100644 app/assets/javascripts/svg_icons/index.js.es6 create mode 100644 app/assets/javascripts/vue_icons/index.js.es6 delete mode 100644 app/assets/javascripts/vue_pipelines_index/running_icon.js.es6 create mode 100644 app/assets/javascripts/vue_pipelines_status/failed.js.es6 create mode 100644 app/assets/javascripts/vue_pipelines_status/index.js.es6 create mode 100644 app/assets/javascripts/vue_pipelines_status/pending.js.es6 rename app/assets/javascripts/{vue_pipelines_index => vue_pipelines_status}/running.js.es6 (67%) create mode 100644 app/assets/javascripts/vue_pipelines_status/status.js.es6 diff --git a/app/assets/javascripts/svg_icons/index.js.es6 b/app/assets/javascripts/svg_icons/index.js.es6 new file mode 100644 index 00000000000..b84c5f85a01 --- /dev/null +++ b/app/assets/javascripts/svg_icons/index.js.es6 @@ -0,0 +1,38 @@ +//= require vue +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueRunningIcon = Vue.extend({ + template: ` + + + + + + + `, + }); + + gl.VuePendingIcon = Vue.extend({ + template: ` + + + + + + + `, + }); + + gl.VueSuccessIcon = Vue.extend({ + template: ` + + + + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_icons/index.js.es6 b/app/assets/javascripts/vue_icons/index.js.es6 new file mode 100644 index 00000000000..413f9b45393 --- /dev/null +++ b/app/assets/javascripts/vue_icons/index.js.es6 @@ -0,0 +1,49 @@ +//= require vue +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueRunningIcon = Vue.extend({ + template: ` + + + + + + + `, + }); + + gl.VuePendingIcon = Vue.extend({ + template: ` + + + + + + + `, + }); + + gl.VueSuccessIcon = Vue.extend({ + template: ` + + + + + + + `, + }); + + gl.VueFailedIcon = Vue.extend({ + template: ` + + + + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index ecdb3e397c2..0ba043ef612 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -1,21 +1,21 @@ /* global Vue, VueResource, gl */ +/* eslint-disable no-bitwise*/ -//= require vue //= require vue-resource //= require ./store.js.es6 -//= require ./pipeline_url.vue.js.es6 -//= require ./vue_gl_pagination.vue.js.es6 -//= require ./pipeline_head.vue.js.es6 -//= require ./running_icon.vue.js.es6 -//= require ./running.vue.js.es6 -//= require ./stages.vue.js.es6 -//= require ./pipeline_actions.vue.js.es6 -//= require ./branch_commit.vue.js.es6 -//= require ./pipelines.vue.js.es6 +//= require ./pipeline_url.js.es6 +//= require ./gl_pagination.js.es6 +//= require ./pipeline_head.js.es6 +//= require ./stages.js.es6 +//= require ./pipeline_actions.js.es6 +//= require ./branch_commit.js.es6 +//= require ./pipelines.js.es6 (() => { - if (~window.location.href.indexOf('scope')) return null; + const url = window.location.href; + if (~url.indexOf('scope')) return null; + const project = document.querySelector('.pipelines'); Vue.use(VueResource); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index a1d472cc464..6cc516d025f 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -11,6 +11,7 @@ 'vue-pipeline-url': gl.VuePipelineUrl, 'vue-pipeline-head': gl.VuePipelineHead, 'vue-gl-pagination': gl.VueGlPagination, + 'vue-status-pipeline': gl.VueStatusPipeline, }, data() { return { @@ -53,13 +54,11 @@ - - - - + + { - gl.VueRunningIcon = Vue.extend({ - template: ` - - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 89982b11b31..b4bdc27ede3 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -23,5 +23,14 @@ goFetch(); }, 60000); } + + fetchSvg(type, icon) { + this.$http.get(`/shared/icons/${icon}`) + .then((response) => { + this[type] = JSON.parse(response.body); + }, () => new Flash( + 'Something went wrong on our end.' + )); + } }; })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/failed.js.es6 b/app/assets/javascripts/vue_pipelines_status/failed.js.es6 new file mode 100644 index 00000000000..33050186fe5 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_status/failed.js.es6 @@ -0,0 +1,24 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueFailedPipeline = Vue.extend({ + components: { + 'vue-failed-icon': gl.VuePendingIcon, + }, + props: [ + 'pipeline', + 'pipelineurl', + ], + template: ` + + + + +  failed + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/index.js.es6 b/app/assets/javascripts/vue_pipelines_status/index.js.es6 new file mode 100644 index 00000000000..cea02b5d6af --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_status/index.js.es6 @@ -0,0 +1,4 @@ +//= require ./pending.js.es6 +//= require ./failed.js.es6 +//= require ./running.js.es6 +//= require ./status.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_status/pending.js.es6 b/app/assets/javascripts/vue_pipelines_status/pending.js.es6 new file mode 100644 index 00000000000..4c4a5ade09d --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_status/pending.js.es6 @@ -0,0 +1,24 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VuePendingPipeline = Vue.extend({ + components: { + 'vue-pending-icon': gl.VuePendingIcon, + }, + props: [ + 'pipeline', + 'pipelineurl', + ], + template: ` + + + + +  pending + + + + `, + }); +})(window.gl || (window.gl = {})); \ No newline at end of file diff --git a/app/assets/javascripts/vue_pipelines_index/running.js.es6 b/app/assets/javascripts/vue_pipelines_status/running.js.es6 similarity index 67% rename from app/assets/javascripts/vue_pipelines_index/running.js.es6 rename to app/assets/javascripts/vue_pipelines_status/running.js.es6 index e363cb3c14e..af8480f4795 100644 --- a/app/assets/javascripts/vue_pipelines_index/running.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/running.js.es6 @@ -14,14 +14,7 @@ - - - +  running diff --git a/app/assets/javascripts/vue_pipelines_status/status.js.es6 b/app/assets/javascripts/vue_pipelines_status/status.js.es6 new file mode 100644 index 00000000000..043468ea538 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_status/status.js.es6 @@ -0,0 +1,38 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueStatusPipeline = Vue.extend({ + components: { + 'vue-running-pipeline': gl.VueRunningPipeline, + 'vue-pending-pipeline': gl.VuePendingPipeline, + 'vue-failed-pipeline': gl.VueFailedPipeline, + }, + props: [ + 'pipeline', + 'pipelineurl', + ], + template: ` + + + + + + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 0e3cec4aa07..a09f23b2fde 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -57,4 +57,6 @@ - else .vue-pipelines-index += page_specific_javascript_tag('vue_icons/index.js') += page_specific_javascript_tag('vue_pipelines_status/index.js') = page_specific_javascript_tag('vue_pipelines_index/index.js') diff --git a/config/application.rb b/config/application.rb index 12415452653..74e00325cf2 100644 --- a/config/application.rb +++ b/config/application.rb @@ -100,6 +100,8 @@ module Gitlab config.assets.precompile << "lib/*.js" config.assets.precompile << "u2f.js" config.assets.precompile << "vue_pipelines_index/index.js" + config.assets.precompile << "vue_pipelines_status/index.js" + config.assets.precompile << "vue_icons/index.js" # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' From 905f7391e76deb92f787f3701224214bd2b4663b Mon Sep 17 00:00:00 2001 From: Regis Date: Sat, 5 Nov 2016 16:43:21 -0600 Subject: [PATCH 050/277] status icon and status components dynamic by scope --- .../vue_pipelines_index/pipelines.js.es6 | 10 ++--- .../vue_pipelines_status/failed.js.es6 | 8 ++-- .../vue_pipelines_status/pending.js.es6 | 8 ++-- .../vue_pipelines_status/running.js.es6 | 8 ++-- .../vue_pipelines_status/status.js.es6 | 42 +++++++++---------- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 6cc516d025f..30f8806fdf2 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -11,7 +11,7 @@ 'vue-pipeline-url': gl.VuePipelineUrl, 'vue-pipeline-head': gl.VuePipelineHead, 'vue-gl-pagination': gl.VueGlPagination, - 'vue-status-pipeline': gl.VueStatusPipeline, + 'vue-status-scope': gl.VueStatusScope, }, data() { return { @@ -54,11 +54,11 @@ - - + { - gl.VueFailedPipeline = Vue.extend({ + gl.VueFailedScope = Vue.extend({ components: { 'vue-failed-icon': gl.VuePendingIcon, }, props: [ - 'pipeline', - 'pipelineurl', + 'scope', + 'scopeurl', ], template: ` - +  failed diff --git a/app/assets/javascripts/vue_pipelines_status/pending.js.es6 b/app/assets/javascripts/vue_pipelines_status/pending.js.es6 index 4c4a5ade09d..1742e52a2bb 100644 --- a/app/assets/javascripts/vue_pipelines_status/pending.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/pending.js.es6 @@ -2,17 +2,17 @@ /* eslint-disable no-param-reassign */ ((gl) => { - gl.VuePendingPipeline = Vue.extend({ + gl.VuePendingScope = Vue.extend({ components: { 'vue-pending-icon': gl.VuePendingIcon, }, props: [ - 'pipeline', - 'pipelineurl', + 'scope', + 'scopeurl', ], template: ` - +  pending diff --git a/app/assets/javascripts/vue_pipelines_status/running.js.es6 b/app/assets/javascripts/vue_pipelines_status/running.js.es6 index af8480f4795..bc2b2b0b1e9 100644 --- a/app/assets/javascripts/vue_pipelines_status/running.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/running.js.es6 @@ -2,17 +2,17 @@ /* eslint-disable no-param-reassign */ ((gl) => { - gl.VueRunningPipeline = Vue.extend({ + gl.VueRunningScope = Vue.extend({ components: { 'vue-running-icon': gl.VueRunningIcon, }, props: [ - 'pipeline', - 'pipelineurl', + 'scope', + 'scopeurl', ], template: ` - +  running diff --git a/app/assets/javascripts/vue_pipelines_status/status.js.es6 b/app/assets/javascripts/vue_pipelines_status/status.js.es6 index 043468ea538..6695b600488 100644 --- a/app/assets/javascripts/vue_pipelines_status/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/status.js.es6 @@ -2,36 +2,36 @@ /* eslint-disable no-param-reassign */ ((gl) => { - gl.VueStatusPipeline = Vue.extend({ + gl.VueStatusScope = Vue.extend({ components: { - 'vue-running-pipeline': gl.VueRunningPipeline, - 'vue-pending-pipeline': gl.VuePendingPipeline, - 'vue-failed-pipeline': gl.VueFailedPipeline, + 'vue-running-scope': gl.VueRunningScope, + 'vue-pending-scope': gl.VuePendingScope, + 'vue-failed-scope': gl.VueFailedScope, }, props: [ - 'pipeline', - 'pipelineurl', + 'scope', + 'scopeurl', ], template: ` - - - + - - + - + `, }); From f140da6b55013c1170285be84cf1f53b23bfe67e Mon Sep 17 00:00:00 2001 From: Regis Date: Sat, 5 Nov 2016 16:50:18 -0600 Subject: [PATCH 051/277] remove files/folders --- 1p | 0 app/assets/javascripts/svg_icons/index.js.es6 | 38 ------------------- 2 files changed, 38 deletions(-) delete mode 100644 1p delete mode 100644 app/assets/javascripts/svg_icons/index.js.es6 diff --git a/1p b/1p deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/app/assets/javascripts/svg_icons/index.js.es6 b/app/assets/javascripts/svg_icons/index.js.es6 deleted file mode 100644 index b84c5f85a01..00000000000 --- a/app/assets/javascripts/svg_icons/index.js.es6 +++ /dev/null @@ -1,38 +0,0 @@ -//= require vue -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueRunningIcon = Vue.extend({ - template: ` - - - - - - - `, - }); - - gl.VuePendingIcon = Vue.extend({ - template: ` - - - - - - - `, - }); - - gl.VueSuccessIcon = Vue.extend({ - template: ` - - - - - - - `, - }); -})(window.gl || (window.gl = {})); From 0c9a4c14209c16d14d856fdb3b5e88c917232c6c Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 6 Nov 2016 11:17:18 -0700 Subject: [PATCH 052/277] icon and status comps - pagination logic almost done --- .../vue_pipeline_status/failed.js.es6 | 24 ++++++++++++ .../vue_pipeline_status/pending.js.es6 | 24 ++++++++++++ .../vue_pipeline_status/running.js.es6 | 24 ++++++++++++ .../vue_pipeline_status/status.js.es6 | 38 +++++++++++++++++++ .../vue_pipelines_index/gl_pagination.js.es6 | 24 ++++++------ .../vue_pipelines_index/pipelines.js.es6 | 10 +++-- 6 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 app/assets/javascripts/vue_pipeline_status/failed.js.es6 create mode 100644 app/assets/javascripts/vue_pipeline_status/pending.js.es6 create mode 100644 app/assets/javascripts/vue_pipeline_status/running.js.es6 create mode 100644 app/assets/javascripts/vue_pipeline_status/status.js.es6 diff --git a/app/assets/javascripts/vue_pipeline_status/failed.js.es6 b/app/assets/javascripts/vue_pipeline_status/failed.js.es6 new file mode 100644 index 00000000000..33050186fe5 --- /dev/null +++ b/app/assets/javascripts/vue_pipeline_status/failed.js.es6 @@ -0,0 +1,24 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueFailedPipeline = Vue.extend({ + components: { + 'vue-failed-icon': gl.VuePendingIcon, + }, + props: [ + 'pipeline', + 'pipelineurl', + ], + template: ` + + + + +  failed + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipeline_status/pending.js.es6 b/app/assets/javascripts/vue_pipeline_status/pending.js.es6 new file mode 100644 index 00000000000..bd2a1d0a496 --- /dev/null +++ b/app/assets/javascripts/vue_pipeline_status/pending.js.es6 @@ -0,0 +1,24 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VuePendingPipeline = Vue.extend({ + components: { + 'vue-pending-icon': gl.VuePendingIcon, + }, + props: [ + 'pipeline', + 'pipelineurl', + ], + template: ` + + + + +  pending + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipeline_status/running.js.es6 b/app/assets/javascripts/vue_pipeline_status/running.js.es6 new file mode 100644 index 00000000000..af8480f4795 --- /dev/null +++ b/app/assets/javascripts/vue_pipeline_status/running.js.es6 @@ -0,0 +1,24 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueRunningPipeline = Vue.extend({ + components: { + 'vue-running-icon': gl.VueRunningIcon, + }, + props: [ + 'pipeline', + 'pipelineurl', + ], + template: ` + + + + +  running + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipeline_status/status.js.es6 b/app/assets/javascripts/vue_pipeline_status/status.js.es6 new file mode 100644 index 00000000000..043468ea538 --- /dev/null +++ b/app/assets/javascripts/vue_pipeline_status/status.js.es6 @@ -0,0 +1,38 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueStatusPipeline = Vue.extend({ + components: { + 'vue-running-pipeline': gl.VueRunningPipeline, + 'vue-pending-pipeline': gl.VuePendingPipeline, + 'vue-failed-pipeline': gl.VueFailedPipeline, + }, + props: [ + 'pipeline', + 'pipelineurl', + ], + template: ` + + + + + + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/gl_pagination.js.es6 b/app/assets/javascripts/vue_pipelines_index/gl_pagination.js.es6 index 84d908f8e52..d4b31649f9b 100644 --- a/app/assets/javascripts/vue_pipelines_index/gl_pagination.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/gl_pagination.js.es6 @@ -14,6 +14,10 @@ if (n - 1 === +this.pagenum) return 'active'; return ''; }, + prevstatus() { + if (+this.pagenum > 1) return ''; + return 'prev disabled'; + }, }, computed: { last() { @@ -29,33 +33,31 @@ if (this.pagenum === 1) return 1; return this.pagenum - 1; }, - next() { - if (this.pagenum === this.last) return `pipelines?p=${this.pagenum}`; - return `pipelines?p=${this.pagenum + 1}`; - }, }, template: `
      diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 30f8806fdf2..b5c451ab221 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -35,10 +35,12 @@ shortsha(pipeline) { return pipeline.sha.slice(0, 8); }, - changepage(event, last) { - if (last) this.pagenum = +last; - if (!last) this.pagenum = +event.target.innerText; - // use p instead of page to avoid rails tyring to make an actual request + changepage(event, page = {}) { + if (page) this.pagenum = +event.target.innerText; + if (page.last) this.pagenum = +page.last; + if (page.where) this.pagenum = +page.next; + + // use p instead of page to avoid making an actual request window.history.pushState({}, null, `?p=${this.pagenum}`); clearInterval(this.intervalId); this.store.fetchDataLoop.call(this, Vue, this.pagenum); From 91a22eb6c537bea870bd14db81b36e6b19044b62 Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 6 Nov 2016 11:23:21 -0700 Subject: [PATCH 053/277] remove extra folder --- .../vue_pipeline_status/failed.js.es6 | 24 ------------ .../vue_pipeline_status/pending.js.es6 | 24 ------------ .../vue_pipeline_status/running.js.es6 | 24 ------------ .../vue_pipeline_status/status.js.es6 | 38 ------------------- 4 files changed, 110 deletions(-) delete mode 100644 app/assets/javascripts/vue_pipeline_status/failed.js.es6 delete mode 100644 app/assets/javascripts/vue_pipeline_status/pending.js.es6 delete mode 100644 app/assets/javascripts/vue_pipeline_status/running.js.es6 delete mode 100644 app/assets/javascripts/vue_pipeline_status/status.js.es6 diff --git a/app/assets/javascripts/vue_pipeline_status/failed.js.es6 b/app/assets/javascripts/vue_pipeline_status/failed.js.es6 deleted file mode 100644 index 33050186fe5..00000000000 --- a/app/assets/javascripts/vue_pipeline_status/failed.js.es6 +++ /dev/null @@ -1,24 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueFailedPipeline = Vue.extend({ - components: { - 'vue-failed-icon': gl.VuePendingIcon, - }, - props: [ - 'pipeline', - 'pipelineurl', - ], - template: ` - - - - -  failed - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipeline_status/pending.js.es6 b/app/assets/javascripts/vue_pipeline_status/pending.js.es6 deleted file mode 100644 index bd2a1d0a496..00000000000 --- a/app/assets/javascripts/vue_pipeline_status/pending.js.es6 +++ /dev/null @@ -1,24 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VuePendingPipeline = Vue.extend({ - components: { - 'vue-pending-icon': gl.VuePendingIcon, - }, - props: [ - 'pipeline', - 'pipelineurl', - ], - template: ` - - - - -  pending - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipeline_status/running.js.es6 b/app/assets/javascripts/vue_pipeline_status/running.js.es6 deleted file mode 100644 index af8480f4795..00000000000 --- a/app/assets/javascripts/vue_pipeline_status/running.js.es6 +++ /dev/null @@ -1,24 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueRunningPipeline = Vue.extend({ - components: { - 'vue-running-icon': gl.VueRunningIcon, - }, - props: [ - 'pipeline', - 'pipelineurl', - ], - template: ` - - - - -  running - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipeline_status/status.js.es6 b/app/assets/javascripts/vue_pipeline_status/status.js.es6 deleted file mode 100644 index 043468ea538..00000000000 --- a/app/assets/javascripts/vue_pipeline_status/status.js.es6 +++ /dev/null @@ -1,38 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueStatusPipeline = Vue.extend({ - components: { - 'vue-running-pipeline': gl.VueRunningPipeline, - 'vue-pending-pipeline': gl.VuePendingPipeline, - 'vue-failed-pipeline': gl.VueFailedPipeline, - }, - props: [ - 'pipeline', - 'pipelineurl', - ], - template: ` - - - - - - - - - `, - }); -})(window.gl || (window.gl = {})); From fd10ff30bbd4b24889fc0c79fbe35af95f9b5666 Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 6 Nov 2016 11:27:27 -0700 Subject: [PATCH 054/277] extract pagination into own folder and precompile in application.rb --- .../gl_pagination.js.es6 => vue_pagination/index.js.es6} | 0 app/assets/javascripts/vue_pipelines_index/index.js.es6 | 1 - app/views/projects/pipelines/index.html.haml | 3 +++ config/application.rb | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) rename app/assets/javascripts/{vue_pipelines_index/gl_pagination.js.es6 => vue_pagination/index.js.es6} (100%) diff --git a/app/assets/javascripts/vue_pipelines_index/gl_pagination.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 similarity index 100% rename from app/assets/javascripts/vue_pipelines_index/gl_pagination.js.es6 rename to app/assets/javascripts/vue_pagination/index.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 0ba043ef612..f7657879ac4 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -5,7 +5,6 @@ //= require ./store.js.es6 //= require ./pipeline_url.js.es6 -//= require ./gl_pagination.js.es6 //= require ./pipeline_head.js.es6 //= require ./stages.js.es6 //= require ./pipeline_actions.js.es6 diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index a09f23b2fde..6f70b239826 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -58,5 +58,8 @@ .vue-pipelines-index = page_specific_javascript_tag('vue_icons/index.js') +-# ^^ this component loads Vue so the rest don't +-# this will no longer be an issue once Vue2 is global += page_specific_javascript_tag('vue_pagination/index.js') = page_specific_javascript_tag('vue_pipelines_status/index.js') = page_specific_javascript_tag('vue_pipelines_index/index.js') diff --git a/config/application.rb b/config/application.rb index 74e00325cf2..4fde984ef62 100644 --- a/config/application.rb +++ b/config/application.rb @@ -101,6 +101,7 @@ module Gitlab config.assets.precompile << "u2f.js" config.assets.precompile << "vue_pipelines_index/index.js" config.assets.precompile << "vue_pipelines_status/index.js" + config.assets.precompile << "vue_pagination/index.js" config.assets.precompile << "vue_icons/index.js" # Version of your assets, change this if you want to expire all your assets From be146a4f0cfa26995748e6c086139cfcacfa613b Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 6 Nov 2016 13:35:29 -0700 Subject: [PATCH 055/277] remove svg api call - more progress on pagination --- .../javascripts/vue_pagination/index.js.es6 | 45 +++++++++++++------ .../vue_pipelines_index/index.js.es6 | 2 +- .../vue_pipelines_index/pipelines.js.es6 | 4 +- .../vue_pipelines_index/store.js.es6 | 9 ---- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index d4b31649f9b..2665b2bd2c5 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -3,14 +3,19 @@ ((gl) => { gl.VueGlPagination = Vue.extend({ + data() { + return { + nslice: +this.pagenum, + }; + }, props: [ 'changepage', - 'pages', 'count', 'pagenum', ], methods: { pagenumberstatus(n) { + console.log(n, this.nslice); if (n - 1 === +this.pagenum) return 'active'; return ''; }, @@ -20,35 +25,46 @@ }, }, computed: { + paginationsection() { + if (this.last < 6 && this.pagenum < 6) { + const pageArray = [...Array(6).keys()]; + pageArray.shift(); + return pageArray.slice(0, this.upcount); + } + const section = [...Array(this.upcount).keys()]; + section.shift(); + this.nslice = +this.pagenum; + return section.slice(+this.pagenum, +this.pagenum + 5); + }, last() { return Math.ceil(+this.count / 5); }, - lastpage() { - return `pipelines?p=${this.last}`; - }, upcount() { return +this.last + 1; }, - prev() { - if (this.pagenum === 1) return 1; - return this.pagenum - 1; + endspread() { + if (+this.pagenum < this.last && +this.pagenum > 5) return true; + return false + }, + begspread() { + if (+this.pagenum > 5 && +this.pagenum < this.last) return true; + return false }, }, template: `
      -
        -
      • +
          +
        • Prev
        • -
        • +
        • {{(n - 1)}}
        • -
        • +
        • …
        • +
        • + … +
        • - Last » + Last »
      diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index f7657879ac4..1aea0820208 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -34,7 +34,7 @@
diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index b5c451ab221..3ab336f0e6d 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -37,8 +37,8 @@ }, changepage(event, page = {}) { if (page) this.pagenum = +event.target.innerText; - if (page.last) this.pagenum = +page.last; - if (page.where) this.pagenum = +page.next; + if (page.where) this.pagenum = +page.where; + if (page.where) this.pagenum = +page.where; // use p instead of page to avoid making an actual request window.history.pushState({}, null, `?p=${this.pagenum}`); diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index b4bdc27ede3..89982b11b31 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -23,14 +23,5 @@ goFetch(); }, 60000); } - - fetchSvg(type, icon) { - this.$http.get(`/shared/icons/${icon}`) - .then((response) => { - this[type] = JSON.parse(response.body); - }, () => new Flash( - 'Something went wrong on our end.' - )); - } }; })(window.gl || (window.gl = {})); From 286b96f02501db38535f8a1e5f9b8a15bfafe5a6 Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 6 Nov 2016 13:56:08 -0700 Subject: [PATCH 056/277] a lot more progress on pagination :) --- app/assets/javascripts/vue_pagination/index.js.es6 | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 2665b2bd2c5..a6a8dd93480 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -34,6 +34,7 @@ const section = [...Array(this.upcount).keys()]; section.shift(); this.nslice = +this.pagenum; + this.endcount = +this.pagenum + 5; return section.slice(+this.pagenum, +this.pagenum + 5); }, last() { @@ -44,17 +45,17 @@ }, endspread() { if (+this.pagenum < this.last && +this.pagenum > 5) return true; - return false + return false; }, begspread() { if (+this.pagenum > 5 && +this.pagenum < this.last) return true; - return false + return false; }, }, template: `
    -
  • +
  • Prev
  • @@ -67,15 +68,15 @@
  • …
  • -
  • …
  • -
  • +
  • Last »
From fa2f052ba78d4cdbc8d1800da25f5cc8041f655e Mon Sep 17 00:00:00 2001 From: Regis Date: Sun, 6 Nov 2016 14:00:36 -0700 Subject: [PATCH 057/277] formatting --- app/assets/javascripts/vue_pagination/index.js.es6 | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index a6a8dd93480..73232b45b81 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -15,7 +15,6 @@ ], methods: { pagenumberstatus(n) { - console.log(n, this.nslice); if (n - 1 === +this.pagenum) return 'active'; return ''; }, @@ -68,7 +67,10 @@
  • …
  • -
  • …
  • -
  • +
  • Last »
  • From 31af345b476f0995888dc1e75bcabc95def2aba2 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 7 Nov 2016 14:16:57 -0700 Subject: [PATCH 058/277] fix prev button being misplaced --- .../javascripts/vue_pagination/index.js.es6 | 31 ++++++++++--------- .../vue_pipelines_index/index.js.es6 | 2 +- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 73232b45b81..88a2c5567a1 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -3,16 +3,16 @@ ((gl) => { gl.VueGlPagination = Vue.extend({ - data() { - return { - nslice: +this.pagenum, - }; - }, props: [ 'changepage', 'count', 'pagenum', ], + data() { + return { + nslice: +this.pagenum, + }; + }, methods: { pagenumberstatus(n) { if (n - 1 === +this.pagenum) return 'active'; @@ -24,17 +24,20 @@ }, }, computed: { + dynamicpage() { + const section = [...Array(this.upcount).keys()]; + section.shift(); + this.nslice = +this.pagenum; + this.endcount = +this.pagenum + 5; + return section.slice(+this.pagenum, +this.pagenum + 5); + }, paginationsection() { if (this.last < 6 && this.pagenum < 6) { const pageArray = [...Array(6).keys()]; pageArray.shift(); return pageArray.slice(0, this.upcount); } - const section = [...Array(this.upcount).keys()]; - section.shift(); - this.nslice = +this.pagenum; - this.endcount = +this.pagenum + 5; - return section.slice(+this.pagenum, +this.pagenum + 5); + return this.dynamicpage; }, last() { return Math.ceil(+this.count / 5); @@ -54,7 +57,7 @@ template: `
      -
    • +
    • Prev
    • @@ -71,16 +74,14 @@ class="next" v-if='(n === upcount || n === endcount) && pagenum !== last' > - - Next - + Next
    • …
    • Last »
    • diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 1aea0820208..f7657879ac4 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -34,7 +34,7 @@
    From b0e6d0788625ae4a1e522c33496c06b4cdd53020 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 7 Nov 2016 14:43:11 -0700 Subject: [PATCH 059/277] end dot logic complete --- .../javascripts/vue_pagination/index.js.es6 | 26 +++++++++++-------- .../vue_pipelines_index/index.js.es6 | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 88a2c5567a1..71031c4d76d 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -29,7 +29,12 @@ section.shift(); this.nslice = +this.pagenum; this.endcount = +this.pagenum + 5; - return section.slice(+this.pagenum, +this.pagenum + 5); + if (+this.pagenum + 5 <= this.last) { + return section.slice(+this.pagenum, +this.pagenum + 5); + } + if (+this.pagenum + 5 > this.last) { + return section.slice(this.last - 5, this.last); + } }, paginationsection() { if (this.last < 6 && this.pagenum < 6) { @@ -46,7 +51,7 @@ return +this.last + 1; }, endspread() { - if (+this.pagenum < this.last && +this.pagenum > 5) return true; + if (+this.pagenum < this.last) return true; return false; }, begspread() { @@ -57,17 +62,19 @@ template: `
      -
    • +
    • Prev
    • +
    • + … +
    • {{(n - 1)}}
    • - -
    • +
    • …
    • Next
    • -
    • - … -
    From e95a2b156b181145c98a790edd924e90cca3f3ee Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 7 Nov 2016 23:56:02 -0700 Subject: [PATCH 060/277] remove use of spread operator - attempt render function --- .../javascripts/vue_pagination/index.js.es6 | 36 ++++++++++++++++--- .../vue_pipelines_index/pipelines.js.es6 | 1 - 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 71031c4d76d..7703901e113 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -11,6 +11,7 @@ data() { return { nslice: +this.pagenum, + endcount: this.last, }; }, methods: { @@ -20,12 +21,15 @@ }, prevstatus() { if (+this.pagenum > 1) return ''; - return 'prev disabled'; + return 'disabled'; + }, + createSection(n) { + return Array.from(Array(n)).map((e, i) => i); }, }, computed: { dynamicpage() { - const section = [...Array(this.upcount).keys()]; + const section = this.createSection(this.upcount); section.shift(); this.nslice = +this.pagenum; this.endcount = +this.pagenum + 5; @@ -38,7 +42,7 @@ }, paginationsection() { if (this.last < 6 && this.pagenum < 6) { - const pageArray = [...Array(6).keys()]; + const pageArray = this.createSection(6); pageArray.shift(); return pageArray.slice(0, this.upcount); } @@ -81,7 +85,7 @@ class="next" v-if='(n === upcount || n === endcount) && pagenum !== last' > - Next + Next
  • `, + // render(createElement) { + // return createElement('div', { + // class: { + // 'gl-pagination': true, + // }, + // }, [createElement('ul', { + // class: { + // pagination: true, + // clearfix: true, + // }, + // }, this.paginationsection.map((e, i) => { + // if (!i) return createElement('li', [createElement('span', { + // class: { + // prev: this.prevstatus, + // }, + // }, 'Prev')]); + // if (i) { + // return createElement('li', + // [createElement('span', i)] + // ); + // } + // })), + // ]); + // }, }); })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 3ab336f0e6d..4ef9fca6474 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -40,7 +40,6 @@ if (page.where) this.pagenum = +page.where; if (page.where) this.pagenum = +page.where; - // use p instead of page to avoid making an actual request window.history.pushState({}, null, `?p=${this.pagenum}`); clearInterval(this.intervalId); this.store.fetchDataLoop.call(this, Vue, this.pagenum); From cdbbce17cb0f83895183d342fa27e0ee015f58bb Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 8 Nov 2016 12:23:29 -0700 Subject: [PATCH 061/277] cleanup --- .../javascripts/vue_pagination/index.js.es6 | 115 +++--------------- .../vue_pipelines_index/index.js.es6 | 2 +- .../vue_pipelines_index/pipeline_head.js.es6 | 7 -- .../vue_pipelines_index/pipelines.js.es6 | 10 +- .../vue_pipelines_index/store.js.es6 | 2 +- .../vue_pipelines_status/failed.js.es6 | 2 +- 6 files changed, 27 insertions(+), 111 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 7703901e113..dce1c86ffa2 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -8,117 +8,38 @@ 'count', 'pagenum', ], - data() { - return { - nslice: +this.pagenum, - endcount: this.last, - }; - }, methods: { pagenumberstatus(n) { if (n - 1 === +this.pagenum) return 'active'; return ''; }, - prevstatus() { - if (+this.pagenum > 1) return ''; - return 'disabled'; - }, - createSection(n) { - return Array.from(Array(n)).map((e, i) => i); - }, + createSection(n) { return Array.from(Array(n)).map((e, i) => i); }, }, computed: { - dynamicpage() { - const section = this.createSection(this.upcount); - section.shift(); - this.nslice = +this.pagenum; - this.endcount = +this.pagenum + 5; - if (+this.pagenum + 5 <= this.last) { - return section.slice(+this.pagenum, +this.pagenum + 5); - } - if (+this.pagenum + 5 > this.last) { - return section.slice(this.last - 5, this.last); - } - }, - paginationsection() { - if (this.last < 6 && this.pagenum < 6) { - const pageArray = this.createSection(6); - pageArray.shift(); - return pageArray.slice(0, this.upcount); - } - return this.dynamicpage; - }, - last() { - return Math.ceil(+this.count / 5); - }, - upcount() { - return +this.last + 1; - }, - endspread() { - if (+this.pagenum < this.last) return true; - return false; - }, - begspread() { - if (+this.pagenum > 5 && +this.pagenum < this.last) return true; - return false; + last() { return Math.ceil(+this.count / 5); }, + getItems() { + const items = []; + const pages = this.createSection(+this.last + 1); + pages.shift(); + + if (+this.pagenum !== 1) items.push({ text: 'Prev' }); + + pages.forEach(i => items.push({ text: i })); + + if (+this.pagenum < this.last) items.push({ text: 'Next' }); + if (+this.pagenum !== this.last) items.push({ text: 'Last »' }); + + return items; }, }, template: `
    -
      -
    • - Prev -
    • -
    • - … -
    • -
    • - {{(n - 1)}} -
    • -
    • - … -
    • - -
    • - Last » +
        +
      • + {{item.text}}
    `, - // render(createElement) { - // return createElement('div', { - // class: { - // 'gl-pagination': true, - // }, - // }, [createElement('ul', { - // class: { - // pagination: true, - // clearfix: true, - // }, - // }, this.paginationsection.map((e, i) => { - // if (!i) return createElement('li', [createElement('span', { - // class: { - // prev: this.prevstatus, - // }, - // }, 'Prev')]); - // if (i) { - // return createElement('li', - // [createElement('span', i)] - // ); - // } - // })), - // ]); - // }, }); })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 1aea0820208..f7657879ac4 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -34,7 +34,7 @@ diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_head.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_head.js.es6 index 2ead571aa9f..510e54f3a6f 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_head.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_head.js.es6 @@ -3,13 +3,6 @@ ((gl) => { gl.VuePipelineHead = Vue.extend({ - components: { - 'vue-running-icon': gl.VueRunningIcon, - }, - props: [ - 'pipeline', - 'pipelineurl', - ], template: ` diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 4ef9fca6474..ecb07af9a32 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -35,10 +35,12 @@ shortsha(pipeline) { return pipeline.sha.slice(0, 8); }, - changepage(event, page = {}) { - if (page) this.pagenum = +event.target.innerText; - if (page.where) this.pagenum = +page.where; - if (page.where) this.pagenum = +page.where; + changepage(event, last) { + const text = event.target.innerText; + if (typeof +text === 'number') this.pagenum = +text; + if (text === 'Last »') this.pagenum = last; + if (text === 'Next') this.pagnum = +this.pagenum + 1; + if (text === 'Prev') this.pagenum = +this.pagenum - 1; window.history.pushState({}, null, `?p=${this.pagenum}`); clearInterval(this.intervalId); diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 89982b11b31..d4b0c79f225 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -21,7 +21,7 @@ // eventually clearInterval(this.intervalId) this.intervalId = setInterval(() => { goFetch(); - }, 60000); + }, 3000); } }; })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/failed.js.es6 b/app/assets/javascripts/vue_pipelines_status/failed.js.es6 index 92373e12c25..a877cfc688e 100644 --- a/app/assets/javascripts/vue_pipelines_status/failed.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/failed.js.es6 @@ -4,7 +4,7 @@ ((gl) => { gl.VueFailedScope = Vue.extend({ components: { - 'vue-failed-icon': gl.VuePendingIcon, + 'vue-failed-icon': gl.VueFailedIcon, }, props: [ 'scope', From 4dd82e1d49d5dc1958c2425d1d052645638c2274 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 8 Nov 2016 12:31:41 -0700 Subject: [PATCH 062/277] fix changepage logic --- app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index ecb07af9a32..2c26b0a2bc7 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -37,9 +37,9 @@ }, changepage(event, last) { const text = event.target.innerText; - if (typeof +text === 'number') this.pagenum = +text; + if (typeof text === 'number') this.pagenum = +text; if (text === 'Last »') this.pagenum = last; - if (text === 'Next') this.pagnum = +this.pagenum + 1; + if (text === 'Next') this.pagenum = +this.pagenum + 1; if (text === 'Prev') this.pagenum = +this.pagenum - 1; window.history.pushState({}, null, `?p=${this.pagenum}`); From 39a8f1aaf4c05764045de5fcdacd52af2406c152 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 8 Nov 2016 13:04:30 -0700 Subject: [PATCH 063/277] regex for number check --- .../javascripts/vue_pagination/index.js.es6 | 17 +++++++++++------ .../vue_pipelines_index/pipelines.js.es6 | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index dce1c86ffa2..8dabfef3b95 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -9,9 +9,14 @@ 'pagenum', ], methods: { - pagenumberstatus(n) { - if (n - 1 === +this.pagenum) return 'active'; - return ''; + pagestatus(n) { + if (n - 1 === +this.pagenum) return true; + return false; + }, + prevstatus(index) { + if (index > 0) return false; + if (+this.pagenum < 2) return true; + return false; }, createSection(n) { return Array.from(Array(n)).map((e, i) => i); }, }, @@ -22,7 +27,7 @@ const pages = this.createSection(+this.last + 1); pages.shift(); - if (+this.pagenum !== 1) items.push({ text: 'Prev' }); + items.push({ text: 'Prev', class: this.prevstatus() }); pages.forEach(i => items.push({ text: i })); @@ -34,8 +39,8 @@ }, template: `
    -
      -
    • +
        +
      • {{item.text}}
      diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 2c26b0a2bc7..c3fdcd7249c 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -37,7 +37,7 @@ }, changepage(event, last) { const text = event.target.innerText; - if (typeof text === 'number') this.pagenum = +text; + if (/^-?[\d.]+(?:e-?\d+)?$/.test(text)) this.pagenum = +text; if (text === 'Last »') this.pagenum = last; if (text === 'Next') this.pagenum = +this.pagenum + 1; if (text === 'Prev') this.pagenum = +this.pagenum - 1; From 3d9e368e61c14db34fc1cdfa3b5d1826444e4318 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 8 Nov 2016 14:23:16 -0700 Subject: [PATCH 064/277] about to use fatih code --- .../javascripts/vue_pagination/index.js.es6 | 49 +++++++++++++++++-- .../vue_pipelines_index/pipelines.js.es6 | 1 + 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 8dabfef3b95..ed0d6f35ea6 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -10,6 +10,9 @@ ], methods: { pagestatus(n) { + if (this.getItems[1].prev) { + if (n === +this.pagenum) return true; + } if (n - 1 === +this.pagenum) return true; return false; }, @@ -27,12 +30,17 @@ const pages = this.createSection(+this.last + 1); pages.shift(); - items.push({ text: 'Prev', class: this.prevstatus() }); + if (+this.pagenum > 1) items.push({ text: 'First', first: true }); - pages.forEach(i => items.push({ text: i })); + items.push({ text: 'Prev', prev: true, class: this.prevstatus() }); - if (+this.pagenum < this.last) items.push({ text: 'Next' }); - if (+this.pagenum !== this.last) items.push({ text: 'Last »' }); + pages.forEach(i => items.push({ text: i, number: true })); + + let nextDisabled = false; + if (+this.pagenum === this.last) { nextDisabled = true; } + items.push({ text: 'Next', next: true, disabled: nextDisabled }); + + if (+this.pagenum !== this.last) items.push({ text: 'Last »', last: true }); return items; }, @@ -40,9 +48,40 @@ template: `
        -
      • + +
      • + {{item.text}} +
      • + +
      • + {{item.text}} +
      • + +
      • + {{item.text}} +
      • + +
      • {{item.text}}
      • + +
      • + {{item.text}} +
      `, diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index c3fdcd7249c..5c64ec57cce 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -41,6 +41,7 @@ if (text === 'Last »') this.pagenum = last; if (text === 'Next') this.pagenum = +this.pagenum + 1; if (text === 'Prev') this.pagenum = +this.pagenum - 1; + if (text === 'First') this.pagenum = 1; window.history.pushState({}, null, `?p=${this.pagenum}`); clearInterval(this.intervalId); From f4a52de3c75b59af621585173bdf01b02f034515 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 8 Nov 2016 14:57:16 -0700 Subject: [PATCH 065/277] pagination works --- .../javascripts/vue_pagination/index.js.es6 | 87 ++++++++++--------- .../vue_pipelines_index/pipelines.js.es6 | 5 +- 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index ed0d6f35ea6..fb53a84bd9c 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -1,5 +1,5 @@ /* global Vue, gl */ -/* eslint-disable no-param-reassign */ +/* eslint-disable no-param-reassign, no-plusplus */ ((gl) => { gl.VueGlPagination = Vue.extend({ @@ -26,61 +26,64 @@ computed: { last() { return Math.ceil(+this.count / 5); }, getItems() { + const total = +this.count; + const page = +this.pagenum; const items = []; - const pages = this.createSection(+this.last + 1); - pages.shift(); - if (+this.pagenum > 1) items.push({ text: 'First', first: true }); + if (page > 1) { + items.push({ title: '<< First', where: 1 }); + } - items.push({ text: 'Prev', prev: true, class: this.prevstatus() }); + if (page > 1) { + items.push({ title: 'Prev', where: page - 1 }); + } else { + items.push({ title: 'Prev', where: page - 1, disabled: true }); + } - pages.forEach(i => items.push({ text: i, number: true })); + if (page > 6) { + items.push({ title: '...', separator: true }); + } - let nextDisabled = false; - if (+this.pagenum === this.last) { nextDisabled = true; } - items.push({ text: 'Next', next: true, disabled: nextDisabled }); + const start = Math.max(page - 4, 1); + const end = Math.min(page + 4, total); - if (+this.pagenum !== this.last) items.push({ text: 'Last »', last: true }); + for (let i = start; i <= end; i++) { + const isActive = i === page; + items.push({ title: i, active: isActive, where: i }); + } + + if (total - page > 4) { + items.push({ title: '...', separator: true }); + } + + if (page === total) { + items.push({ title: 'Next', where: page + 1, disabled: true }); + } else if (total - page >= 1) { + items.push({ title: 'Next', where: page + 1 }); + } + + if (total - page >= 1) { + items.push({ title: 'Last >>', where: total }); + } return items; }, }, template: `
      -
        - +
        • - {{item.text}} -
        • - -
        • - {{item.text}} -
        • - -
        • - {{item.text}} -
        • - -
        • - {{item.text}} -
        • - -
        • - {{item.text}} + + {{item.title}} +
      diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 5c64ec57cce..049aa07e182 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -37,11 +37,12 @@ }, changepage(event, last) { const text = event.target.innerText; + if (text === '...') return; if (/^-?[\d.]+(?:e-?\d+)?$/.test(text)) this.pagenum = +text; - if (text === 'Last »') this.pagenum = last; + if (text === 'Last >>') this.pagenum = last; if (text === 'Next') this.pagenum = +this.pagenum + 1; if (text === 'Prev') this.pagenum = +this.pagenum - 1; - if (text === 'First') this.pagenum = 1; + if (text === '<< First') this.pagenum = 1; window.history.pushState({}, null, `?p=${this.pagenum}`); clearInterval(this.intervalId); From 595607586330638c405262fe5075d2be033d70c0 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 8 Nov 2016 14:59:20 -0700 Subject: [PATCH 066/277] cleanup --- .../javascripts/vue_pagination/index.js.es6 | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index fb53a84bd9c..2d7ae08053e 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -26,13 +26,11 @@ computed: { last() { return Math.ceil(+this.count / 5); }, getItems() { - const total = +this.count; + const total = +this.last; const page = +this.pagenum; const items = []; - if (page > 1) { - items.push({ title: '<< First', where: 1 }); - } + if (page > 1) items.push({ title: '<< First', where: 1 }); if (page > 1) { items.push({ title: 'Prev', where: page - 1 }); @@ -40,9 +38,7 @@ items.push({ title: 'Prev', where: page - 1, disabled: true }); } - if (page > 6) { - items.push({ title: '...', separator: true }); - } + if (page > 6) items.push({ title: '...', separator: true }); const start = Math.max(page - 4, 1); const end = Math.min(page + 4, total); @@ -52,9 +48,7 @@ items.push({ title: i, active: isActive, where: i }); } - if (total - page > 4) { - items.push({ title: '...', separator: true }); - } + if (total - page > 4) items.push({ title: '...', separator: true }); if (page === total) { items.push({ title: 'Next', where: page + 1, disabled: true }); @@ -62,9 +56,7 @@ items.push({ title: 'Next', where: page + 1 }); } - if (total - page >= 1) { - items.push({ title: 'Last >>', where: total }); - } + if (total - page >= 1) items.push({ title: 'Last >>', where: total }); return items; }, From 1c85514ac080af69eba6b2f486f2d78ebfadad57 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 8 Nov 2016 15:03:25 -0700 Subject: [PATCH 067/277] remove uneeded methods --- .../javascripts/vue_pagination/index.js.es6 | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 2d7ae08053e..089f7ca0281 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -8,23 +8,10 @@ 'count', 'pagenum', ], - methods: { - pagestatus(n) { - if (this.getItems[1].prev) { - if (n === +this.pagenum) return true; - } - if (n - 1 === +this.pagenum) return true; - return false; - }, - prevstatus(index) { - if (index > 0) return false; - if (+this.pagenum < 2) return true; - return false; - }, - createSection(n) { return Array.from(Array(n)).map((e, i) => i); }, - }, computed: { - last() { return Math.ceil(+this.count / 5); }, + last() { + return Math.ceil(+this.count / 5); + }, getItems() { const total = +this.last; const page = +this.pagenum; From 7ae775d7aafa7dd02392d50cd20ccdac75b261e1 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 9 Nov 2016 15:45:44 -0700 Subject: [PATCH 068/277] time_ago in vue nearly done --- .../vue_pipelines_index/branch_commit.js.es6 | 3 - .../vue_pipelines_index/index.js.es6 | 1 + .../vue_pipelines_index/pipelines.js.es6 | 3 +- .../vue_pipelines_index/store.js.es6 | 2 - .../vue_pipelines_index/time_ago.js.es6 | 96 + vendor/assets/javascripts/vue.full.js | 17170 +++++++--------- vendor/assets/javascripts/vue.min.js | 10 +- 7 files changed, 7409 insertions(+), 9876 deletions(-) create mode 100644 app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 index ee8b1234697..1f743999f4d 100644 --- a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 @@ -14,9 +14,6 @@
      - master
      diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index f7657879ac4..c72899f1c19 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -9,6 +9,7 @@ //= require ./stages.js.es6 //= require ./pipeline_actions.js.es6 //= require ./branch_commit.js.es6 +//= require ./time_ago.js.es6 //= require ./pipelines.js.es6 (() => { diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 049aa07e182..b1ef73d4caa 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -12,6 +12,7 @@ 'vue-pipeline-head': gl.VuePipelineHead, 'vue-gl-pagination': gl.VueGlPagination, 'vue-status-scope': gl.VueStatusScope, + 'vue-time-ago': gl.VueTimeAgo, }, data() { return { @@ -75,7 +76,7 @@ > - + diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index d4b0c79f225..1a9d13f15a2 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -15,10 +15,8 @@ 'Something went wrong on our end.' )); - // inital fetch and then start timeout loop goFetch(); - // eventually clearInterval(this.intervalId) this.intervalId = setInterval(() => { goFetch(); }, 3000); diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 new file mode 100644 index 00000000000..fe325ded088 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -0,0 +1,96 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ +((gl) => { + gl.VueTimeAgo = Vue.extend({ + props: [ + 'pipeline', + ], + methods: { + formatSection(section) { + if (`${section}`.split('').length <= 1) return `0${section}`; + return `${section}`; + }, + hours(date) { + return this.formatSection(date.getHours()); + }, + minutes(date) { + return this.formatSection(date.getMinutes()); + }, + seconds(date) { + return this.formatSection(date.getSeconds()); + }, + }, + computed: { + finishdate() { + const date = new Date( + new Date( + this.pipeline.finished_at + ).getTime() - new Date( + this.pipeline.started_at + ).getTime() + ); + return ( + `${this.hours(date)}:${this.minutes(date)}:${this.seconds(date)}` + ); + }, + runningdate() { + const date = new Date( + new Date().getTime() - new Date(this.pipeline.started_at).getTime() + ); + return ( + `${this.hours(date)}:${this.minutes(date)}:${this.seconds(date)}` + ); + }, + timeStopped() { + const options = { + weekday: 'long', + year: 'numeric', + month: 'short', + day: 'numeric', + }; + + options.timeZoneName = 'short'; + + const finished = this.pipeline.finished_at; + + if (!finished) return false; + + return { + words: gl.utils.getTimeago().format(finished), + }; + }, + duration() { + if (this.timeStopped) return this.finishdate; + return this.runningdate; + }, + }, + template: ` + +

      + + + + + + {{duration}} +

      +

      + + +

      + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/vendor/assets/javascripts/vue.full.js b/vendor/assets/javascripts/vue.full.js index 7ae95897a01..07c4778449b 100644 --- a/vendor/assets/javascripts/vue.full.js +++ b/vendor/assets/javascripts/vue.full.js @@ -1,10073 +1,7515 @@ /*! - * Vue.js v1.0.26 - * (c) 2016 Evan You + * Vue.js v2.0.3 + * (c) 2014-2016 Evan You * Released under the MIT License. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.Vue = factory()); -}(this, function () { 'use strict'; +}(this, (function () { 'use strict'; - function set(obj, key, val) { - if (hasOwn(obj, key)) { - obj[key] = val; - return; +/* */ + +/** + * Convert a value to a string that is actually rendered. + */ +function _toString (val) { + return val == null + ? '' + : typeof val === 'object' + ? JSON.stringify(val, null, 2) + : String(val) +} + +/** + * Convert a input value to a number for persistence. + * If the conversion fails, return original string. + */ +function toNumber (val) { + var n = parseFloat(val, 10); + return (n || n === 0) ? n : val +} + +/** + * Make a map and return a function for checking if a key + * is in that map. + */ +function makeMap ( + str, + expectsLowerCase +) { + var map = Object.create(null); + var list = str.split(','); + for (var i = 0; i < list.length; i++) { + map[list[i]] = true; + } + return expectsLowerCase + ? function (val) { return map[val.toLowerCase()]; } + : function (val) { return map[val]; } +} + +/** + * Check if a tag is a built-in tag. + */ +var isBuiltInTag = makeMap('slot,component', true); + +/** + * Remove an item from an array + */ +function remove$1 (arr, item) { + if (arr.length) { + var index = arr.indexOf(item); + if (index > -1) { + return arr.splice(index, 1) } - if (obj._isVue) { - set(obj._data, key, val); - return; + } +} + +/** + * Check whether the object has the property. + */ +var hasOwnProperty = Object.prototype.hasOwnProperty; +function hasOwn (obj, key) { + return hasOwnProperty.call(obj, key) +} + +/** + * Check if value is primitive + */ +function isPrimitive (value) { + return typeof value === 'string' || typeof value === 'number' +} + +/** + * Create a cached version of a pure function. + */ +function cached (fn) { + var cache = Object.create(null); + return function cachedFn (str) { + var hit = cache[str]; + return hit || (cache[str] = fn(str)) + } +} + +/** + * Camelize a hyphen-delmited string. + */ +var camelizeRE = /-(\w)/g; +var camelize = cached(function (str) { + return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) +}); + +/** + * Capitalize a string. + */ +var capitalize = cached(function (str) { + return str.charAt(0).toUpperCase() + str.slice(1) +}); + +/** + * Hyphenate a camelCase string. + */ +var hyphenateRE = /([^-])([A-Z])/g; +var hyphenate = cached(function (str) { + return str + .replace(hyphenateRE, '$1-$2') + .replace(hyphenateRE, '$1-$2') + .toLowerCase() +}); + +/** + * Simple bind, faster than native + */ +function bind$1 (fn, ctx) { + function boundFn (a) { + var l = arguments.length; + return l + ? l > 1 + ? fn.apply(ctx, arguments) + : fn.call(ctx, a) + : fn.call(ctx) + } + // record original fn length + boundFn._length = fn.length; + return boundFn +} + +/** + * Convert an Array-like object to a real Array. + */ +function toArray (list, start) { + start = start || 0; + var i = list.length - start; + var ret = new Array(i); + while (i--) { + ret[i] = list[i + start]; + } + return ret +} + +/** + * Mix properties into target object. + */ +function extend (to, _from) { + for (var key in _from) { + to[key] = _from[key]; + } + return to +} + +/** + * Quick object check - this is primarily used to tell + * Objects from primitive values when we know the value + * is a JSON-compliant type. + */ +function isObject (obj) { + return obj !== null && typeof obj === 'object' +} + +/** + * Strict object type check. Only returns true + * for plain JavaScript objects. + */ +var toString = Object.prototype.toString; +var OBJECT_STRING = '[object Object]'; +function isPlainObject (obj) { + return toString.call(obj) === OBJECT_STRING +} + +/** + * Merge an Array of Objects into a single Object. + */ +function toObject (arr) { + var res = {}; + for (var i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]); } - var ob = obj.__ob__; - if (!ob) { - obj[key] = val; - return; - } - ob.convert(key, val); - ob.dep.notify(); - if (ob.vms) { - var i = ob.vms.length; - while (i--) { - var vm = ob.vms[i]; - vm._proxy(key); - vm._digest(); + } + return res +} + +/** + * Perform no operation. + */ +function noop () {} + +/** + * Always return false. + */ +var no = function () { return false; }; + +/** + * Generate a static keys string from compiler modules. + */ +function genStaticKeys (modules) { + return modules.reduce(function (keys, m) { + return keys.concat(m.staticKeys || []) + }, []).join(',') +} + +/** + * Check if two values are loosely equal - that is, + * if they are plain objects, do they have the same shape? + */ +function looseEqual (a, b) { + /* eslint-disable eqeqeq */ + return a == b || ( + isObject(a) && isObject(b) + ? JSON.stringify(a) === JSON.stringify(b) + : false + ) + /* eslint-enable eqeqeq */ +} + +function looseIndexOf (arr, val) { + for (var i = 0; i < arr.length; i++) { + if (looseEqual(arr[i], val)) { return i } + } + return -1 +} + +/* */ + +var config = { + /** + * Option merge strategies (used in core/util/options) + */ + optionMergeStrategies: Object.create(null), + + /** + * Whether to suppress warnings. + */ + silent: false, + + /** + * Whether to enable devtools + */ + devtools: "development" !== 'production', + + /** + * Error handler for watcher errors + */ + errorHandler: null, + + /** + * Ignore certain custom elements + */ + ignoredElements: null, + + /** + * Custom user key aliases for v-on + */ + keyCodes: Object.create(null), + + /** + * Check if a tag is reserved so that it cannot be registered as a + * component. This is platform-dependent and may be overwritten. + */ + isReservedTag: no, + + /** + * Check if a tag is an unknown element. + * Platform-dependent. + */ + isUnknownElement: no, + + /** + * Get the namespace of an element + */ + getTagNamespace: noop, + + /** + * Check if an attribute must be bound using property, e.g. value + * Platform-dependent. + */ + mustUseProp: no, + + /** + * List of asset types that a component can own. + */ + _assetTypes: [ + 'component', + 'directive', + 'filter' + ], + + /** + * List of lifecycle hooks. + */ + _lifecycleHooks: [ + 'beforeCreate', + 'created', + 'beforeMount', + 'mounted', + 'beforeUpdate', + 'updated', + 'beforeDestroy', + 'destroyed', + 'activated', + 'deactivated' + ], + + /** + * Max circular updates allowed in a scheduler flush cycle. + */ + _maxUpdateCount: 100, + + /** + * Server rendering? + */ + _isServer: "client" === 'server' +}; + +/* */ + +/** + * Check if a string starts with $ or _ + */ +function isReserved (str) { + var c = (str + '').charCodeAt(0); + return c === 0x24 || c === 0x5F +} + +/** + * Define a property. + */ +function def (obj, key, val, enumerable) { + Object.defineProperty(obj, key, { + value: val, + enumerable: !!enumerable, + writable: true, + configurable: true + }); +} + +/** + * Parse simple path. + */ +var bailRE = /[^\w\.\$]/; +function parsePath (path) { + if (bailRE.test(path)) { + return + } else { + var segments = path.split('.'); + return function (obj) { + for (var i = 0; i < segments.length; i++) { + if (!obj) { return } + obj = obj[segments[i]]; } + return obj } - return val; } +} - /** - * Delete a property and trigger change if necessary. - * - * @param {Object} obj - * @param {String} key - */ +/* */ +/* globals MutationObserver */ - function del(obj, key) { - if (!hasOwn(obj, key)) { - return; - } - delete obj[key]; - var ob = obj.__ob__; - if (!ob) { - if (obj._isVue) { - delete obj._data[key]; - obj._digest(); - } - return; - } - ob.dep.notify(); - if (ob.vms) { - var i = ob.vms.length; - while (i--) { - var vm = ob.vms[i]; - vm._unproxy(key); - vm._digest(); - } +// can we use __proto__? +var hasProto = '__proto__' in {}; + +// Browser environment sniffing +var inBrowser = + typeof window !== 'undefined' && + Object.prototype.toString.call(window) !== '[object Object]'; + +var UA = inBrowser && window.navigator.userAgent.toLowerCase(); +var isIE = UA && /msie|trident/.test(UA); +var isIE9 = UA && UA.indexOf('msie 9.0') > 0; +var isEdge = UA && UA.indexOf('edge/') > 0; +var isAndroid = UA && UA.indexOf('android') > 0; +var isIOS = UA && /iphone|ipad|ipod|ios/.test(UA); + +// detect devtools +var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; + +/* istanbul ignore next */ +function isNative (Ctor) { + return /native code/.test(Ctor.toString()) +} + +/** + * Defer a task to execute it asynchronously. + */ +var nextTick = (function () { + var callbacks = []; + var pending = false; + var timerFunc; + + function nextTickHandler () { + pending = false; + var copies = callbacks.slice(0); + callbacks.length = 0; + for (var i = 0; i < copies.length; i++) { + copies[i](); } } - var hasOwnProperty = Object.prototype.hasOwnProperty; - /** - * Check whether the object has the property. - * - * @param {Object} obj - * @param {String} key - * @return {Boolean} - */ - - function hasOwn(obj, key) { - return hasOwnProperty.call(obj, key); - } - - /** - * Check if an expression is a literal value. - * - * @param {String} exp - * @return {Boolean} - */ - - var literalValueRE = /^\s?(true|false|-?[\d\.]+|'[^']*'|"[^"]*")\s?$/; - - function isLiteral(exp) { - return literalValueRE.test(exp); - } - - /** - * Check if a string starts with $ or _ - * - * @param {String} str - * @return {Boolean} - */ - - function isReserved(str) { - var c = (str + '').charCodeAt(0); - return c === 0x24 || c === 0x5F; - } - - /** - * Guard text output, make sure undefined outputs - * empty string - * - * @param {*} value - * @return {String} - */ - - function _toString(value) { - return value == null ? '' : value.toString(); - } - - /** - * Check and convert possible numeric strings to numbers - * before setting back to data - * - * @param {*} value - * @return {*|Number} - */ - - function toNumber(value) { - if (typeof value !== 'string') { - return value; - } else { - var parsed = Number(value); - return isNaN(parsed) ? value : parsed; - } - } - - /** - * Convert string boolean literals into real booleans. - * - * @param {*} value - * @return {*|Boolean} - */ - - function toBoolean(value) { - return value === 'true' ? true : value === 'false' ? false : value; - } - - /** - * Strip quotes from a string - * - * @param {String} str - * @return {String | false} - */ - - function stripQuotes(str) { - var a = str.charCodeAt(0); - var b = str.charCodeAt(str.length - 1); - return a === b && (a === 0x22 || a === 0x27) ? str.slice(1, -1) : str; - } - - /** - * Camelize a hyphen-delmited string. - * - * @param {String} str - * @return {String} - */ - - var camelizeRE = /-(\w)/g; - - function camelize(str) { - return str.replace(camelizeRE, toUpper); - } - - function toUpper(_, c) { - return c ? c.toUpperCase() : ''; - } - - /** - * Hyphenate a camelCase string. - * - * @param {String} str - * @return {String} - */ - - var hyphenateRE = /([a-z\d])([A-Z])/g; - - function hyphenate(str) { - return str.replace(hyphenateRE, '$1-$2').toLowerCase(); - } - - /** - * Converts hyphen/underscore/slash delimitered names into - * camelized classNames. - * - * e.g. my-component => MyComponent - * some_else => SomeElse - * some/comp => SomeComp - * - * @param {String} str - * @return {String} - */ - - var classifyRE = /(?:^|[-_\/])(\w)/g; - - function classify(str) { - return str.replace(classifyRE, toUpper); - } - - /** - * Simple bind, faster than native - * - * @param {Function} fn - * @param {Object} ctx - * @return {Function} - */ - - function bind(fn, ctx) { - return function (a) { - var l = arguments.length; - return l ? l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx); + // the nextTick behavior leverages the microtask queue, which can be accessed + // via either native Promise.then or MutationObserver. + // MutationObserver has wider support, however it is seriously bugged in + // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It + // completely stops working after triggering a few times... so, if native + // Promise is available, we will use it: + /* istanbul ignore if */ + if (typeof Promise !== 'undefined' && isNative(Promise)) { + var p = Promise.resolve(); + timerFunc = function () { + p.then(nextTickHandler); + // in problematic UIWebViews, Promise.then doesn't completely break, but + // it can get stuck in a weird state where callbacks are pushed into the + // microtask queue but the queue isn't being flushed, until the browser + // needs to do some other work, e.g. handle a timer. Therefore we can + // "force" the microtask queue to be flushed by adding an empty timer. + if (isIOS) { setTimeout(noop); } + }; + } else if (typeof MutationObserver !== 'undefined' && ( + isNative(MutationObserver) || + // PhantomJS and iOS 7.x + MutationObserver.toString() === '[object MutationObserverConstructor]' + )) { + // use MutationObserver where native Promise is not available, + // e.g. PhantomJS IE11, iOS7, Android 4.4 + var counter = 1; + var observer = new MutationObserver(nextTickHandler); + var textNode = document.createTextNode(String(counter)); + observer.observe(textNode, { + characterData: true + }); + timerFunc = function () { + counter = (counter + 1) % 2; + textNode.data = String(counter); + }; + } else { + // fallback to setTimeout + /* istanbul ignore next */ + timerFunc = function () { + setTimeout(nextTickHandler, 0); }; } - /** - * Convert an Array-like object to a real Array. - * - * @param {Array-like} list - * @param {Number} [start] - start index - * @return {Array} - */ - - function toArray(list, start) { - start = start || 0; - var i = list.length - start; - var ret = new Array(i); - while (i--) { - ret[i] = list[i + start]; + return function queueNextTick (cb, ctx) { + var func = ctx + ? function () { cb.call(ctx); } + : cb; + callbacks.push(func); + if (!pending) { + pending = true; + timerFunc(); } - return ret; } +})(); - /** - * Mix properties into target object. - * - * @param {Object} to - * @param {Object} from - */ - - function extend(to, from) { - var keys = Object.keys(from); - var i = keys.length; - while (i--) { - to[keys[i]] = from[keys[i]]; +var _Set; +/* istanbul ignore if */ +if (typeof Set !== 'undefined' && isNative(Set)) { + // use native Set when available. + _Set = Set; +} else { + // a non-standard Set polyfill that only works with primitive keys. + _Set = (function () { + function Set () { + this.set = Object.create(null); + } + Set.prototype.has = function has (key) { + return this.set[key] !== undefined + }; + Set.prototype.add = function add (key) { + this.set[key] = 1; + }; + Set.prototype.clear = function clear () { + this.set = Object.create(null); + }; + + return Set; + }()); +} + +/* not type checking this file because flow doesn't play well with Proxy */ + +var hasProxy; +var proxyHandlers; +var initProxy; + +{ + var allowedGlobals = makeMap( + 'Infinity,undefined,NaN,isFinite,isNaN,' + + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + + 'require' // for Webpack/Browserify + ); + + hasProxy = + typeof Proxy !== 'undefined' && + Proxy.toString().match(/native code/); + + proxyHandlers = { + has: function has (target, key) { + var has = key in target; + var isAllowed = allowedGlobals(key) || key.charAt(0) === '_'; + if (!has && !isAllowed) { + warn( + "Property or method \"" + key + "\" is not defined on the instance but " + + "referenced during render. Make sure to declare reactive data " + + "properties in the data option.", + target + ); + } + return has || !isAllowed + } + }; + + initProxy = function initProxy (vm) { + if (hasProxy) { + vm._renderProxy = new Proxy(vm, proxyHandlers); + } else { + vm._renderProxy = vm; + } + }; +} + +/* */ + + +var uid$2 = 0; + +/** + * A dep is an observable that can have multiple + * directives subscribing to it. + */ +var Dep = function Dep () { + this.id = uid$2++; + this.subs = []; +}; + +Dep.prototype.addSub = function addSub (sub) { + this.subs.push(sub); +}; + +Dep.prototype.removeSub = function removeSub (sub) { + remove$1(this.subs, sub); +}; + +Dep.prototype.depend = function depend () { + if (Dep.target) { + Dep.target.addDep(this); + } +}; + +Dep.prototype.notify = function notify () { + // stablize the subscriber list first + var subs = this.subs.slice(); + for (var i = 0, l = subs.length; i < l; i++) { + subs[i].update(); + } +}; + +// the current target watcher being evaluated. +// this is globally unique because there could be only one +// watcher being evaluated at any time. +Dep.target = null; +var targetStack = []; + +function pushTarget (_target) { + if (Dep.target) { targetStack.push(Dep.target); } + Dep.target = _target; +} + +function popTarget () { + Dep.target = targetStack.pop(); +} + +/* */ + + +var queue = []; +var has$1 = {}; +var circular = {}; +var waiting = false; +var flushing = false; +var index = 0; + +/** + * Reset the scheduler's state. + */ +function resetSchedulerState () { + queue.length = 0; + has$1 = {}; + { + circular = {}; + } + waiting = flushing = false; +} + +/** + * Flush both queues and run the watchers. + */ +function flushSchedulerQueue () { + flushing = true; + + // Sort queue before flush. + // This ensures that: + // 1. Components are updated from parent to child. (because parent is always + // created before the child) + // 2. A component's user watchers are run before its render watcher (because + // user watchers are created before the render watcher) + // 3. If a component is destroyed during a parent component's watcher run, + // its watchers can be skipped. + queue.sort(function (a, b) { return a.id - b.id; }); + + // do not cache length because more watchers might be pushed + // as we run existing watchers + for (index = 0; index < queue.length; index++) { + var watcher = queue[index]; + var id = watcher.id; + has$1[id] = null; + watcher.run(); + // in dev build, check and stop circular updates. + if ("development" !== 'production' && has$1[id] != null) { + circular[id] = (circular[id] || 0) + 1; + if (circular[id] > config._maxUpdateCount) { + warn( + 'You may have an infinite update loop ' + ( + watcher.user + ? ("in watcher with expression \"" + (watcher.expression) + "\"") + : "in a component render function." + ), + watcher.vm + ); + break + } } - return to; } - /** - * Quick object check - this is primarily used to tell - * Objects from primitive values when we know the value - * is a JSON-compliant type. - * - * @param {*} obj - * @return {Boolean} - */ - - function isObject(obj) { - return obj !== null && typeof obj === 'object'; + // devtool hook + /* istanbul ignore if */ + if (devtools && config.devtools) { + devtools.emit('flush'); } - /** - * Strict object type check. Only returns true - * for plain JavaScript objects. - * - * @param {*} obj - * @return {Boolean} - */ + resetSchedulerState(); +} - var toString = Object.prototype.toString; - var OBJECT_STRING = '[object Object]'; +/** + * Push a watcher into the watcher queue. + * Jobs with duplicate IDs will be skipped unless it's + * pushed when the queue is being flushed. + */ +function queueWatcher (watcher) { + var id = watcher.id; + if (has$1[id] == null) { + has$1[id] = true; + if (!flushing) { + queue.push(watcher); + } else { + // if already flushing, splice the watcher based on its id + // if already past its id, it will be run next immediately. + var i = queue.length - 1; + while (i >= 0 && queue[i].id > watcher.id) { + i--; + } + queue.splice(Math.max(i, index) + 1, 0, watcher); + } + // queue the flush + if (!waiting) { + waiting = true; + nextTick(flushSchedulerQueue); + } + } +} - function isPlainObject(obj) { - return toString.call(obj) === OBJECT_STRING; +/* */ + +var uid$1 = 0; + +/** + * A watcher parses an expression, collects dependencies, + * and fires callback when the expression value changes. + * This is used for both the $watch() api and directives. + */ +var Watcher = function Watcher ( + vm, + expOrFn, + cb, + options +) { + if ( options === void 0 ) options = {}; + + this.vm = vm; + vm._watchers.push(this); + // options + this.deep = !!options.deep; + this.user = !!options.user; + this.lazy = !!options.lazy; + this.sync = !!options.sync; + this.expression = expOrFn.toString(); + this.cb = cb; + this.id = ++uid$1; // uid for batching + this.active = true; + this.dirty = this.lazy; // for lazy watchers + this.deps = []; + this.newDeps = []; + this.depIds = new _Set(); + this.newDepIds = new _Set(); + // parse expression for getter + if (typeof expOrFn === 'function') { + this.getter = expOrFn; + } else { + this.getter = parsePath(expOrFn); + if (!this.getter) { + this.getter = function () {}; + "development" !== 'production' && warn( + "Failed watching path: \"" + expOrFn + "\" " + + 'Watcher only accepts simple dot-delimited paths. ' + + 'For full control, use a function instead.', + vm + ); + } + } + this.value = this.lazy + ? undefined + : this.get(); +}; + +/** + * Evaluate the getter, and re-collect dependencies. + */ +Watcher.prototype.get = function get () { + pushTarget(this); + var value = this.getter.call(this.vm, this.vm); + // "touch" every property so they are all tracked as + // dependencies for deep watching + if (this.deep) { + traverse(value); + } + popTarget(); + this.cleanupDeps(); + return value +}; + +/** + * Add a dependency to this directive. + */ +Watcher.prototype.addDep = function addDep (dep) { + var id = dep.id; + if (!this.newDepIds.has(id)) { + this.newDepIds.add(id); + this.newDeps.push(dep); + if (!this.depIds.has(id)) { + dep.addSub(this); + } + } +}; + +/** + * Clean up for dependency collection. + */ +Watcher.prototype.cleanupDeps = function cleanupDeps () { + var this$1 = this; + + var i = this.deps.length; + while (i--) { + var dep = this$1.deps[i]; + if (!this$1.newDepIds.has(dep.id)) { + dep.removeSub(this$1); + } + } + var tmp = this.depIds; + this.depIds = this.newDepIds; + this.newDepIds = tmp; + this.newDepIds.clear(); + tmp = this.deps; + this.deps = this.newDeps; + this.newDeps = tmp; + this.newDeps.length = 0; +}; + +/** + * Subscriber interface. + * Will be called when a dependency changes. + */ +Watcher.prototype.update = function update () { + /* istanbul ignore else */ + if (this.lazy) { + this.dirty = true; + } else if (this.sync) { + this.run(); + } else { + queueWatcher(this); + } +}; + +/** + * Scheduler job interface. + * Will be called by the scheduler. + */ +Watcher.prototype.run = function run () { + if (this.active) { + var value = this.get(); + if ( + value !== this.value || + // Deep watchers and watchers on Object/Arrays should fire even + // when the value is the same, because the value may + // have mutated. + isObject(value) || + this.deep + ) { + // set new value + var oldValue = this.value; + this.value = value; + if (this.user) { + try { + this.cb.call(this.vm, value, oldValue); + } catch (e) { + "development" !== 'production' && warn( + ("Error in watcher \"" + (this.expression) + "\""), + this.vm + ); + /* istanbul ignore else */ + if (config.errorHandler) { + config.errorHandler.call(null, e, this.vm); + } else { + throw e + } + } + } else { + this.cb.call(this.vm, value, oldValue); + } + } + } +}; + +/** + * Evaluate the value of the watcher. + * This only gets called for lazy watchers. + */ +Watcher.prototype.evaluate = function evaluate () { + this.value = this.get(); + this.dirty = false; +}; + +/** + * Depend on all deps collected by this watcher. + */ +Watcher.prototype.depend = function depend () { + var this$1 = this; + + var i = this.deps.length; + while (i--) { + this$1.deps[i].depend(); + } +}; + +/** + * Remove self from all dependencies' subcriber list. + */ +Watcher.prototype.teardown = function teardown () { + var this$1 = this; + + if (this.active) { + // remove self from vm's watcher list + // this is a somewhat expensive operation so we skip it + // if the vm is being destroyed or is performing a v-for + // re-render (the watcher list is then filtered by v-for). + if (!this.vm._isBeingDestroyed && !this.vm._vForRemoving) { + remove$1(this.vm._watchers, this); + } + var i = this.deps.length; + while (i--) { + this$1.deps[i].removeSub(this$1); + } + this.active = false; + } +}; + +/** + * Recursively traverse an object to evoke all converted + * getters, so that every nested property inside the object + * is collected as a "deep" dependency. + */ +var seenObjects = new _Set(); +function traverse (val, seen) { + var i, keys; + if (!seen) { + seen = seenObjects; + seen.clear(); + } + var isA = Array.isArray(val); + var isO = isObject(val); + if ((isA || isO) && Object.isExtensible(val)) { + if (val.__ob__) { + var depId = val.__ob__.dep.id; + if (seen.has(depId)) { + return + } else { + seen.add(depId); + } + } + if (isA) { + i = val.length; + while (i--) { traverse(val[i], seen); } + } else if (isO) { + keys = Object.keys(val); + i = keys.length; + while (i--) { traverse(val[keys[i]], seen); } + } + } +} + +/* + * not type checking this file because flow doesn't play well with + * dynamically accessing methods on Array prototype + */ + +var arrayProto = Array.prototype; +var arrayMethods = Object.create(arrayProto);[ + 'push', + 'pop', + 'shift', + 'unshift', + 'splice', + 'sort', + 'reverse' +] +.forEach(function (method) { + // cache original method + var original = arrayProto[method]; + def(arrayMethods, method, function mutator () { + var arguments$1 = arguments; + + // avoid leaking arguments: + // http://jsperf.com/closure-with-arguments + var i = arguments.length; + var args = new Array(i); + while (i--) { + args[i] = arguments$1[i]; + } + var result = original.apply(this, args); + var ob = this.__ob__; + var inserted; + switch (method) { + case 'push': + inserted = args; + break + case 'unshift': + inserted = args; + break + case 'splice': + inserted = args.slice(2); + break + } + if (inserted) { ob.observeArray(inserted); } + // notify change + ob.dep.notify(); + return result + }); +}); + +/* */ + +var arrayKeys = Object.getOwnPropertyNames(arrayMethods); + +/** + * By default, when a reactive property is set, the new value is + * also converted to become reactive. However when passing down props, + * we don't want to force conversion because the value may be a nested value + * under a frozen data structure. Converting it would defeat the optimization. + */ +var observerState = { + shouldConvert: true, + isSettingProps: false +}; + +/** + * Observer class that are attached to each observed + * object. Once attached, the observer converts target + * object's property keys into getter/setters that + * collect dependencies and dispatches updates. + */ +var Observer = function Observer (value) { + this.value = value; + this.dep = new Dep(); + this.vmCount = 0; + def(value, '__ob__', this); + if (Array.isArray(value)) { + var augment = hasProto + ? protoAugment + : copyAugment; + augment(value, arrayMethods, arrayKeys); + this.observeArray(value); + } else { + this.walk(value); + } +}; + +/** + * Walk through each property and convert them into + * getter/setters. This method should only be called when + * value type is Object. + */ +Observer.prototype.walk = function walk (obj) { + var keys = Object.keys(obj); + for (var i = 0; i < keys.length; i++) { + defineReactive$$1(obj, keys[i], obj[keys[i]]); + } +}; + +/** + * Observe a list of Array items. + */ +Observer.prototype.observeArray = function observeArray (items) { + for (var i = 0, l = items.length; i < l; i++) { + observe(items[i]); + } +}; + +// helpers + +/** + * Augment an target Object or Array by intercepting + * the prototype chain using __proto__ + */ +function protoAugment (target, src) { + /* eslint-disable no-proto */ + target.__proto__ = src; + /* eslint-enable no-proto */ +} + +/** + * Augment an target Object or Array by defining + * hidden properties. + * + * istanbul ignore next + */ +function copyAugment (target, src, keys) { + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + def(target, key, src[key]); + } +} + +/** + * Attempt to create an observer instance for a value, + * returns the new observer if successfully observed, + * or the existing observer if the value already has one. + */ +function observe (value) { + if (!isObject(value)) { + return + } + var ob; + if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { + ob = value.__ob__; + } else if ( + observerState.shouldConvert && + !config._isServer && + (Array.isArray(value) || isPlainObject(value)) && + Object.isExtensible(value) && + !value._isVue + ) { + ob = new Observer(value); + } + return ob +} + +/** + * Define a reactive property on an Object. + */ +function defineReactive$$1 ( + obj, + key, + val, + customSetter +) { + var dep = new Dep(); + + var property = Object.getOwnPropertyDescriptor(obj, key); + if (property && property.configurable === false) { + return } - /** - * Array type check. - * - * @param {*} obj - * @return {Boolean} - */ + // cater for pre-defined getter/setters + var getter = property && property.get; + var setter = property && property.set; - var isArray = Array.isArray; + var childOb = observe(val); + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter () { + var value = getter ? getter.call(obj) : val; + if (Dep.target) { + dep.depend(); + if (childOb) { + childOb.dep.depend(); + } + if (Array.isArray(value)) { + dependArray(value); + } + } + return value + }, + set: function reactiveSetter (newVal) { + var value = getter ? getter.call(obj) : val; + if (newVal === value) { + return + } + if ("development" !== 'production' && customSetter) { + customSetter(); + } + if (setter) { + setter.call(obj, newVal); + } else { + val = newVal; + } + childOb = observe(newVal); + dep.notify(); + } + }); +} + +/** + * Set a property on an object. Adds the new property and + * triggers change notification if the property doesn't + * already exist. + */ +function set (obj, key, val) { + if (Array.isArray(obj)) { + obj.splice(key, 1, val); + return val + } + if (hasOwn(obj, key)) { + obj[key] = val; + return + } + var ob = obj.__ob__; + if (obj._isVue || (ob && ob.vmCount)) { + "development" !== 'production' && warn( + 'Avoid adding reactive properties to a Vue instance or its root $data ' + + 'at runtime - declare it upfront in the data option.' + ); + return + } + if (!ob) { + obj[key] = val; + return + } + defineReactive$$1(ob.value, key, val); + ob.dep.notify(); + return val +} + +/** + * Delete a property and trigger change if necessary. + */ +function del (obj, key) { + var ob = obj.__ob__; + if (obj._isVue || (ob && ob.vmCount)) { + "development" !== 'production' && warn( + 'Avoid deleting properties on a Vue instance or its root $data ' + + '- just set it to null.' + ); + return + } + if (!hasOwn(obj, key)) { + return + } + delete obj[key]; + if (!ob) { + return + } + ob.dep.notify(); +} + +/** + * Collect dependencies on array elements when the array is touched, since + * we cannot intercept array element access like property getters. + */ +function dependArray (value) { + for (var e = void 0, i = 0, l = value.length; i < l; i++) { + e = value[i]; + e && e.__ob__ && e.__ob__.dep.depend(); + if (Array.isArray(e)) { + dependArray(e); + } + } +} + +/* */ + +function initState (vm) { + vm._watchers = []; + initProps(vm); + initData(vm); + initComputed(vm); + initMethods(vm); + initWatch(vm); +} + +function initProps (vm) { + var props = vm.$options.props; + if (props) { + var propsData = vm.$options.propsData || {}; + var keys = vm.$options._propKeys = Object.keys(props); + var isRoot = !vm.$parent; + // root instance props should be converted + observerState.shouldConvert = isRoot; + var loop = function ( i ) { + var key = keys[i]; + /* istanbul ignore else */ + { + defineReactive$$1(vm, key, validateProp(key, props, propsData, vm), function () { + if (vm.$parent && !observerState.isSettingProps) { + warn( + "Avoid mutating a prop directly since the value will be " + + "overwritten whenever the parent component re-renders. " + + "Instead, use a data or computed property based on the prop's " + + "value. Prop being mutated: \"" + key + "\"", + vm + ); + } + }); + } + }; + + for (var i = 0; i < keys.length; i++) loop( i ); + observerState.shouldConvert = true; + } +} + +function initData (vm) { + var data = vm.$options.data; + data = vm._data = typeof data === 'function' + ? data.call(vm) + : data || {}; + if (!isPlainObject(data)) { + data = {}; + "development" !== 'production' && warn( + 'data functions should return an object.', + vm + ); + } + // proxy data on instance + var keys = Object.keys(data); + var props = vm.$options.props; + var i = keys.length; + while (i--) { + if (props && hasOwn(props, keys[i])) { + "development" !== 'production' && warn( + "The data property \"" + (keys[i]) + "\" is already declared as a prop. " + + "Use prop default value instead.", + vm + ); + } else { + proxy(vm, keys[i]); + } + } + // observe data + observe(data); + data.__ob__ && data.__ob__.vmCount++; +} + +var computedSharedDefinition = { + enumerable: true, + configurable: true, + get: noop, + set: noop +}; + +function initComputed (vm) { + var computed = vm.$options.computed; + if (computed) { + for (var key in computed) { + var userDef = computed[key]; + if (typeof userDef === 'function') { + computedSharedDefinition.get = makeComputedGetter(userDef, vm); + computedSharedDefinition.set = noop; + } else { + computedSharedDefinition.get = userDef.get + ? userDef.cache !== false + ? makeComputedGetter(userDef.get, vm) + : bind$1(userDef.get, vm) + : noop; + computedSharedDefinition.set = userDef.set + ? bind$1(userDef.set, vm) + : noop; + } + Object.defineProperty(vm, key, computedSharedDefinition); + } + } +} + +function makeComputedGetter (getter, owner) { + var watcher = new Watcher(owner, getter, noop, { + lazy: true + }); + return function computedGetter () { + if (watcher.dirty) { + watcher.evaluate(); + } + if (Dep.target) { + watcher.depend(); + } + return watcher.value + } +} + +function initMethods (vm) { + var methods = vm.$options.methods; + if (methods) { + for (var key in methods) { + vm[key] = methods[key] == null ? noop : bind$1(methods[key], vm); + if ("development" !== 'production' && methods[key] == null) { + warn( + "method \"" + key + "\" has an undefined value in the component definition. " + + "Did you reference the function correctly?", + vm + ); + } + } + } +} + +function initWatch (vm) { + var watch = vm.$options.watch; + if (watch) { + for (var key in watch) { + var handler = watch[key]; + if (Array.isArray(handler)) { + for (var i = 0; i < handler.length; i++) { + createWatcher(vm, key, handler[i]); + } + } else { + createWatcher(vm, key, handler); + } + } + } +} + +function createWatcher (vm, key, handler) { + var options; + if (isPlainObject(handler)) { + options = handler; + handler = handler.handler; + } + if (typeof handler === 'string') { + handler = vm[handler]; + } + vm.$watch(key, handler, options); +} + +function stateMixin (Vue) { + // flow somehow has problems with directly declared definition object + // when using Object.defineProperty, so we have to procedurally build up + // the object here. + var dataDef = {}; + dataDef.get = function () { + return this._data + }; + { + dataDef.set = function (newData) { + warn( + 'Avoid replacing instance root $data. ' + + 'Use nested data properties instead.', + this + ); + }; + } + Object.defineProperty(Vue.prototype, '$data', dataDef); + + Vue.prototype.$set = set; + Vue.prototype.$delete = del; + + Vue.prototype.$watch = function ( + expOrFn, + cb, + options + ) { + var vm = this; + options = options || {}; + options.user = true; + var watcher = new Watcher(vm, expOrFn, cb, options); + if (options.immediate) { + cb.call(vm, watcher.value); + } + return function unwatchFn () { + watcher.teardown(); + } + }; +} + +function proxy (vm, key) { + if (!isReserved(key)) { + Object.defineProperty(vm, key, { + configurable: true, + enumerable: true, + get: function proxyGetter () { + return vm._data[key] + }, + set: function proxySetter (val) { + vm._data[key] = val; + } + }); + } +} + +/* */ + +var VNode = function VNode ( + tag, + data, + children, + text, + elm, + ns, + context, + componentOptions +) { + this.tag = tag; + this.data = data; + this.children = children; + this.text = text; + this.elm = elm; + this.ns = ns; + this.context = context; + this.functionalContext = undefined; + this.key = data && data.key; + this.componentOptions = componentOptions; + this.child = undefined; + this.parent = undefined; + this.raw = false; + this.isStatic = false; + this.isRootInsert = true; + this.isComment = false; + this.isCloned = false; +}; + +var emptyVNode = function () { + var node = new VNode(); + node.text = ''; + node.isComment = true; + return node +}; + +// optimized shallow clone +// used for static nodes and slot nodes because they may be reused across +// multiple renders, cloning them avoids errors when DOM manipulations rely +// on their elm reference. +function cloneVNode (vnode) { + var cloned = new VNode( + vnode.tag, + vnode.data, + vnode.children, + vnode.text, + vnode.elm, + vnode.ns, + vnode.context, + vnode.componentOptions + ); + cloned.isStatic = vnode.isStatic; + cloned.key = vnode.key; + cloned.isCloned = true; + return cloned +} + +function cloneVNodes (vnodes) { + var res = new Array(vnodes.length); + for (var i = 0; i < vnodes.length; i++) { + res[i] = cloneVNode(vnodes[i]); + } + return res +} + +/* */ + +function mergeVNodeHook (def, hookKey, hook, key) { + key = key + hookKey; + var injectedHash = def.__injected || (def.__injected = {}); + if (!injectedHash[key]) { + injectedHash[key] = true; + var oldHook = def[hookKey]; + if (oldHook) { + def[hookKey] = function () { + oldHook.apply(this, arguments); + hook.apply(this, arguments); + }; + } else { + def[hookKey] = hook; + } + } +} + +/* */ + +function updateListeners ( + on, + oldOn, + add, + remove$$1, + vm +) { + var name, cur, old, fn, event, capture; + for (name in on) { + cur = on[name]; + old = oldOn[name]; + if (!cur) { + "development" !== 'production' && warn( + "Invalid handler for event \"" + name + "\": got " + String(cur), + vm + ); + } else if (!old) { + capture = name.charAt(0) === '!'; + event = capture ? name.slice(1) : name; + if (Array.isArray(cur)) { + add(event, (cur.invoker = arrInvoker(cur)), capture); + } else { + if (!cur.invoker) { + fn = cur; + cur = on[name] = {}; + cur.fn = fn; + cur.invoker = fnInvoker(cur); + } + add(event, cur.invoker, capture); + } + } else if (cur !== old) { + if (Array.isArray(old)) { + old.length = cur.length; + for (var i = 0; i < old.length; i++) { old[i] = cur[i]; } + on[name] = old; + } else { + old.fn = cur; + on[name] = old; + } + } + } + for (name in oldOn) { + if (!on[name]) { + event = name.charAt(0) === '!' ? name.slice(1) : name; + remove$$1(event, oldOn[name].invoker); + } + } +} + +function arrInvoker (arr) { + return function (ev) { + var arguments$1 = arguments; + + var single = arguments.length === 1; + for (var i = 0; i < arr.length; i++) { + single ? arr[i](ev) : arr[i].apply(null, arguments$1); + } + } +} + +function fnInvoker (o) { + return function (ev) { + var single = arguments.length === 1; + single ? o.fn(ev) : o.fn.apply(null, arguments); + } +} + +/* */ + +function normalizeChildren ( + children, + ns, + nestedIndex +) { + if (isPrimitive(children)) { + return [createTextVNode(children)] + } + if (Array.isArray(children)) { + var res = []; + for (var i = 0, l = children.length; i < l; i++) { + var c = children[i]; + var last = res[res.length - 1]; + // nested + if (Array.isArray(c)) { + res.push.apply(res, normalizeChildren(c, ns, ((nestedIndex || '') + "_" + i))); + } else if (isPrimitive(c)) { + if (last && last.text) { + last.text += String(c); + } else if (c !== '') { + // convert primitive to vnode + res.push(createTextVNode(c)); + } + } else if (c instanceof VNode) { + if (c.text && last && last.text) { + last.text += c.text; + } else { + // inherit parent namespace + if (ns) { + applyNS(c, ns); + } + // default key for nested array children (likely generated by v-for) + if (c.tag && c.key == null && nestedIndex != null) { + c.key = "__vlist" + nestedIndex + "_" + i + "__"; + } + res.push(c); + } + } + } + return res + } +} + +function createTextVNode (val) { + return new VNode(undefined, undefined, undefined, String(val)) +} + +function applyNS (vnode, ns) { + if (vnode.tag && !vnode.ns) { + vnode.ns = ns; + if (vnode.children) { + for (var i = 0, l = vnode.children.length; i < l; i++) { + applyNS(vnode.children[i], ns); + } + } + } +} + +/* */ + +function getFirstComponentChild (children) { + return children && children.filter(function (c) { return c && c.componentOptions; })[0] +} + +/* */ + +var activeInstance = null; + +function initLifecycle (vm) { + var options = vm.$options; + + // locate first non-abstract parent + var parent = options.parent; + if (parent && !options.abstract) { + while (parent.$options.abstract && parent.$parent) { + parent = parent.$parent; + } + parent.$children.push(vm); + } + + vm.$parent = parent; + vm.$root = parent ? parent.$root : vm; + + vm.$children = []; + vm.$refs = {}; + + vm._watcher = null; + vm._inactive = false; + vm._isMounted = false; + vm._isDestroyed = false; + vm._isBeingDestroyed = false; +} + +function lifecycleMixin (Vue) { + Vue.prototype._mount = function ( + el, + hydrating + ) { + var vm = this; + vm.$el = el; + if (!vm.$options.render) { + vm.$options.render = emptyVNode; + { + /* istanbul ignore if */ + if (vm.$options.template) { + warn( + 'You are using the runtime-only build of Vue where the template ' + + 'option is not available. Either pre-compile the templates into ' + + 'render functions, or use the compiler-included build.', + vm + ); + } else { + warn( + 'Failed to mount component: template or render function not defined.', + vm + ); + } + } + } + callHook(vm, 'beforeMount'); + vm._watcher = new Watcher(vm, function () { + vm._update(vm._render(), hydrating); + }, noop); + hydrating = false; + // manually mounted instance, call mounted on self + // mounted is called for render-created child components in its inserted hook + if (vm.$vnode == null) { + vm._isMounted = true; + callHook(vm, 'mounted'); + } + return vm + }; + + Vue.prototype._update = function (vnode, hydrating) { + var vm = this; + if (vm._isMounted) { + callHook(vm, 'beforeUpdate'); + } + var prevEl = vm.$el; + var prevActiveInstance = activeInstance; + activeInstance = vm; + var prevVnode = vm._vnode; + vm._vnode = vnode; + if (!prevVnode) { + // Vue.prototype.__patch__ is injected in entry points + // based on the rendering backend used. + vm.$el = vm.__patch__(vm.$el, vnode, hydrating); + } else { + vm.$el = vm.__patch__(prevVnode, vnode); + } + activeInstance = prevActiveInstance; + // update __vue__ reference + if (prevEl) { + prevEl.__vue__ = null; + } + if (vm.$el) { + vm.$el.__vue__ = vm; + } + // if parent is an HOC, update its $el as well + if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { + vm.$parent.$el = vm.$el; + } + if (vm._isMounted) { + callHook(vm, 'updated'); + } + }; + + Vue.prototype._updateFromParent = function ( + propsData, + listeners, + parentVnode, + renderChildren + ) { + var vm = this; + var hasChildren = !!(vm.$options._renderChildren || renderChildren); + vm.$options._parentVnode = parentVnode; + vm.$options._renderChildren = renderChildren; + // update props + if (propsData && vm.$options.props) { + observerState.shouldConvert = false; + { + observerState.isSettingProps = true; + } + var propKeys = vm.$options._propKeys || []; + for (var i = 0; i < propKeys.length; i++) { + var key = propKeys[i]; + vm[key] = validateProp(key, vm.$options.props, propsData, vm); + } + observerState.shouldConvert = true; + { + observerState.isSettingProps = false; + } + } + // update listeners + if (listeners) { + var oldListeners = vm.$options._parentListeners; + vm.$options._parentListeners = listeners; + vm._updateListeners(listeners, oldListeners); + } + // resolve slots + force update if has children + if (hasChildren) { + vm.$slots = resolveSlots(renderChildren, vm._renderContext); + vm.$forceUpdate(); + } + }; + + Vue.prototype.$forceUpdate = function () { + var vm = this; + if (vm._watcher) { + vm._watcher.update(); + } + }; + + Vue.prototype.$destroy = function () { + var vm = this; + if (vm._isBeingDestroyed) { + return + } + callHook(vm, 'beforeDestroy'); + vm._isBeingDestroyed = true; + // remove self from parent + var parent = vm.$parent; + if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { + remove$1(parent.$children, vm); + } + // teardown watchers + if (vm._watcher) { + vm._watcher.teardown(); + } + var i = vm._watchers.length; + while (i--) { + vm._watchers[i].teardown(); + } + // remove reference from data ob + // frozen object may not have observer. + if (vm._data.__ob__) { + vm._data.__ob__.vmCount--; + } + // call the last hook... + vm._isDestroyed = true; + callHook(vm, 'destroyed'); + // turn off all instance listeners. + vm.$off(); + // remove __vue__ reference + if (vm.$el) { + vm.$el.__vue__ = null; + } + // invoke destroy hooks on current rendered tree + vm.__patch__(vm._vnode, null); + }; +} + +function callHook (vm, hook) { + var handlers = vm.$options[hook]; + if (handlers) { + for (var i = 0, j = handlers.length; i < j; i++) { + handlers[i].call(vm); + } + } + vm.$emit('hook:' + hook); +} + +/* */ + +var hooks = { init: init, prepatch: prepatch, insert: insert, destroy: destroy$1 }; +var hooksToMerge = Object.keys(hooks); + +function createComponent ( + Ctor, + data, + context, + children, + tag +) { + if (!Ctor) { + return + } + + if (isObject(Ctor)) { + Ctor = Vue$3.extend(Ctor); + } + + if (typeof Ctor !== 'function') { + { + warn(("Invalid Component definition: " + (String(Ctor))), context); + } + return + } + + // async component + if (!Ctor.cid) { + if (Ctor.resolved) { + Ctor = Ctor.resolved; + } else { + Ctor = resolveAsyncComponent(Ctor, function () { + // it's ok to queue this on every render because + // $forceUpdate is buffered by the scheduler. + context.$forceUpdate(); + }); + if (!Ctor) { + // return nothing if this is indeed an async component + // wait for the callback to trigger parent update. + return + } + } + } + + data = data || {}; + + // extract props + var propsData = extractProps(data, Ctor); + + // functional component + if (Ctor.options.functional) { + return createFunctionalComponent(Ctor, propsData, data, context, children) + } + + // extract listeners, since these needs to be treated as + // child component listeners instead of DOM listeners + var listeners = data.on; + // replace with listeners with .native modifier + data.on = data.nativeOn; + + if (Ctor.options.abstract) { + // abstract components do not keep anything + // other than props & listeners + data = {}; + } + + // merge component management hooks onto the placeholder node + mergeHooks(data); + + // return a placeholder vnode + var name = Ctor.options.name || tag; + var vnode = new VNode( + ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')), + data, undefined, undefined, undefined, undefined, context, + { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children } + ); + return vnode +} + +function createFunctionalComponent ( + Ctor, + propsData, + data, + context, + children +) { + var props = {}; + var propOptions = Ctor.options.props; + if (propOptions) { + for (var key in propOptions) { + props[key] = validateProp(key, propOptions, propsData); + } + } + var vnode = Ctor.options.render.call( + null, + // ensure the createElement function in functional components + // gets a unique context - this is necessary for correct named slot check + bind$1(createElement, { _self: Object.create(context) }), + { + props: props, + data: data, + parent: context, + children: normalizeChildren(children), + slots: function () { return resolveSlots(children, context); } + } + ); + if (vnode instanceof VNode) { + vnode.functionalContext = context; + if (data.slot) { + (vnode.data || (vnode.data = {})).slot = data.slot; + } + } + return vnode +} + +function createComponentInstanceForVnode ( + vnode, // we know it's MountedComponentVNode but flow doesn't + parent // activeInstance in lifecycle state +) { + var vnodeComponentOptions = vnode.componentOptions; + var options = { + _isComponent: true, + parent: parent, + propsData: vnodeComponentOptions.propsData, + _componentTag: vnodeComponentOptions.tag, + _parentVnode: vnode, + _parentListeners: vnodeComponentOptions.listeners, + _renderChildren: vnodeComponentOptions.children + }; + // check inline-template render functions + var inlineTemplate = vnode.data.inlineTemplate; + if (inlineTemplate) { + options.render = inlineTemplate.render; + options.staticRenderFns = inlineTemplate.staticRenderFns; + } + return new vnodeComponentOptions.Ctor(options) +} + +function init (vnode, hydrating) { + if (!vnode.child || vnode.child._isDestroyed) { + var child = vnode.child = createComponentInstanceForVnode(vnode, activeInstance); + child.$mount(hydrating ? vnode.elm : undefined, hydrating); + } +} + +function prepatch ( + oldVnode, + vnode +) { + var options = vnode.componentOptions; + var child = vnode.child = oldVnode.child; + child._updateFromParent( + options.propsData, // updated props + options.listeners, // updated listeners + vnode, // new parent vnode + options.children // new children + ); +} + +function insert (vnode) { + if (!vnode.child._isMounted) { + vnode.child._isMounted = true; + callHook(vnode.child, 'mounted'); + } + if (vnode.data.keepAlive) { + vnode.child._inactive = false; + callHook(vnode.child, 'activated'); + } +} + +function destroy$1 (vnode) { + if (!vnode.child._isDestroyed) { + if (!vnode.data.keepAlive) { + vnode.child.$destroy(); + } else { + vnode.child._inactive = true; + callHook(vnode.child, 'deactivated'); + } + } +} + +function resolveAsyncComponent ( + factory, + cb +) { + if (factory.requested) { + // pool callbacks + factory.pendingCallbacks.push(cb); + } else { + factory.requested = true; + var cbs = factory.pendingCallbacks = [cb]; + var sync = true; + + var resolve = function (res) { + if (isObject(res)) { + res = Vue$3.extend(res); + } + // cache resolved + factory.resolved = res; + // invoke callbacks only if this is not a synchronous resolve + // (async resolves are shimmed as synchronous during SSR) + if (!sync) { + for (var i = 0, l = cbs.length; i < l; i++) { + cbs[i](res); + } + } + }; + + var reject = function (reason) { + "development" !== 'production' && warn( + "Failed to resolve async component: " + (String(factory)) + + (reason ? ("\nReason: " + reason) : '') + ); + }; + + var res = factory(resolve, reject); + + // handle promise + if (res && typeof res.then === 'function' && !factory.resolved) { + res.then(resolve, reject); + } + + sync = false; + // return in case resolved synchronously + return factory.resolved + } +} + +function extractProps (data, Ctor) { + // we are only extrating raw values here. + // validation and default values are handled in the child + // component itself. + var propOptions = Ctor.options.props; + if (!propOptions) { + return + } + var res = {}; + var attrs = data.attrs; + var props = data.props; + var domProps = data.domProps; + if (attrs || props || domProps) { + for (var key in propOptions) { + var altKey = hyphenate(key); + checkProp(res, props, key, altKey, true) || + checkProp(res, attrs, key, altKey) || + checkProp(res, domProps, key, altKey); + } + } + return res +} + +function checkProp ( + res, + hash, + key, + altKey, + preserve +) { + if (hash) { + if (hasOwn(hash, key)) { + res[key] = hash[key]; + if (!preserve) { + delete hash[key]; + } + return true + } else if (hasOwn(hash, altKey)) { + res[key] = hash[altKey]; + if (!preserve) { + delete hash[altKey]; + } + return true + } + } + return false +} + +function mergeHooks (data) { + if (!data.hook) { + data.hook = {}; + } + for (var i = 0; i < hooksToMerge.length; i++) { + var key = hooksToMerge[i]; + var fromParent = data.hook[key]; + var ours = hooks[key]; + data.hook[key] = fromParent ? mergeHook$1(ours, fromParent) : ours; + } +} + +function mergeHook$1 (a, b) { + // since all hooks have at most two args, use fixed args + // to avoid having to use fn.apply(). + return function (_, __) { + a(_, __); + b(_, __); + } +} + +/* */ + +// wrapper function for providing a more flexible interface +// without getting yelled at by flow +function createElement ( + tag, + data, + children +) { + if (data && (Array.isArray(data) || typeof data !== 'object')) { + children = data; + data = undefined; + } + // make sure to use real instance instead of proxy as context + return _createElement(this._self, tag, data, children) +} + +function _createElement ( + context, + tag, + data, + children +) { + if (data && data.__ob__) { + "development" !== 'production' && warn( + "Avoid using observed data object as vnode data: " + (JSON.stringify(data)) + "\n" + + 'Always create fresh vnode data objects in each render!', + context + ); + return + } + if (!tag) { + // in case of component :is set to falsy value + return emptyVNode() + } + if (typeof tag === 'string') { + var Ctor; + var ns = config.getTagNamespace(tag); + if (config.isReservedTag(tag)) { + // platform built-in elements + return new VNode( + tag, data, normalizeChildren(children, ns), + undefined, undefined, ns, context + ) + } else if ((Ctor = resolveAsset(context.$options, 'components', tag))) { + // component + return createComponent(Ctor, data, context, children, tag) + } else { + // unknown or unlisted namespaced elements + // check at runtime because it may get assigned a namespace when its + // parent normalizes children + return new VNode( + tag, data, normalizeChildren(children, ns), + undefined, undefined, ns, context + ) + } + } else { + // direct component options / constructor + return createComponent(tag, data, context, children) + } +} + +/* */ + +function initRender (vm) { + vm.$vnode = null; // the placeholder node in parent tree + vm._vnode = null; // the root of the child tree + vm._staticTrees = null; + vm._renderContext = vm.$options._parentVnode && vm.$options._parentVnode.context; + vm.$slots = resolveSlots(vm.$options._renderChildren, vm._renderContext); + // bind the public createElement fn to this instance + // so that we get proper render context inside it. + vm.$createElement = bind$1(createElement, vm); + if (vm.$options.el) { + vm.$mount(vm.$options.el); + } +} + +function renderMixin (Vue) { + Vue.prototype.$nextTick = function (fn) { + nextTick(fn, this); + }; + + Vue.prototype._render = function () { + var vm = this; + var ref = vm.$options; + var render = ref.render; + var staticRenderFns = ref.staticRenderFns; + var _parentVnode = ref._parentVnode; + + if (vm._isMounted) { + // clone slot nodes on re-renders + for (var key in vm.$slots) { + vm.$slots[key] = cloneVNodes(vm.$slots[key]); + } + } + + if (staticRenderFns && !vm._staticTrees) { + vm._staticTrees = []; + } + // set parent vnode. this allows render functions to have access + // to the data on the placeholder node. + vm.$vnode = _parentVnode; + // render self + var vnode; + try { + vnode = render.call(vm._renderProxy, vm.$createElement); + } catch (e) { + { + warn(("Error when rendering " + (formatComponentName(vm)) + ":")); + } + /* istanbul ignore else */ + if (config.errorHandler) { + config.errorHandler.call(null, e, vm); + } else { + if (config._isServer) { + throw e + } else { + setTimeout(function () { throw e }, 0); + } + } + // return previous vnode to prevent render error causing blank component + vnode = vm._vnode; + } + // return empty vnode in case the render function errored out + if (!(vnode instanceof VNode)) { + if ("development" !== 'production' && Array.isArray(vnode)) { + warn( + 'Multiple root nodes returned from render function. Render function ' + + 'should return a single root node.', + vm + ); + } + vnode = emptyVNode(); + } + // set parent + vnode.parent = _parentVnode; + return vnode + }; + + // shorthands used in render functions + Vue.prototype._h = createElement; + // toString for mustaches + Vue.prototype._s = _toString; + // number conversion + Vue.prototype._n = toNumber; + // empty vnode + Vue.prototype._e = emptyVNode; + // loose equal + Vue.prototype._q = looseEqual; + // loose indexOf + Vue.prototype._i = looseIndexOf; + + // render static tree by index + Vue.prototype._m = function renderStatic ( + index, + isInFor + ) { + var tree = this._staticTrees[index]; + // if has already-rendered static tree and not inside v-for, + // we can reuse the same tree by doing a shallow clone. + if (tree && !isInFor) { + return Array.isArray(tree) + ? cloneVNodes(tree) + : cloneVNode(tree) + } + // otherwise, render a fresh tree. + tree = this._staticTrees[index] = this.$options.staticRenderFns[index].call(this._renderProxy); + if (Array.isArray(tree)) { + for (var i = 0; i < tree.length; i++) { + if (typeof tree[i] !== 'string') { + tree[i].isStatic = true; + tree[i].key = "__static__" + index + "_" + i; + } + } + } else { + tree.isStatic = true; + tree.key = "__static__" + index; + } + return tree + }; + + // filter resolution helper + var identity = function (_) { return _; }; + Vue.prototype._f = function resolveFilter (id) { + return resolveAsset(this.$options, 'filters', id, true) || identity + }; + + // render v-for + Vue.prototype._l = function renderList ( + val, + render + ) { + var ret, i, l, keys, key; + if (Array.isArray(val)) { + ret = new Array(val.length); + for (i = 0, l = val.length; i < l; i++) { + ret[i] = render(val[i], i); + } + } else if (typeof val === 'number') { + ret = new Array(val); + for (i = 0; i < val; i++) { + ret[i] = render(i + 1, i); + } + } else if (isObject(val)) { + keys = Object.keys(val); + ret = new Array(keys.length); + for (i = 0, l = keys.length; i < l; i++) { + key = keys[i]; + ret[i] = render(val[key], key, i); + } + } + return ret + }; + + // renderSlot + Vue.prototype._t = function ( + name, + fallback + ) { + var slotNodes = this.$slots[name]; + // warn duplicate slot usage + if (slotNodes && "development" !== 'production') { + slotNodes._rendered && warn( + "Duplicate presence of slot \"" + name + "\" found in the same render tree " + + "- this will likely cause render errors.", + this + ); + slotNodes._rendered = true; + } + return slotNodes || fallback + }; + + // apply v-bind object + Vue.prototype._b = function bindProps ( + data, + value, + asProp + ) { + if (value) { + if (!isObject(value)) { + "development" !== 'production' && warn( + 'v-bind without argument expects an Object or Array value', + this + ); + } else { + if (Array.isArray(value)) { + value = toObject(value); + } + for (var key in value) { + if (key === 'class' || key === 'style') { + data[key] = value[key]; + } else { + var hash = asProp || config.mustUseProp(key) + ? data.domProps || (data.domProps = {}) + : data.attrs || (data.attrs = {}); + hash[key] = value[key]; + } + } + } + } + return data + }; + + // expose v-on keyCodes + Vue.prototype._k = function getKeyCodes (key) { + return config.keyCodes[key] + }; +} + +function resolveSlots ( + renderChildren, + context +) { + var slots = {}; + if (!renderChildren) { + return slots + } + var children = normalizeChildren(renderChildren) || []; + var defaultSlot = []; + var name, child; + for (var i = 0, l = children.length; i < l; i++) { + child = children[i]; + // named slots should only be respected if the vnode was rendered in the + // same context. + if ((child.context === context || child.functionalContext === context) && + child.data && (name = child.data.slot)) { + var slot = (slots[name] || (slots[name] = [])); + if (child.tag === 'template') { + slot.push.apply(slot, child.children); + } else { + slot.push(child); + } + } else { + defaultSlot.push(child); + } + } + // ignore single whitespace + if (defaultSlot.length && !( + defaultSlot.length === 1 && + (defaultSlot[0].text === ' ' || defaultSlot[0].isComment) + )) { + slots.default = defaultSlot; + } + return slots +} + +/* */ + +function initEvents (vm) { + vm._events = Object.create(null); + // init parent attached events + var listeners = vm.$options._parentListeners; + var on = bind$1(vm.$on, vm); + var off = bind$1(vm.$off, vm); + vm._updateListeners = function (listeners, oldListeners) { + updateListeners(listeners, oldListeners || {}, on, off, vm); + }; + if (listeners) { + vm._updateListeners(listeners); + } +} + +function eventsMixin (Vue) { + Vue.prototype.$on = function (event, fn) { + var vm = this;(vm._events[event] || (vm._events[event] = [])).push(fn); + return vm + }; + + Vue.prototype.$once = function (event, fn) { + var vm = this; + function on () { + vm.$off(event, on); + fn.apply(vm, arguments); + } + on.fn = fn; + vm.$on(event, on); + return vm + }; + + Vue.prototype.$off = function (event, fn) { + var vm = this; + // all + if (!arguments.length) { + vm._events = Object.create(null); + return vm + } + // specific event + var cbs = vm._events[event]; + if (!cbs) { + return vm + } + if (arguments.length === 1) { + vm._events[event] = null; + return vm + } + // specific handler + var cb; + var i = cbs.length; + while (i--) { + cb = cbs[i]; + if (cb === fn || cb.fn === fn) { + cbs.splice(i, 1); + break + } + } + return vm + }; + + Vue.prototype.$emit = function (event) { + var vm = this; + var cbs = vm._events[event]; + if (cbs) { + cbs = cbs.length > 1 ? toArray(cbs) : cbs; + var args = toArray(arguments, 1); + for (var i = 0, l = cbs.length; i < l; i++) { + cbs[i].apply(vm, args); + } + } + return vm + }; +} + +/* */ + +var uid = 0; + +function initMixin (Vue) { + Vue.prototype._init = function (options) { + var vm = this; + // a uid + vm._uid = uid++; + // a flag to avoid this being observed + vm._isVue = true; + // merge options + if (options && options._isComponent) { + // optimize internal component instantiation + // since dynamic options merging is pretty slow, and none of the + // internal component options needs special treatment. + initInternalComponent(vm, options); + } else { + vm.$options = mergeOptions( + resolveConstructorOptions(vm), + options || {}, + vm + ); + } + /* istanbul ignore else */ + { + initProxy(vm); + } + // expose real self + vm._self = vm; + initLifecycle(vm); + initEvents(vm); + callHook(vm, 'beforeCreate'); + initState(vm); + callHook(vm, 'created'); + initRender(vm); + }; + + function initInternalComponent (vm, options) { + var opts = vm.$options = Object.create(resolveConstructorOptions(vm)); + // doing this because it's faster than dynamic enumeration. + opts.parent = options.parent; + opts.propsData = options.propsData; + opts._parentVnode = options._parentVnode; + opts._parentListeners = options._parentListeners; + opts._renderChildren = options._renderChildren; + opts._componentTag = options._componentTag; + if (options.render) { + opts.render = options.render; + opts.staticRenderFns = options.staticRenderFns; + } + } + + function resolveConstructorOptions (vm) { + var Ctor = vm.constructor; + var options = Ctor.options; + if (Ctor.super) { + var superOptions = Ctor.super.options; + var cachedSuperOptions = Ctor.superOptions; + if (superOptions !== cachedSuperOptions) { + // super option changed + Ctor.superOptions = superOptions; + options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions); + if (options.name) { + options.components[options.name] = Ctor; + } + } + } + return options + } +} + +function Vue$3 (options) { + if ("development" !== 'production' && + !(this instanceof Vue$3)) { + warn('Vue is a constructor and should be called with the `new` keyword'); + } + this._init(options); +} + +initMixin(Vue$3); +stateMixin(Vue$3); +eventsMixin(Vue$3); +lifecycleMixin(Vue$3); +renderMixin(Vue$3); + +var warn = noop; +var formatComponentName; + +{ + var hasConsole = typeof console !== 'undefined'; + + warn = function (msg, vm) { + if (hasConsole && (!config.silent)) { + console.error("[Vue warn]: " + msg + " " + ( + vm ? formatLocation(formatComponentName(vm)) : '' + )); + } + }; + + formatComponentName = function (vm) { + if (vm.$root === vm) { + return 'root instance' + } + var name = vm._isVue + ? vm.$options.name || vm.$options._componentTag + : vm.name; + return ( + (name ? ("component <" + name + ">") : "anonymous component") + + (vm._isVue && vm.$options.__file ? (" at " + (vm.$options.__file)) : '') + ) + }; + + var formatLocation = function (str) { + if (str === 'anonymous component') { + str += " - use the \"name\" option for better debugging messages."; + } + return ("\n(found in " + str + ")") + }; +} + +/* */ + +/** + * Option overwriting strategies are functions that handle + * how to merge a parent option value and a child option + * value into the final value. + */ +var strats = config.optionMergeStrategies; + +/** + * Options with restrictions + */ +{ + strats.el = strats.propsData = function (parent, child, vm, key) { + if (!vm) { + warn( + "option \"" + key + "\" can only be used during instance " + + 'creation with the `new` keyword.' + ); + } + return defaultStrat(parent, child) + }; +} + +/** + * Helper that recursively merges two data objects together. + */ +function mergeData (to, from) { + var key, toVal, fromVal; + for (key in from) { + toVal = to[key]; + fromVal = from[key]; + if (!hasOwn(to, key)) { + set(to, key, fromVal); + } else if (isObject(toVal) && isObject(fromVal)) { + mergeData(toVal, fromVal); + } + } + return to +} + +/** + * Data + */ +strats.data = function ( + parentVal, + childVal, + vm +) { + if (!vm) { + // in a Vue.extend merge, both should be functions + if (!childVal) { + return parentVal + } + if (typeof childVal !== 'function') { + "development" !== 'production' && warn( + 'The "data" option should be a function ' + + 'that returns a per-instance value in component ' + + 'definitions.', + vm + ); + return parentVal + } + if (!parentVal) { + return childVal + } + // when parentVal & childVal are both present, + // we need to return a function that returns the + // merged result of both functions... no need to + // check if parentVal is a function here because + // it has to be a function to pass previous merges. + return function mergedDataFn () { + return mergeData( + childVal.call(this), + parentVal.call(this) + ) + } + } else if (parentVal || childVal) { + return function mergedInstanceDataFn () { + // instance merge + var instanceData = typeof childVal === 'function' + ? childVal.call(vm) + : childVal; + var defaultData = typeof parentVal === 'function' + ? parentVal.call(vm) + : undefined; + if (instanceData) { + return mergeData(instanceData, defaultData) + } else { + return defaultData + } + } + } +}; + +/** + * Hooks and param attributes are merged as arrays. + */ +function mergeHook ( + parentVal, + childVal +) { + return childVal + ? parentVal + ? parentVal.concat(childVal) + : Array.isArray(childVal) + ? childVal + : [childVal] + : parentVal +} + +config._lifecycleHooks.forEach(function (hook) { + strats[hook] = mergeHook; +}); + +/** + * Assets + * + * When a vm is present (instance creation), we need to do + * a three-way merge between constructor options, instance + * options and parent options. + */ +function mergeAssets (parentVal, childVal) { + var res = Object.create(parentVal || null); + return childVal + ? extend(res, childVal) + : res +} + +config._assetTypes.forEach(function (type) { + strats[type + 's'] = mergeAssets; +}); + +/** + * Watchers. + * + * Watchers hashes should not overwrite one + * another, so we merge them as arrays. + */ +strats.watch = function (parentVal, childVal) { + /* istanbul ignore if */ + if (!childVal) { return parentVal } + if (!parentVal) { return childVal } + var ret = {}; + extend(ret, parentVal); + for (var key in childVal) { + var parent = ret[key]; + var child = childVal[key]; + if (parent && !Array.isArray(parent)) { + parent = [parent]; + } + ret[key] = parent + ? parent.concat(child) + : [child]; + } + return ret +}; + +/** + * Other object hashes. + */ +strats.props = +strats.methods = +strats.computed = function (parentVal, childVal) { + if (!childVal) { return parentVal } + if (!parentVal) { return childVal } + var ret = Object.create(null); + extend(ret, parentVal); + extend(ret, childVal); + return ret +}; + +/** + * Default strategy. + */ +var defaultStrat = function (parentVal, childVal) { + return childVal === undefined + ? parentVal + : childVal +}; + +/** + * Make sure component options get converted to actual + * constructors. + */ +function normalizeComponents (options) { + if (options.components) { + var components = options.components; + var def; + for (var key in components) { + var lower = key.toLowerCase(); + if (isBuiltInTag(lower) || config.isReservedTag(lower)) { + "development" !== 'production' && warn( + 'Do not use built-in or reserved HTML elements as component ' + + 'id: ' + key + ); + continue + } + def = components[key]; + if (isPlainObject(def)) { + components[key] = Vue$3.extend(def); + } + } + } +} + +/** + * Ensure all props option syntax are normalized into the + * Object-based format. + */ +function normalizeProps (options) { + var props = options.props; + if (!props) { return } + var res = {}; + var i, val, name; + if (Array.isArray(props)) { + i = props.length; + while (i--) { + val = props[i]; + if (typeof val === 'string') { + name = camelize(val); + res[name] = { type: null }; + } else { + warn('props must be strings when using array syntax.'); + } + } + } else if (isPlainObject(props)) { + for (var key in props) { + val = props[key]; + name = camelize(key); + res[name] = isPlainObject(val) + ? val + : { type: val }; + } + } + options.props = res; +} + +/** + * Normalize raw function directives into object format. + */ +function normalizeDirectives (options) { + var dirs = options.directives; + if (dirs) { + for (var key in dirs) { + var def = dirs[key]; + if (typeof def === 'function') { + dirs[key] = { bind: def, update: def }; + } + } + } +} + +/** + * Merge two option objects into a new one. + * Core utility used in both instantiation and inheritance. + */ +function mergeOptions ( + parent, + child, + vm +) { + normalizeComponents(child); + normalizeProps(child); + normalizeDirectives(child); + var extendsFrom = child.extends; + if (extendsFrom) { + parent = typeof extendsFrom === 'function' + ? mergeOptions(parent, extendsFrom.options, vm) + : mergeOptions(parent, extendsFrom, vm); + } + if (child.mixins) { + for (var i = 0, l = child.mixins.length; i < l; i++) { + var mixin = child.mixins[i]; + if (mixin.prototype instanceof Vue$3) { + mixin = mixin.options; + } + parent = mergeOptions(parent, mixin, vm); + } + } + var options = {}; + var key; + for (key in parent) { + mergeField(key); + } + for (key in child) { + if (!hasOwn(parent, key)) { + mergeField(key); + } + } + function mergeField (key) { + var strat = strats[key] || defaultStrat; + options[key] = strat(parent[key], child[key], vm, key); + } + return options +} + +/** + * Resolve an asset. + * This function is used because child instances need access + * to assets defined in its ancestor chain. + */ +function resolveAsset ( + options, + type, + id, + warnMissing +) { + /* istanbul ignore if */ + if (typeof id !== 'string') { + return + } + var assets = options[type]; + var res = assets[id] || + // camelCase ID + assets[camelize(id)] || + // Pascal Case ID + assets[capitalize(camelize(id))]; + if ("development" !== 'production' && warnMissing && !res) { + warn( + 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, + options + ); + } + return res +} + +/* */ + +function validateProp ( + key, + propOptions, + propsData, + vm +) { + var prop = propOptions[key]; + var absent = !hasOwn(propsData, key); + var value = propsData[key]; + // handle boolean props + if (isBooleanType(prop.type)) { + if (absent && !hasOwn(prop, 'default')) { + value = false; + } else if (value === '' || value === hyphenate(key)) { + value = true; + } + } + // check default value + if (value === undefined) { + value = getPropDefaultValue(vm, prop, key); + // since the default value is a fresh copy, + // make sure to observe it. + var prevShouldConvert = observerState.shouldConvert; + observerState.shouldConvert = true; + observe(value); + observerState.shouldConvert = prevShouldConvert; + } + { + assertProp(prop, key, value, vm, absent); + } + return value +} + +/** + * Get the default value of a prop. + */ +function getPropDefaultValue (vm, prop, name) { + // no default, return undefined + if (!hasOwn(prop, 'default')) { + return undefined + } + var def = prop.default; + // warn against non-factory defaults for Object & Array + if (isObject(def)) { + "development" !== 'production' && warn( + 'Invalid default value for prop "' + name + '": ' + + 'Props with type Object/Array must use a factory function ' + + 'to return the default value.', + vm + ); + } + // call factory function for non-Function types + return typeof def === 'function' && prop.type !== Function + ? def.call(vm) + : def +} + +/** + * Assert whether a prop is valid. + */ +function assertProp ( + prop, + name, + value, + vm, + absent +) { + if (prop.required && absent) { + warn( + 'Missing required prop: "' + name + '"', + vm + ); + return + } + if (value == null && !prop.required) { + return + } + var type = prop.type; + var valid = !type || type === true; + var expectedTypes = []; + if (type) { + if (!Array.isArray(type)) { + type = [type]; + } + for (var i = 0; i < type.length && !valid; i++) { + var assertedType = assertType(value, type[i]); + expectedTypes.push(assertedType.expectedType); + valid = assertedType.valid; + } + } + if (!valid) { + warn( + 'Invalid prop: type check failed for prop "' + name + '".' + + ' Expected ' + expectedTypes.map(capitalize).join(', ') + + ', got ' + Object.prototype.toString.call(value).slice(8, -1) + '.', + vm + ); + return + } + var validator = prop.validator; + if (validator) { + if (!validator(value)) { + warn( + 'Invalid prop: custom validator check failed for prop "' + name + '".', + vm + ); + } + } +} + +/** + * Assert the type of a value + */ +function assertType (value, type) { + var valid; + var expectedType = getType(type); + if (expectedType === 'String') { + valid = typeof value === (expectedType = 'string'); + } else if (expectedType === 'Number') { + valid = typeof value === (expectedType = 'number'); + } else if (expectedType === 'Boolean') { + valid = typeof value === (expectedType = 'boolean'); + } else if (expectedType === 'Function') { + valid = typeof value === (expectedType = 'function'); + } else if (expectedType === 'Object') { + valid = isPlainObject(value); + } else if (expectedType === 'Array') { + valid = Array.isArray(value); + } else { + valid = value instanceof type; + } + return { + valid: valid, + expectedType: expectedType + } +} + +/** + * Use function string name to check built-in types, + * because a simple equality check will fail when running + * across different vms / iframes. + */ +function getType (fn) { + var match = fn && fn.toString().match(/^\s*function (\w+)/); + return match && match[1] +} + +function isBooleanType (fn) { + if (!Array.isArray(fn)) { + return getType(fn) === 'Boolean' + } + for (var i = 0, len = fn.length; i < len; i++) { + if (getType(fn[i]) === 'Boolean') { + return true + } + } + /* istanbul ignore next */ + return false +} + + + +var util = Object.freeze({ + defineReactive: defineReactive$$1, + _toString: _toString, + toNumber: toNumber, + makeMap: makeMap, + isBuiltInTag: isBuiltInTag, + remove: remove$1, + hasOwn: hasOwn, + isPrimitive: isPrimitive, + cached: cached, + camelize: camelize, + capitalize: capitalize, + hyphenate: hyphenate, + bind: bind$1, + toArray: toArray, + extend: extend, + isObject: isObject, + isPlainObject: isPlainObject, + toObject: toObject, + noop: noop, + no: no, + genStaticKeys: genStaticKeys, + looseEqual: looseEqual, + looseIndexOf: looseIndexOf, + isReserved: isReserved, + def: def, + parsePath: parsePath, + hasProto: hasProto, + inBrowser: inBrowser, + UA: UA, + isIE: isIE, + isIE9: isIE9, + isEdge: isEdge, + isAndroid: isAndroid, + isIOS: isIOS, + devtools: devtools, + nextTick: nextTick, + get _Set () { return _Set; }, + mergeOptions: mergeOptions, + resolveAsset: resolveAsset, + get warn () { return warn; }, + get formatComponentName () { return formatComponentName; }, + validateProp: validateProp +}); + +/* */ + +function initUse (Vue) { + Vue.use = function (plugin) { + /* istanbul ignore if */ + if (plugin.installed) { + return + } + // additional parameters + var args = toArray(arguments, 1); + args.unshift(this); + if (typeof plugin.install === 'function') { + plugin.install.apply(plugin, args); + } else { + plugin.apply(null, args); + } + plugin.installed = true; + return this + }; +} + +/* */ + +function initMixin$1 (Vue) { + Vue.mixin = function (mixin) { + Vue.options = mergeOptions(Vue.options, mixin); + }; +} + +/* */ + +function initExtend (Vue) { + /** + * Each instance constructor, including Vue, has a unique + * cid. This enables us to create wrapped "child + * constructors" for prototypal inheritance and cache them. + */ + Vue.cid = 0; + var cid = 1; /** - * Define a property. - * - * @param {Object} obj - * @param {String} key - * @param {*} val - * @param {Boolean} [enumerable] + * Class inheritance */ + Vue.extend = function (extendOptions) { + extendOptions = extendOptions || {}; + var Super = this; + var isFirstExtend = Super.cid === 0; + if (isFirstExtend && extendOptions._Ctor) { + return extendOptions._Ctor + } + var name = extendOptions.name || Super.options.name; + { + if (!/^[a-zA-Z][\w-]*$/.test(name)) { + warn( + 'Invalid component name: "' + name + '". Component names ' + + 'can only contain alphanumeric characaters and the hyphen.' + ); + name = null; + } + } + var Sub = function VueComponent (options) { + this._init(options); + }; + Sub.prototype = Object.create(Super.prototype); + Sub.prototype.constructor = Sub; + Sub.cid = cid++; + Sub.options = mergeOptions( + Super.options, + extendOptions + ); + Sub['super'] = Super; + // allow further extension + Sub.extend = Super.extend; + // create asset registers, so extended classes + // can have their private assets too. + config._assetTypes.forEach(function (type) { + Sub[type] = Super[type]; + }); + // enable recursive self-lookup + if (name) { + Sub.options.components[name] = Sub; + } + // keep a reference to the super options at extension time. + // later at instantiation we can check if Super's options have + // been updated. + Sub.superOptions = Super.options; + Sub.extendOptions = extendOptions; + // cache constructor + if (isFirstExtend) { + extendOptions._Ctor = Sub; + } + return Sub + }; +} - function def(obj, key, val, enumerable) { - Object.defineProperty(obj, key, { - value: val, - enumerable: !!enumerable, - writable: true, - configurable: true +/* */ + +function initAssetRegisters (Vue) { + /** + * Create asset registration methods. + */ + config._assetTypes.forEach(function (type) { + Vue[type] = function ( + id, + definition + ) { + if (!definition) { + return this.options[type + 's'][id] + } else { + /* istanbul ignore if */ + { + if (type === 'component' && config.isReservedTag(id)) { + warn( + 'Do not use built-in or reserved HTML elements as component ' + + 'id: ' + id + ); + } + } + if (type === 'component' && isPlainObject(definition)) { + definition.name = definition.name || id; + definition = Vue.extend(definition); + } + if (type === 'directive' && typeof definition === 'function') { + definition = { bind: definition, update: definition }; + } + this.options[type + 's'][id] = definition; + return definition + } + }; + }); +} + +var KeepAlive = { + name: 'keep-alive', + abstract: true, + created: function created () { + this.cache = Object.create(null); + }, + render: function render () { + var vnode = getFirstComponentChild(this.$slots.default); + if (vnode && vnode.componentOptions) { + var opts = vnode.componentOptions; + var key = vnode.key == null + // same constructor may get registered as different local components + // so cid alone is not enough (#3269) + ? opts.Ctor.cid + '::' + opts.tag + : vnode.key; + if (this.cache[key]) { + vnode.child = this.cache[key].child; + } else { + this.cache[key] = vnode; + } + vnode.data.keepAlive = true; + } + return vnode + }, + destroyed: function destroyed () { + var this$1 = this; + + for (var key in this.cache) { + var vnode = this$1.cache[key]; + callHook(vnode.child, 'deactivated'); + vnode.child.$destroy(); + } + } +}; + +var builtInComponents = { + KeepAlive: KeepAlive +}; + +/* */ + +function initGlobalAPI (Vue) { + // config + var configDef = {}; + configDef.get = function () { return config; }; + { + configDef.set = function () { + warn( + 'Do not replace the Vue.config object, set individual fields instead.' + ); + }; + } + Object.defineProperty(Vue, 'config', configDef); + Vue.util = util; + Vue.set = set; + Vue.delete = del; + Vue.nextTick = nextTick; + + Vue.options = Object.create(null); + config._assetTypes.forEach(function (type) { + Vue.options[type + 's'] = Object.create(null); + }); + + extend(Vue.options.components, builtInComponents); + + initUse(Vue); + initMixin$1(Vue); + initExtend(Vue); + initAssetRegisters(Vue); +} + +initGlobalAPI(Vue$3); + +Object.defineProperty(Vue$3.prototype, '$isServer', { + get: function () { return config._isServer; } +}); + +Vue$3.version = '2.0.3'; + +/* */ + +// attributes that should be using props for binding +var mustUseProp = makeMap('value,selected,checked,muted'); + +var isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck'); + +var isBooleanAttr = makeMap( + 'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' + + 'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' + + 'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' + + 'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' + + 'required,reversed,scoped,seamless,selected,sortable,translate,' + + 'truespeed,typemustmatch,visible' +); + +var isAttr = makeMap( + 'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' + + 'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' + + 'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' + + 'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' + + 'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' + + 'form,formaction,headers,,height,hidden,high,href,hreflang,http-equiv,' + + 'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' + + 'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' + + 'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' + + 'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' + + 'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' + + 'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' + + 'target,title,type,usemap,value,width,wrap' +); + + + +var xlinkNS = 'http://www.w3.org/1999/xlink'; + +var isXlink = function (name) { + return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink' +}; + +var getXlinkProp = function (name) { + return isXlink(name) ? name.slice(6, name.length) : '' +}; + +var isFalsyAttrValue = function (val) { + return val == null || val === false +}; + +/* */ + +function genClassForVnode (vnode) { + var data = vnode.data; + var parentNode = vnode; + var childNode = vnode; + while (childNode.child) { + childNode = childNode.child._vnode; + if (childNode.data) { + data = mergeClassData(childNode.data, data); + } + } + while ((parentNode = parentNode.parent)) { + if (parentNode.data) { + data = mergeClassData(data, parentNode.data); + } + } + return genClassFromData(data) +} + +function mergeClassData (child, parent) { + return { + staticClass: concat(child.staticClass, parent.staticClass), + class: child.class + ? [child.class, parent.class] + : parent.class + } +} + +function genClassFromData (data) { + var dynamicClass = data.class; + var staticClass = data.staticClass; + if (staticClass || dynamicClass) { + return concat(staticClass, stringifyClass(dynamicClass)) + } + /* istanbul ignore next */ + return '' +} + +function concat (a, b) { + return a ? b ? (a + ' ' + b) : a : (b || '') +} + +function stringifyClass (value) { + var res = ''; + if (!value) { + return res + } + if (typeof value === 'string') { + return value + } + if (Array.isArray(value)) { + var stringified; + for (var i = 0, l = value.length; i < l; i++) { + if (value[i]) { + if ((stringified = stringifyClass(value[i]))) { + res += stringified + ' '; + } + } + } + return res.slice(0, -1) + } + if (isObject(value)) { + for (var key in value) { + if (value[key]) { res += key + ' '; } + } + return res.slice(0, -1) + } + /* istanbul ignore next */ + return res +} + +/* */ + +var namespaceMap = { + svg: 'http://www.w3.org/2000/svg', + math: 'http://www.w3.org/1998/Math/MathML' +}; + +var isHTMLTag = makeMap( + 'html,body,base,head,link,meta,style,title,' + + 'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' + + 'div,dd,dl,dt,figcaption,figure,hr,img,li,main,ol,p,pre,ul,' + + 'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' + + 's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' + + 'embed,object,param,source,canvas,script,noscript,del,ins,' + + 'caption,col,colgroup,table,thead,tbody,td,th,tr,' + + 'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' + + 'output,progress,select,textarea,' + + 'details,dialog,menu,menuitem,summary,' + + 'content,element,shadow,template' +); + +var isUnaryTag = makeMap( + 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' + + 'link,meta,param,source,track,wbr', + true +); + +// Elements that you can, intentionally, leave open +// (and which close themselves) +var canBeLeftOpenTag = makeMap( + 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source', + true +); + +// HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3 +// Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content +var isNonPhrasingTag = makeMap( + 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' + + 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' + + 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' + + 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' + + 'title,tr,track', + true +); + +// this map is intentionally selective, only covering SVG elements that may +// contain child elements. +var isSVG = makeMap( + 'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font,' + + 'font-face,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' + + 'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view', + true +); + +var isPreTag = function (tag) { return tag === 'pre'; }; + +var isReservedTag = function (tag) { + return isHTMLTag(tag) || isSVG(tag) +}; + +function getTagNamespace (tag) { + if (isSVG(tag)) { + return 'svg' + } + // basic support for MathML + // note it doesn't support other MathML elements being component roots + if (tag === 'math') { + return 'math' + } +} + +var unknownElementCache = Object.create(null); +function isUnknownElement (tag) { + /* istanbul ignore if */ + if (!inBrowser) { + return true + } + if (isReservedTag(tag)) { + return false + } + tag = tag.toLowerCase(); + /* istanbul ignore if */ + if (unknownElementCache[tag] != null) { + return unknownElementCache[tag] + } + var el = document.createElement(tag); + if (tag.indexOf('-') > -1) { + // http://stackoverflow.com/a/28210364/1070244 + return (unknownElementCache[tag] = ( + el.constructor === window.HTMLUnknownElement || + el.constructor === window.HTMLElement + )) + } else { + return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString())) + } +} + +/* */ + +/** + * Query an element selector if it's not an element already. + */ +function query (el) { + if (typeof el === 'string') { + var selector = el; + el = document.querySelector(el); + if (!el) { + "development" !== 'production' && warn( + 'Cannot find element: ' + selector + ); + return document.createElement('div') + } + } + return el +} + +/* */ + +function createElement$1 (tagName, vnode) { + var elm = document.createElement(tagName); + if (tagName !== 'select') { + return elm + } + if (vnode.data && vnode.data.attrs && 'multiple' in vnode.data.attrs) { + elm.setAttribute('multiple', 'multiple'); + } + return elm +} + +function createElementNS (namespace, tagName) { + return document.createElementNS(namespaceMap[namespace], tagName) +} + +function createTextNode (text) { + return document.createTextNode(text) +} + +function createComment (text) { + return document.createComment(text) +} + +function insertBefore (parentNode, newNode, referenceNode) { + parentNode.insertBefore(newNode, referenceNode); +} + +function removeChild (node, child) { + node.removeChild(child); +} + +function appendChild (node, child) { + node.appendChild(child); +} + +function parentNode (node) { + return node.parentNode +} + +function nextSibling (node) { + return node.nextSibling +} + +function tagName (node) { + return node.tagName +} + +function setTextContent (node, text) { + node.textContent = text; +} + +function childNodes (node) { + return node.childNodes +} + +function setAttribute (node, key, val) { + node.setAttribute(key, val); +} + + +var nodeOps = Object.freeze({ + createElement: createElement$1, + createElementNS: createElementNS, + createTextNode: createTextNode, + createComment: createComment, + insertBefore: insertBefore, + removeChild: removeChild, + appendChild: appendChild, + parentNode: parentNode, + nextSibling: nextSibling, + tagName: tagName, + setTextContent: setTextContent, + childNodes: childNodes, + setAttribute: setAttribute +}); + +/* */ + +var ref = { + create: function create (_, vnode) { + registerRef(vnode); + }, + update: function update (oldVnode, vnode) { + if (oldVnode.data.ref !== vnode.data.ref) { + registerRef(oldVnode, true); + registerRef(vnode); + } + }, + destroy: function destroy (vnode) { + registerRef(vnode, true); + } +}; + +function registerRef (vnode, isRemoval) { + var key = vnode.data.ref; + if (!key) { return } + + var vm = vnode.context; + var ref = vnode.child || vnode.elm; + var refs = vm.$refs; + if (isRemoval) { + if (Array.isArray(refs[key])) { + remove$1(refs[key], ref); + } else if (refs[key] === ref) { + refs[key] = undefined; + } + } else { + if (vnode.data.refInFor) { + if (Array.isArray(refs[key])) { + refs[key].push(ref); + } else { + refs[key] = [ref]; + } + } else { + refs[key] = ref; + } + } +} + +/** + * Virtual DOM patching algorithm based on Snabbdom by + * Simon Friis Vindum (@paldepind) + * Licensed under the MIT License + * https://github.com/paldepind/snabbdom/blob/master/LICENSE + * + * modified by Evan You (@yyx990803) + * + +/* + * Not type-checking this because this file is perf-critical and the cost + * of making flow understand it is not worth it. + */ + +var emptyNode = new VNode('', {}, []); + +var hooks$1 = ['create', 'update', 'remove', 'destroy']; + +function isUndef (s) { + return s == null +} + +function isDef (s) { + return s != null +} + +function sameVnode (vnode1, vnode2) { + return ( + vnode1.key === vnode2.key && + vnode1.tag === vnode2.tag && + vnode1.isComment === vnode2.isComment && + !vnode1.data === !vnode2.data + ) +} + +function createKeyToOldIdx (children, beginIdx, endIdx) { + var i, key; + var map = {}; + for (i = beginIdx; i <= endIdx; ++i) { + key = children[i].key; + if (isDef(key)) { map[key] = i; } + } + return map +} + +function createPatchFunction (backend) { + var i, j; + var cbs = {}; + + var modules = backend.modules; + var nodeOps = backend.nodeOps; + + for (i = 0; i < hooks$1.length; ++i) { + cbs[hooks$1[i]] = []; + for (j = 0; j < modules.length; ++j) { + if (modules[j][hooks$1[i]] !== undefined) { cbs[hooks$1[i]].push(modules[j][hooks$1[i]]); } + } + } + + function emptyNodeAt (elm) { + return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm) + } + + function createRmCb (childElm, listeners) { + function remove$$1 () { + if (--remove$$1.listeners === 0) { + removeElement(childElm); + } + } + remove$$1.listeners = listeners; + return remove$$1 + } + + function removeElement (el) { + var parent = nodeOps.parentNode(el); + nodeOps.removeChild(parent, el); + } + + function createElm (vnode, insertedVnodeQueue, nested) { + var i; + var data = vnode.data; + vnode.isRootInsert = !nested; + if (isDef(data)) { + if (isDef(i = data.hook) && isDef(i = i.init)) { i(vnode); } + // after calling the init hook, if the vnode is a child component + // it should've created a child instance and mounted it. the child + // component also has set the placeholder vnode's elm. + // in that case we can just return the element and be done. + if (isDef(i = vnode.child)) { + initComponent(vnode, insertedVnodeQueue); + return vnode.elm + } + } + var children = vnode.children; + var tag = vnode.tag; + if (isDef(tag)) { + { + if ( + !vnode.ns && + !(config.ignoredElements && config.ignoredElements.indexOf(tag) > -1) && + config.isUnknownElement(tag) + ) { + warn( + 'Unknown custom element: <' + tag + '> - did you ' + + 'register the component correctly? For recursive components, ' + + 'make sure to provide the "name" option.', + vnode.context + ); + } + } + vnode.elm = vnode.ns + ? nodeOps.createElementNS(vnode.ns, tag) + : nodeOps.createElement(tag, vnode); + setScope(vnode); + createChildren(vnode, children, insertedVnodeQueue); + if (isDef(data)) { + invokeCreateHooks(vnode, insertedVnodeQueue); + } + } else if (vnode.isComment) { + vnode.elm = nodeOps.createComment(vnode.text); + } else { + vnode.elm = nodeOps.createTextNode(vnode.text); + } + return vnode.elm + } + + function createChildren (vnode, children, insertedVnodeQueue) { + if (Array.isArray(children)) { + for (var i = 0; i < children.length; ++i) { + nodeOps.appendChild(vnode.elm, createElm(children[i], insertedVnodeQueue, true)); + } + } else if (isPrimitive(vnode.text)) { + nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text)); + } + } + + function isPatchable (vnode) { + while (vnode.child) { + vnode = vnode.child._vnode; + } + return isDef(vnode.tag) + } + + function invokeCreateHooks (vnode, insertedVnodeQueue) { + for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) { + cbs.create[i$1](emptyNode, vnode); + } + i = vnode.data.hook; // Reuse variable + if (isDef(i)) { + if (i.create) { i.create(emptyNode, vnode); } + if (i.insert) { insertedVnodeQueue.push(vnode); } + } + } + + function initComponent (vnode, insertedVnodeQueue) { + if (vnode.data.pendingInsert) { + insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert); + } + vnode.elm = vnode.child.$el; + if (isPatchable(vnode)) { + invokeCreateHooks(vnode, insertedVnodeQueue); + setScope(vnode); + } else { + // empty component root. + // skip all element-related modules except for ref (#3455) + registerRef(vnode); + // make sure to invoke the insert hook + insertedVnodeQueue.push(vnode); + } + } + + // set scope id attribute for scoped CSS. + // this is implemented as a special case to avoid the overhead + // of going through the normal attribute patching process. + function setScope (vnode) { + var i; + if (isDef(i = vnode.context) && isDef(i = i.$options._scopeId)) { + nodeOps.setAttribute(vnode.elm, i, ''); + } + if (isDef(i = activeInstance) && + i !== vnode.context && + isDef(i = i.$options._scopeId)) { + nodeOps.setAttribute(vnode.elm, i, ''); + } + } + + function addVnodes (parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) { + for (; startIdx <= endIdx; ++startIdx) { + nodeOps.insertBefore(parentElm, createElm(vnodes[startIdx], insertedVnodeQueue), before); + } + } + + function invokeDestroyHook (vnode) { + var i, j; + var data = vnode.data; + if (isDef(data)) { + if (isDef(i = data.hook) && isDef(i = i.destroy)) { i(vnode); } + for (i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](vnode); } + } + if (isDef(i = vnode.children)) { + for (j = 0; j < vnode.children.length; ++j) { + invokeDestroyHook(vnode.children[j]); + } + } + } + + function removeVnodes (parentElm, vnodes, startIdx, endIdx) { + for (; startIdx <= endIdx; ++startIdx) { + var ch = vnodes[startIdx]; + if (isDef(ch)) { + if (isDef(ch.tag)) { + removeAndInvokeRemoveHook(ch); + invokeDestroyHook(ch); + } else { // Text node + nodeOps.removeChild(parentElm, ch.elm); + } + } + } + } + + function removeAndInvokeRemoveHook (vnode, rm) { + if (rm || isDef(vnode.data)) { + var listeners = cbs.remove.length + 1; + if (!rm) { + // directly removing + rm = createRmCb(vnode.elm, listeners); + } else { + // we have a recursively passed down rm callback + // increase the listeners count + rm.listeners += listeners; + } + // recursively invoke hooks on child component root node + if (isDef(i = vnode.child) && isDef(i = i._vnode) && isDef(i.data)) { + removeAndInvokeRemoveHook(i, rm); + } + for (i = 0; i < cbs.remove.length; ++i) { + cbs.remove[i](vnode, rm); + } + if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) { + i(vnode, rm); + } else { + rm(); + } + } else { + removeElement(vnode.elm); + } + } + + function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { + var oldStartIdx = 0; + var newStartIdx = 0; + var oldEndIdx = oldCh.length - 1; + var oldStartVnode = oldCh[0]; + var oldEndVnode = oldCh[oldEndIdx]; + var newEndIdx = newCh.length - 1; + var newStartVnode = newCh[0]; + var newEndVnode = newCh[newEndIdx]; + var oldKeyToIdx, idxInOld, elmToMove, before; + + // removeOnly is a special flag used only by + // to ensure removed elements stay in correct relative positions + // during leaving transitions + var canMove = !removeOnly; + + while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { + if (isUndef(oldStartVnode)) { + oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left + } else if (isUndef(oldEndVnode)) { + oldEndVnode = oldCh[--oldEndIdx]; + } else if (sameVnode(oldStartVnode, newStartVnode)) { + patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue); + oldStartVnode = oldCh[++oldStartIdx]; + newStartVnode = newCh[++newStartIdx]; + } else if (sameVnode(oldEndVnode, newEndVnode)) { + patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue); + oldEndVnode = oldCh[--oldEndIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right + patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue); + canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)); + oldStartVnode = oldCh[++oldStartIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left + patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue); + canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); + oldEndVnode = oldCh[--oldEndIdx]; + newStartVnode = newCh[++newStartIdx]; + } else { + if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); } + idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null; + if (isUndef(idxInOld)) { // New element + nodeOps.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm); + newStartVnode = newCh[++newStartIdx]; + } else { + elmToMove = oldCh[idxInOld]; + /* istanbul ignore if */ + if ("development" !== 'production' && !elmToMove) { + warn( + 'It seems there are duplicate keys that is causing an update error. ' + + 'Make sure each v-for item has a unique key.' + ); + } + if (elmToMove.tag !== newStartVnode.tag) { + // same key but different element. treat as new element + nodeOps.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm); + newStartVnode = newCh[++newStartIdx]; + } else { + patchVnode(elmToMove, newStartVnode, insertedVnodeQueue); + oldCh[idxInOld] = undefined; + canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm); + newStartVnode = newCh[++newStartIdx]; + } + } + } + } + if (oldStartIdx > oldEndIdx) { + before = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; + addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); + } else if (newStartIdx > newEndIdx) { + removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); + } + } + + function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) { + if (oldVnode === vnode) { + return + } + // reuse element for static trees. + // note we only do this if the vnode is cloned - + // if the new node is not cloned it means the render functions have been + // reset by the hot-reload-api and we need to do a proper re-render. + if (vnode.isStatic && + oldVnode.isStatic && + vnode.key === oldVnode.key && + vnode.isCloned) { + vnode.elm = oldVnode.elm; + return + } + var i; + var data = vnode.data; + var hasData = isDef(data); + if (hasData && isDef(i = data.hook) && isDef(i = i.prepatch)) { + i(oldVnode, vnode); + } + var elm = vnode.elm = oldVnode.elm; + var oldCh = oldVnode.children; + var ch = vnode.children; + if (hasData && isPatchable(vnode)) { + for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); } + if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); } + } + if (isUndef(vnode.text)) { + if (isDef(oldCh) && isDef(ch)) { + if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); } + } else if (isDef(ch)) { + if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } + addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); + } else if (isDef(oldCh)) { + removeVnodes(elm, oldCh, 0, oldCh.length - 1); + } else if (isDef(oldVnode.text)) { + nodeOps.setTextContent(elm, ''); + } + } else if (oldVnode.text !== vnode.text) { + nodeOps.setTextContent(elm, vnode.text); + } + if (hasData) { + if (isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); } + } + } + + function invokeInsertHook (vnode, queue, initial) { + // delay insert hooks for component root nodes, invoke them after the + // element is really inserted + if (initial && vnode.parent) { + vnode.parent.data.pendingInsert = queue; + } else { + for (var i = 0; i < queue.length; ++i) { + queue[i].data.hook.insert(queue[i]); + } + } + } + + var bailed = false; + function hydrate (elm, vnode, insertedVnodeQueue) { + { + if (!assertNodeMatch(elm, vnode)) { + return false + } + } + vnode.elm = elm; + var tag = vnode.tag; + var data = vnode.data; + var children = vnode.children; + if (isDef(data)) { + if (isDef(i = data.hook) && isDef(i = i.init)) { i(vnode, true /* hydrating */); } + if (isDef(i = vnode.child)) { + // child component. it should have hydrated its own tree. + initComponent(vnode, insertedVnodeQueue); + return true + } + } + if (isDef(tag)) { + if (isDef(children)) { + var childNodes = nodeOps.childNodes(elm); + // empty element, allow client to pick up and populate children + if (!childNodes.length) { + createChildren(vnode, children, insertedVnodeQueue); + } else { + var childrenMatch = true; + if (childNodes.length !== children.length) { + childrenMatch = false; + } else { + for (var i$1 = 0; i$1 < children.length; i$1++) { + if (!hydrate(childNodes[i$1], children[i$1], insertedVnodeQueue)) { + childrenMatch = false; + break + } + } + } + if (!childrenMatch) { + if ("development" !== 'production' && + typeof console !== 'undefined' && + !bailed) { + bailed = true; + console.warn('Parent: ', elm); + console.warn('Mismatching childNodes vs. VNodes: ', childNodes, children); + } + return false + } + } + } + if (isDef(data)) { + invokeCreateHooks(vnode, insertedVnodeQueue); + } + } + return true + } + + function assertNodeMatch (node, vnode) { + if (vnode.tag) { + return ( + vnode.tag.indexOf('vue-component') === 0 || + vnode.tag === nodeOps.tagName(node).toLowerCase() + ) + } else { + return _toString(vnode.text) === node.data + } + } + + return function patch (oldVnode, vnode, hydrating, removeOnly) { + if (!vnode) { + if (oldVnode) { invokeDestroyHook(oldVnode); } + return + } + + var elm, parent; + var isInitialPatch = false; + var insertedVnodeQueue = []; + + if (!oldVnode) { + // empty mount, create new root element + isInitialPatch = true; + createElm(vnode, insertedVnodeQueue); + } else { + var isRealElement = isDef(oldVnode.nodeType); + if (!isRealElement && sameVnode(oldVnode, vnode)) { + patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly); + } else { + if (isRealElement) { + // mounting to a real element + // check if this is server-rendered content and if we can perform + // a successful hydration. + if (oldVnode.nodeType === 1 && oldVnode.hasAttribute('server-rendered')) { + oldVnode.removeAttribute('server-rendered'); + hydrating = true; + } + if (hydrating) { + if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { + invokeInsertHook(vnode, insertedVnodeQueue, true); + return oldVnode + } else { + warn( + 'The client-side rendered virtual DOM tree is not matching ' + + 'server-rendered content. This is likely caused by incorrect ' + + 'HTML markup, for example nesting block-level elements inside ' + + '

      , or missing . Bailing hydration and performing ' + + 'full client-side render.' + ); + } + } + // either not server-rendered, or hydration failed. + // create an empty node and replace it + oldVnode = emptyNodeAt(oldVnode); + } + elm = oldVnode.elm; + parent = nodeOps.parentNode(elm); + + createElm(vnode, insertedVnodeQueue); + + // component root element replaced. + // update parent placeholder node element. + if (vnode.parent) { + vnode.parent.elm = vnode.elm; + if (isPatchable(vnode)) { + for (var i = 0; i < cbs.create.length; ++i) { + cbs.create[i](emptyNode, vnode.parent); + } + } + } + + if (parent !== null) { + nodeOps.insertBefore(parent, vnode.elm, nodeOps.nextSibling(elm)); + removeVnodes(parent, [oldVnode], 0, 0); + } else if (isDef(oldVnode.tag)) { + invokeDestroyHook(oldVnode); + } + } + } + + invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); + return vnode.elm + } +} + +/* */ + +var directives = { + create: updateDirectives, + update: updateDirectives, + destroy: function unbindDirectives (vnode) { + updateDirectives(vnode, emptyNode); + } +}; + +function updateDirectives ( + oldVnode, + vnode +) { + if (!oldVnode.data.directives && !vnode.data.directives) { + return + } + var isCreate = oldVnode === emptyNode; + var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context); + var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context); + + var dirsWithInsert = []; + var dirsWithPostpatch = []; + + var key, oldDir, dir; + for (key in newDirs) { + oldDir = oldDirs[key]; + dir = newDirs[key]; + if (!oldDir) { + // new directive, bind + callHook$1(dir, 'bind', vnode, oldVnode); + if (dir.def && dir.def.inserted) { + dirsWithInsert.push(dir); + } + } else { + // existing directive, update + dir.oldValue = oldDir.value; + callHook$1(dir, 'update', vnode, oldVnode); + if (dir.def && dir.def.componentUpdated) { + dirsWithPostpatch.push(dir); + } + } + } + + if (dirsWithInsert.length) { + var callInsert = function () { + dirsWithInsert.forEach(function (dir) { + callHook$1(dir, 'inserted', vnode, oldVnode); + }); + }; + if (isCreate) { + mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', callInsert, 'dir-insert'); + } else { + callInsert(); + } + } + + if (dirsWithPostpatch.length) { + mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'postpatch', function () { + dirsWithPostpatch.forEach(function (dir) { + callHook$1(dir, 'componentUpdated', vnode, oldVnode); + }); + }, 'dir-postpatch'); + } + + if (!isCreate) { + for (key in oldDirs) { + if (!newDirs[key]) { + // no longer present, unbind + callHook$1(oldDirs[key], 'unbind', oldVnode); + } + } + } +} + +var emptyModifiers = Object.create(null); + +function normalizeDirectives$1 ( + dirs, + vm +) { + var res = Object.create(null); + if (!dirs) { + return res + } + var i, dir; + for (i = 0; i < dirs.length; i++) { + dir = dirs[i]; + if (!dir.modifiers) { + dir.modifiers = emptyModifiers; + } + res[getRawDirName(dir)] = dir; + dir.def = resolveAsset(vm.$options, 'directives', dir.name, true); + } + return res +} + +function getRawDirName (dir) { + return dir.rawName || ((dir.name) + "." + (Object.keys(dir.modifiers || {}).join('.'))) +} + +function callHook$1 (dir, hook, vnode, oldVnode) { + var fn = dir.def && dir.def[hook]; + if (fn) { + fn(vnode.elm, dir, vnode, oldVnode); + } +} + +var baseModules = [ + ref, + directives +]; + +/* */ + +function updateAttrs (oldVnode, vnode) { + if (!oldVnode.data.attrs && !vnode.data.attrs) { + return + } + var key, cur, old; + var elm = vnode.elm; + var oldAttrs = oldVnode.data.attrs || {}; + var attrs = vnode.data.attrs || {}; + // clone observed objects, as the user probably wants to mutate it + if (attrs.__ob__) { + attrs = vnode.data.attrs = extend({}, attrs); + } + + for (key in attrs) { + cur = attrs[key]; + old = oldAttrs[key]; + if (old !== cur) { + setAttr(elm, key, cur); + } + } + for (key in oldAttrs) { + if (attrs[key] == null) { + if (isXlink(key)) { + elm.removeAttributeNS(xlinkNS, getXlinkProp(key)); + } else if (!isEnumeratedAttr(key)) { + elm.removeAttribute(key); + } + } + } +} + +function setAttr (el, key, value) { + if (isBooleanAttr(key)) { + // set attribute for blank value + // e.g. + if (isFalsyAttrValue(value)) { + el.removeAttribute(key); + } else { + el.setAttribute(key, key); + } + } else if (isEnumeratedAttr(key)) { + el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true'); + } else if (isXlink(key)) { + if (isFalsyAttrValue(value)) { + el.removeAttributeNS(xlinkNS, getXlinkProp(key)); + } else { + el.setAttributeNS(xlinkNS, key, value); + } + } else { + if (isFalsyAttrValue(value)) { + el.removeAttribute(key); + } else { + el.setAttribute(key, value); + } + } +} + +var attrs = { + create: updateAttrs, + update: updateAttrs +}; + +/* */ + +function updateClass (oldVnode, vnode) { + var el = vnode.elm; + var data = vnode.data; + var oldData = oldVnode.data; + if (!data.staticClass && !data.class && + (!oldData || (!oldData.staticClass && !oldData.class))) { + return + } + + var cls = genClassForVnode(vnode); + + // handle transition classes + var transitionClass = el._transitionClasses; + if (transitionClass) { + cls = concat(cls, stringifyClass(transitionClass)); + } + + // set the class + if (cls !== el._prevClass) { + el.setAttribute('class', cls); + el._prevClass = cls; + } +} + +var klass = { + create: updateClass, + update: updateClass +}; + +// skip type checking this file because we need to attach private properties +// to elements + +function updateDOMListeners (oldVnode, vnode) { + if (!oldVnode.data.on && !vnode.data.on) { + return + } + var on = vnode.data.on || {}; + var oldOn = oldVnode.data.on || {}; + var add = vnode.elm._v_add || (vnode.elm._v_add = function (event, handler, capture) { + vnode.elm.addEventListener(event, handler, capture); + }); + var remove = vnode.elm._v_remove || (vnode.elm._v_remove = function (event, handler) { + vnode.elm.removeEventListener(event, handler); + }); + updateListeners(on, oldOn, add, remove, vnode.context); +} + +var events = { + create: updateDOMListeners, + update: updateDOMListeners +}; + +/* */ + +function updateDOMProps (oldVnode, vnode) { + if (!oldVnode.data.domProps && !vnode.data.domProps) { + return + } + var key, cur; + var elm = vnode.elm; + var oldProps = oldVnode.data.domProps || {}; + var props = vnode.data.domProps || {}; + // clone observed objects, as the user probably wants to mutate it + if (props.__ob__) { + props = vnode.data.domProps = extend({}, props); + } + + for (key in oldProps) { + if (props[key] == null) { + elm[key] = undefined; + } + } + for (key in props) { + // ignore children if the node has textContent or innerHTML, + // as these will throw away existing DOM nodes and cause removal errors + // on subsequent patches (#3360) + if ((key === 'textContent' || key === 'innerHTML') && vnode.children) { + vnode.children.length = 0; + } + cur = props[key]; + if (key === 'value') { + // store value as _value as well since + // non-string values will be stringified + elm._value = cur; + // avoid resetting cursor position when value is the same + var strCur = cur == null ? '' : String(cur); + if (elm.value !== strCur && !elm.composing) { + elm.value = strCur; + } + } else { + elm[key] = cur; + } + } +} + +var domProps = { + create: updateDOMProps, + update: updateDOMProps +}; + +/* */ + +var prefixes = ['Webkit', 'Moz', 'ms']; + +var testEl; +var normalize = cached(function (prop) { + testEl = testEl || document.createElement('div'); + prop = camelize(prop); + if (prop !== 'filter' && (prop in testEl.style)) { + return prop + } + var upper = prop.charAt(0).toUpperCase() + prop.slice(1); + for (var i = 0; i < prefixes.length; i++) { + var prefixed = prefixes[i] + upper; + if (prefixed in testEl.style) { + return prefixed + } + } +}); + +function updateStyle (oldVnode, vnode) { + if ((!oldVnode.data || !oldVnode.data.style) && !vnode.data.style) { + return + } + var cur, name; + var el = vnode.elm; + var oldStyle = oldVnode.data.style || {}; + var style = vnode.data.style || {}; + + // handle string + if (typeof style === 'string') { + el.style.cssText = style; + return + } + + var needClone = style.__ob__; + + // handle array syntax + if (Array.isArray(style)) { + style = vnode.data.style = toObject(style); + } + + // clone the style for future updates, + // in case the user mutates the style object in-place. + if (needClone) { + style = vnode.data.style = extend({}, style); + } + + for (name in oldStyle) { + if (style[name] == null) { + el.style[normalize(name)] = ''; + } + } + for (name in style) { + cur = style[name]; + if (cur !== oldStyle[name]) { + // ie9 setting to null has no effect, must use empty string + el.style[normalize(name)] = cur == null ? '' : cur; + } + } +} + +var style = { + create: updateStyle, + update: updateStyle +}; + +/* */ + +/** + * Add class with compatibility for SVG since classList is not supported on + * SVG elements in IE + */ +function addClass (el, cls) { + /* istanbul ignore else */ + if (el.classList) { + if (cls.indexOf(' ') > -1) { + cls.split(/\s+/).forEach(function (c) { return el.classList.add(c); }); + } else { + el.classList.add(cls); + } + } else { + var cur = ' ' + el.getAttribute('class') + ' '; + if (cur.indexOf(' ' + cls + ' ') < 0) { + el.setAttribute('class', (cur + cls).trim()); + } + } +} + +/** + * Remove class with compatibility for SVG since classList is not supported on + * SVG elements in IE + */ +function removeClass (el, cls) { + /* istanbul ignore else */ + if (el.classList) { + if (cls.indexOf(' ') > -1) { + cls.split(/\s+/).forEach(function (c) { return el.classList.remove(c); }); + } else { + el.classList.remove(cls); + } + } else { + var cur = ' ' + el.getAttribute('class') + ' '; + var tar = ' ' + cls + ' '; + while (cur.indexOf(tar) >= 0) { + cur = cur.replace(tar, ' '); + } + el.setAttribute('class', cur.trim()); + } +} + +/* */ + +var hasTransition = inBrowser && !isIE9; +var TRANSITION = 'transition'; +var ANIMATION = 'animation'; + +// Transition property/event sniffing +var transitionProp = 'transition'; +var transitionEndEvent = 'transitionend'; +var animationProp = 'animation'; +var animationEndEvent = 'animationend'; +if (hasTransition) { + /* istanbul ignore if */ + if (window.ontransitionend === undefined && + window.onwebkittransitionend !== undefined) { + transitionProp = 'WebkitTransition'; + transitionEndEvent = 'webkitTransitionEnd'; + } + if (window.onanimationend === undefined && + window.onwebkitanimationend !== undefined) { + animationProp = 'WebkitAnimation'; + animationEndEvent = 'webkitAnimationEnd'; + } +} + +var raf = (inBrowser && window.requestAnimationFrame) || setTimeout; +function nextFrame (fn) { + raf(function () { + raf(fn); + }); +} + +function addTransitionClass (el, cls) { + (el._transitionClasses || (el._transitionClasses = [])).push(cls); + addClass(el, cls); +} + +function removeTransitionClass (el, cls) { + if (el._transitionClasses) { + remove$1(el._transitionClasses, cls); + } + removeClass(el, cls); +} + +function whenTransitionEnds ( + el, + expectedType, + cb +) { + var ref = getTransitionInfo(el, expectedType); + var type = ref.type; + var timeout = ref.timeout; + var propCount = ref.propCount; + if (!type) { return cb() } + var event = type === TRANSITION ? transitionEndEvent : animationEndEvent; + var ended = 0; + var end = function () { + el.removeEventListener(event, onEnd); + cb(); + }; + var onEnd = function (e) { + if (e.target === el) { + if (++ended >= propCount) { + end(); + } + } + }; + setTimeout(function () { + if (ended < propCount) { + end(); + } + }, timeout + 1); + el.addEventListener(event, onEnd); +} + +var transformRE = /\b(transform|all)(,|$)/; + +function getTransitionInfo (el, expectedType) { + var styles = window.getComputedStyle(el); + var transitioneDelays = styles[transitionProp + 'Delay'].split(', '); + var transitionDurations = styles[transitionProp + 'Duration'].split(', '); + var transitionTimeout = getTimeout(transitioneDelays, transitionDurations); + var animationDelays = styles[animationProp + 'Delay'].split(', '); + var animationDurations = styles[animationProp + 'Duration'].split(', '); + var animationTimeout = getTimeout(animationDelays, animationDurations); + + var type; + var timeout = 0; + var propCount = 0; + /* istanbul ignore if */ + if (expectedType === TRANSITION) { + if (transitionTimeout > 0) { + type = TRANSITION; + timeout = transitionTimeout; + propCount = transitionDurations.length; + } + } else if (expectedType === ANIMATION) { + if (animationTimeout > 0) { + type = ANIMATION; + timeout = animationTimeout; + propCount = animationDurations.length; + } + } else { + timeout = Math.max(transitionTimeout, animationTimeout); + type = timeout > 0 + ? transitionTimeout > animationTimeout + ? TRANSITION + : ANIMATION + : null; + propCount = type + ? type === TRANSITION + ? transitionDurations.length + : animationDurations.length + : 0; + } + var hasTransform = + type === TRANSITION && + transformRE.test(styles[transitionProp + 'Property']); + return { + type: type, + timeout: timeout, + propCount: propCount, + hasTransform: hasTransform + } +} + +function getTimeout (delays, durations) { + return Math.max.apply(null, durations.map(function (d, i) { + return toMs(d) + toMs(delays[i]) + })) +} + +function toMs (s) { + return Number(s.slice(0, -1)) * 1000 +} + +/* */ + +function enter (vnode) { + var el = vnode.elm; + + // call leave callback now + if (el._leaveCb) { + el._leaveCb.cancelled = true; + el._leaveCb(); + } + + var data = resolveTransition(vnode.data.transition); + if (!data) { + return + } + + /* istanbul ignore if */ + if (el._enterCb || el.nodeType !== 1) { + return + } + + var css = data.css; + var type = data.type; + var enterClass = data.enterClass; + var enterActiveClass = data.enterActiveClass; + var appearClass = data.appearClass; + var appearActiveClass = data.appearActiveClass; + var beforeEnter = data.beforeEnter; + var enter = data.enter; + var afterEnter = data.afterEnter; + var enterCancelled = data.enterCancelled; + var beforeAppear = data.beforeAppear; + var appear = data.appear; + var afterAppear = data.afterAppear; + var appearCancelled = data.appearCancelled; + + // activeInstance will always be the component managing this + // transition. One edge case to check is when the is placed + // as the root node of a child component. In that case we need to check + // 's parent for appear check. + var transitionNode = activeInstance.$vnode; + var context = transitionNode && transitionNode.parent + ? transitionNode.parent.context + : activeInstance; + + var isAppear = !context._isMounted || !vnode.isRootInsert; + + if (isAppear && !appear && appear !== '') { + return + } + + var startClass = isAppear ? appearClass : enterClass; + var activeClass = isAppear ? appearActiveClass : enterActiveClass; + var beforeEnterHook = isAppear ? (beforeAppear || beforeEnter) : beforeEnter; + var enterHook = isAppear ? (typeof appear === 'function' ? appear : enter) : enter; + var afterEnterHook = isAppear ? (afterAppear || afterEnter) : afterEnter; + var enterCancelledHook = isAppear ? (appearCancelled || enterCancelled) : enterCancelled; + + var expectsCSS = css !== false && !isIE9; + var userWantsControl = + enterHook && + // enterHook may be a bound method which exposes + // the length of original fn as _length + (enterHook._length || enterHook.length) > 1; + + var cb = el._enterCb = once(function () { + if (expectsCSS) { + removeTransitionClass(el, activeClass); + } + if (cb.cancelled) { + if (expectsCSS) { + removeTransitionClass(el, startClass); + } + enterCancelledHook && enterCancelledHook(el); + } else { + afterEnterHook && afterEnterHook(el); + } + el._enterCb = null; + }); + + if (!vnode.data.show) { + // remove pending leave element on enter by injecting an insert hook + mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', function () { + var parent = el.parentNode; + var pendingNode = parent && parent._pending && parent._pending[vnode.key]; + if (pendingNode && pendingNode.tag === vnode.tag && pendingNode.elm._leaveCb) { + pendingNode.elm._leaveCb(); + } + enterHook && enterHook(el, cb); + }, 'transition-insert'); + } + + // start enter transition + beforeEnterHook && beforeEnterHook(el); + if (expectsCSS) { + addTransitionClass(el, startClass); + addTransitionClass(el, activeClass); + nextFrame(function () { + removeTransitionClass(el, startClass); + if (!cb.cancelled && !userWantsControl) { + whenTransitionEnds(el, type, cb); + } }); } - /** - * Debounce a function so it only gets called after the - * input stops arriving after the given wait period. - * - * @param {Function} func - * @param {Number} wait - * @return {Function} - the debounced function - */ - - function _debounce(func, wait) { - var timeout, args, context, timestamp, result; - var later = function later() { - var last = Date.now() - timestamp; - if (last < wait && last >= 0) { - timeout = setTimeout(later, wait - last); - } else { - timeout = null; - result = func.apply(context, args); - if (!timeout) context = args = null; - } - }; - return function () { - context = this; - args = arguments; - timestamp = Date.now(); - if (!timeout) { - timeout = setTimeout(later, wait); - } - return result; - }; + if (vnode.data.show) { + enterHook && enterHook(el, cb); } - /** - * Manual indexOf because it's slightly faster than - * native. - * - * @param {Array} arr - * @param {*} obj - */ + if (!expectsCSS && !userWantsControl) { + cb(); + } +} - function indexOf(arr, obj) { - var i = arr.length; - while (i--) { - if (arr[i] === obj) return i; +function leave (vnode, rm) { + var el = vnode.elm; + + // call enter callback now + if (el._enterCb) { + el._enterCb.cancelled = true; + el._enterCb(); + } + + var data = resolveTransition(vnode.data.transition); + if (!data) { + return rm() + } + + /* istanbul ignore if */ + if (el._leaveCb || el.nodeType !== 1) { + return + } + + var css = data.css; + var type = data.type; + var leaveClass = data.leaveClass; + var leaveActiveClass = data.leaveActiveClass; + var beforeLeave = data.beforeLeave; + var leave = data.leave; + var afterLeave = data.afterLeave; + var leaveCancelled = data.leaveCancelled; + var delayLeave = data.delayLeave; + + var expectsCSS = css !== false && !isIE9; + var userWantsControl = + leave && + // leave hook may be a bound method which exposes + // the length of original fn as _length + (leave._length || leave.length) > 1; + + var cb = el._leaveCb = once(function () { + if (el.parentNode && el.parentNode._pending) { + el.parentNode._pending[vnode.key] = null; } - return -1; - } - - /** - * Make a cancellable version of an async callback. - * - * @param {Function} fn - * @return {Function} - */ - - function cancellable(fn) { - var cb = function cb() { - if (!cb.cancelled) { - return fn.apply(this, arguments); + if (expectsCSS) { + removeTransitionClass(el, leaveActiveClass); + } + if (cb.cancelled) { + if (expectsCSS) { + removeTransitionClass(el, leaveClass); } - }; - cb.cancel = function () { - cb.cancelled = true; - }; - return cb; + leaveCancelled && leaveCancelled(el); + } else { + rm(); + afterLeave && afterLeave(el); + } + el._leaveCb = null; + }); + + if (delayLeave) { + delayLeave(performLeave); + } else { + performLeave(); } - /** - * Check if two values are loosely equal - that is, - * if they are plain objects, do they have the same shape? - * - * @param {*} a - * @param {*} b - * @return {Boolean} - */ - - function looseEqual(a, b) { - /* eslint-disable eqeqeq */ - return a == b || (isObject(a) && isObject(b) ? JSON.stringify(a) === JSON.stringify(b) : false); - /* eslint-enable eqeqeq */ + function performLeave () { + // the delayed leave may have already been cancelled + if (cb.cancelled) { + return + } + // record leaving element + if (!vnode.data.show) { + (el.parentNode._pending || (el.parentNode._pending = {}))[vnode.key] = vnode; + } + beforeLeave && beforeLeave(el); + if (expectsCSS) { + addTransitionClass(el, leaveClass); + addTransitionClass(el, leaveActiveClass); + nextFrame(function () { + removeTransitionClass(el, leaveClass); + if (!cb.cancelled && !userWantsControl) { + whenTransitionEnds(el, type, cb); + } + }); + } + leave && leave(el, cb); + if (!expectsCSS && !userWantsControl) { + cb(); + } } +} - var hasProto = ('__proto__' in {}); - - // Browser environment sniffing - var inBrowser = typeof window !== 'undefined' && Object.prototype.toString.call(window) !== '[object Object]'; - - // detect devtools - var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; - - // UA sniffing for working around browser-specific quirks - var UA = inBrowser && window.navigator.userAgent.toLowerCase(); - var isIE = UA && UA.indexOf('trident') > 0; - var isIE9 = UA && UA.indexOf('msie 9.0') > 0; - var isAndroid = UA && UA.indexOf('android') > 0; - var isIos = UA && /(iphone|ipad|ipod|ios)/i.test(UA); - var iosVersionMatch = isIos && UA.match(/os ([\d_]+)/); - var iosVersion = iosVersionMatch && iosVersionMatch[1].split('_'); - - // detecting iOS UIWebView by indexedDB - var hasMutationObserverBug = iosVersion && Number(iosVersion[0]) >= 9 && Number(iosVersion[1]) >= 3 && !window.indexedDB; - - var transitionProp = undefined; - var transitionEndEvent = undefined; - var animationProp = undefined; - var animationEndEvent = undefined; - - // Transition property/event sniffing - if (inBrowser && !isIE9) { - var isWebkitTrans = window.ontransitionend === undefined && window.onwebkittransitionend !== undefined; - var isWebkitAnim = window.onanimationend === undefined && window.onwebkitanimationend !== undefined; - transitionProp = isWebkitTrans ? 'WebkitTransition' : 'transition'; - transitionEndEvent = isWebkitTrans ? 'webkitTransitionEnd' : 'transitionend'; - animationProp = isWebkitAnim ? 'WebkitAnimation' : 'animation'; - animationEndEvent = isWebkitAnim ? 'webkitAnimationEnd' : 'animationend'; +function resolveTransition (def$$1) { + if (!def$$1) { + return } + /* istanbul ignore else */ + if (typeof def$$1 === 'object') { + var res = {}; + if (def$$1.css !== false) { + extend(res, autoCssTransition(def$$1.name || 'v')); + } + extend(res, def$$1); + return res + } else if (typeof def$$1 === 'string') { + return autoCssTransition(def$$1) + } +} - /** - * Defer a task to execute it asynchronously. Ideally this - * should be executed as a microtask, so we leverage - * MutationObserver if it's available, and fallback to - * setTimeout(0). - * - * @param {Function} cb - * @param {Object} ctx - */ +var autoCssTransition = cached(function (name) { + return { + enterClass: (name + "-enter"), + leaveClass: (name + "-leave"), + appearClass: (name + "-enter"), + enterActiveClass: (name + "-enter-active"), + leaveActiveClass: (name + "-leave-active"), + appearActiveClass: (name + "-enter-active") + } +}); - var nextTick = (function () { - var callbacks = []; - var pending = false; - var timerFunc; - function nextTickHandler() { - pending = false; - var copies = callbacks.slice(0); - callbacks = []; - for (var i = 0; i < copies.length; i++) { - copies[i](); +function once (fn) { + var called = false; + return function () { + if (!called) { + called = true; + fn(); + } + } +} + +var transition = inBrowser ? { + create: function create (_, vnode) { + if (!vnode.data.show) { + enter(vnode); + } + }, + remove: function remove (vnode, rm) { + /* istanbul ignore else */ + if (!vnode.data.show) { + leave(vnode, rm); + } else { + rm(); + } + } +} : {}; + +var platformModules = [ + attrs, + klass, + events, + domProps, + style, + transition +]; + +/* */ + +// the directive module should be applied last, after all +// built-in modules have been applied. +var modules = platformModules.concat(baseModules); + +var patch$1 = createPatchFunction({ nodeOps: nodeOps, modules: modules }); + +/** + * Not type checking this file because flow doesn't like attaching + * properties to Elements. + */ + +var modelableTagRE = /^input|select|textarea|vue-component-[0-9]+(-[0-9a-zA-Z_\-]*)?$/; + +/* istanbul ignore if */ +if (isIE9) { + // http://www.matts411.com/post/internet-explorer-9-oninput/ + document.addEventListener('selectionchange', function () { + var el = document.activeElement; + if (el && el.vmodel) { + trigger(el, 'input'); + } + }); +} + +var model = { + inserted: function inserted (el, binding, vnode) { + { + if (!modelableTagRE.test(vnode.tag)) { + warn( + "v-model is not supported on element type: <" + (vnode.tag) + ">. " + + 'If you are working with contenteditable, it\'s recommended to ' + + 'wrap a library dedicated for that purpose inside a custom component.', + vnode.context + ); } } + if (vnode.tag === 'select') { + var cb = function () { + setSelected(el, binding, vnode.context); + }; + cb(); + /* istanbul ignore if */ + if (isIE || isEdge) { + setTimeout(cb, 0); + } + } else if ( + (vnode.tag === 'textarea' || el.type === 'text') && + !binding.modifiers.lazy + ) { + if (!isAndroid) { + el.addEventListener('compositionstart', onCompositionStart); + el.addEventListener('compositionend', onCompositionEnd); + } + /* istanbul ignore if */ + if (isIE9) { + el.vmodel = true; + } + } + }, + componentUpdated: function componentUpdated (el, binding, vnode) { + if (vnode.tag === 'select') { + setSelected(el, binding, vnode.context); + // in case the options rendered by v-for have changed, + // it's possible that the value is out-of-sync with the rendered options. + // detect such cases and filter out values that no longer has a matchig + // option in the DOM. + var needReset = el.multiple + ? binding.value.some(function (v) { return hasNoMatchingOption(v, el.options); }) + : binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, el.options); + if (needReset) { + trigger(el, 'change'); + } + } + } +}; + +function setSelected (el, binding, vm) { + var value = binding.value; + var isMultiple = el.multiple; + if (isMultiple && !Array.isArray(value)) { + "development" !== 'production' && warn( + "', '']; - - map.thead = map.tbody = map.colgroup = map.caption = map.tfoot = [1, '', '
      ']; - - map.g = map.defs = map.symbol = map.use = map.image = map.text = map.circle = map.ellipse = map.line = map.path = map.polygon = map.polyline = map.rect = [1, '', '']; - - /** - * Check if a node is a supported template node with a - * DocumentFragment content. - * - * @param {Node} node - * @return {Boolean} - */ - - function isRealTemplate(node) { - return isTemplate(node) && isFragment(node.content); - } - - var tagRE$1 = /<([\w:-]+)/; - var entityRE = /&#?\w+?;/; - var commentRE = //g,"$1").replace(//g,"$1")),t.chars&&t.chars(n),""});f+=e.length-h.length,e=h,a("",d,f-v,f)}else{var m=e.indexOf("<");if(0===m){if(/^");if(g>=0){n(g+3);continue}}if(/^");if(y>=0){n(y+2);continue}}var _=e.match(Aa);if(_){n(_[0].length);continue}var b=e.match(ka);if(b){var $=f;n(b[0].length),a(b[0],b[1],$,f);continue}var w=r();if(w){i(w);continue}}var C=void 0;m>=0?(C=e.substring(0,m),n(m)):(C=e,e=""),t.chars&&t.chars(C)}if(e===o)throw new Error("Error parsing template:\n\n"+e)}a()}function tn(e){function t(){(o||(o=[])).push(e.slice(d,i).trim()),d=i+1}var n,r,i,a,o,s=!1,c=!1,u=0,l=0,f=0,d=0;for(i=0;io&&a.push(JSON.stringify(e.slice(o,i)));var s=tn(r[1].trim());a.push("_s("+s+")"),o=i+r[0].length}return o-1:_q("+t+","+r+")"),ln(e,"change","var $$a="+t+",$$el=$event.target,$$c=$$el.checked?("+r+"):("+i+");if(Array.isArray($$a)){var $$v="+n+",$$i=_i($$a,$$v);if($$c){$$i<0&&("+t+"=$$a.concat($$v))}else{$$i>-1&&("+t+"=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}}else{"+t+"=$$c}",null,!0)}function cr(e,t){var n=fn(e,"value")||"null";sn(e,"checked","_q("+t+","+n+")"),ln(e,"change",t+"="+n,null,!0)}function ur(e,t,n){var r=e.attrsMap.type,i=n||{},a=i.lazy,o=i.number,s=i.trim,c=a||Nr&&"range"===r?"change":"input",u=!a&&"range"!==r,l="input"===e.tag||"textarea"===e.tag,f=l?"$event.target.value"+(s?".trim()":""):"$event",d=o||"number"===r?t+"=_n("+f+")":t+"="+f;l&&u&&(d="if($event.target.composing)return;"+d),sn(e,"value",l?"_s("+t+")":"("+t+")"),ln(e,c,d,null,!0)}function lr(e,t){var n=t+'=Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){return "_value" in o ? o._value : o.value})'+(null==e.attrsMap.multiple?"[0]":"");ln(e,"change",n,null,!0)}function fr(e,t){t.value&&sn(e,"textContent","_s("+t.value+")")}function dr(e,t){t.value&&sn(e,"innerHTML","_s("+t.value+")")}function pr(e,t){return t=t?u(u({},Co),t):Co,tr(e,t)}function vr(e,t,n){var r=(t&&t.warn||li,t&&t.delimiters?String(t.delimiters)+e:e);if(wo[r])return wo[r];var i={},a=pr(e,t);i.render=hr(a.render);var o=a.staticRenderFns.length;i.staticRenderFns=new Array(o);for(var s=0;s0,Mr=Lr&&Lr.indexOf("edge/")>0,Pr=Lr&&Lr.indexOf("android")>0,Rr=Lr&&/iphone|ipad|ipod|ios/.test(Lr),Ir=jr&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__,Br=function(){function e(){r=!1;var e=n.slice(0);n.length=0;for(var t=0;t\/=]+)/,ya=/(?:=)/,_a=[/"([^"]*)"+/.source,/'([^']*)'+/.source,/([^\s"'=<>`]+)/.source],ba=new RegExp("^\\s*"+ga.source+"(?:\\s*("+ya.source+")\\s*(?:"+_a.join("|")+"))?"),$a="[a-zA-Z_][\\w\\-\\.]*",wa="((?:"+$a+"\\:)?"+$a+")",Ca=new RegExp("^<"+wa),xa=/^\s*(\/?)>/,ka=new RegExp("^<\\/"+wa+"[^>]*>"),Aa=/^]+>/i,Oa=!1;"x".replace(/x(.)?/g,function(e,t){Oa=""===t});var Ta,Sa,Ea,ja,La,Na,Da,Ma,Pa,Ra,Ia,Ba,Fa,Ha,Ua,za,Va,Ja=n("script,style",!0),qa={},Ka=/</g,Wa=/>/g,Za=/ /g,Ga=/&/g,Ya=/"/g,Qa=/\{\{((?:.|\n)+?)\}\}/g,Xa=/[-.*+?^${}()|[\]\/\\]/g,eo=o(function(e){var t=e[0].replace(Xa,"\\$&"),n=e[1].replace(Xa,"\\$&");return new RegExp(t+"((?:.|\\n)+?)"+n,"g")}),to=/^v-|^@|^:/,no=/(.*?)\s+(?:in|of)\s+(.*)/,ro=/\(([^,]*),([^,]*)(?:,([^,]*))?\)/,io=/^:|^v-bind:/,ao=/^@|^v-on:/,oo=/:(.*)$/,so=/\.[^\.]+/g,co=/\u2028|\u2029/g,uo=o(Qt),lo=/^xmlns:NS\d+/,fo=/^NS\d+:/,po=o(Ln),vo=/^\s*[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?'\]|\[".*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*\s*$/,ho={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},mo={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:"if($event.target !== $event.currentTarget)return;"},go={bind:Hn,cloak:p},yo=(new RegExp("\\b"+"do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,super,throw,while,yield,delete,export,import,return,switch,default,extends,finally,continue,debugger,function,arguments".split(",").join("\\b|\\b")+"\\b"),{staticKeys:["staticClass"],transformNode:nr,genData:rr}),_o={transformNode:ir,genData:ar},bo=[yo,_o],$o={model:or,text:fr,html:dr},wo=Object.create(null),Co={isIE:Nr,expectHTML:!0,modules:bo,staticKeys:v(bo),directives:$o,isReservedTag:ji,isUnaryTag:Ai,mustUseProp:gi,getTagNamespace:qe,isPreTag:Ei},xo=o(function(e){var t=We(e);return t&&t.innerHTML}),ko=Ce.prototype.$mount;return Ce.prototype.$mount=function(e,t){if(e=e&&We(e),e===document.body||e===document.documentElement)return this;var n=this.$options;if(!n.render){var r=n.template;if(r)if("string"==typeof r)"#"===r.charAt(0)&&(r=xo(r));else{if(!r.nodeType)return this;r=r.innerHTML}else e&&(r=mr(e));if(r){var i=vr(r,{warn:li,shouldDecodeNewlines:ha,delimiters:n.delimiters},this),a=i.render,o=i.staticRenderFns;n.render=a,n.staticRenderFns=o}}return ko.call(this,e,t)},Ce.compile=vr,Ce}); \ No newline at end of file From 6f6119b7389ef7b5e13f2800611d5d7a806e41a5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 10 Nov 2016 15:32:23 +0100 Subject: [PATCH 069/277] Support pipelines API Pass `updated_at` to get only incremental changes since last update --- .../projects/pipelines_controller.rb | 16 ++++ app/models/ci/pipeline.rb | 29 +++++-- app/models/commit_status.rb | 11 +-- app/serializers/pipeline_action_entity.rb | 14 ++++ app/serializers/pipeline_artifact_entity.rb | 14 ++++ app/serializers/pipeline_entity.rb | 84 +++++++++++++++++++ app/serializers/pipeline_serializer.rb | 3 + app/serializers/pipeline_stage_entity.rb | 19 +++++ app/serializers/request_aware_entity.rb | 8 ++ .../projects/ci/pipelines/_pipeline.html.haml | 7 +- app/views/projects/commit/_pipeline.html.haml | 2 +- .../projects/commit/_pipelines_list.haml | 2 +- app/views/projects/pipelines/index.html.haml | 3 +- 13 files changed, 190 insertions(+), 22 deletions(-) create mode 100644 app/serializers/pipeline_action_entity.rb create mode 100644 app/serializers/pipeline_artifact_entity.rb create mode 100644 app/serializers/pipeline_entity.rb create mode 100644 app/serializers/pipeline_serializer.rb create mode 100644 app/serializers/pipeline_stage_entity.rb diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 371cc3787fb..0b3503c6848 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -11,6 +11,22 @@ class Projects::PipelinesController < Projects::ApplicationController @running_or_pending_count = PipelinesFinder.new(project).execute(scope: 'running').count @pipelines_count = PipelinesFinder.new(project).execute.count + @last_updated = params[:updated_at] + + respond_to do |format| + format.html + format.json do + render json: { + pipelines: PipelineSerializer.new(project: @project). + represent(@pipelines, current_user: current_user, last_updated: @last_updated), + updated_at: Time.now, + count: { + all: @pipelines_count, + running_or_pending: @running_or_pending_count + } + } + end + end end def new diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 3fee6c18770..37c2c86e3a3 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -98,19 +98,38 @@ module Ci sha[0...8] end - def self.stages - # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries - CommitStatus.where(pipeline: pluck(:id)).stages - end - def self.total_duration where.not(duration: nil).sum(:duration) end + def stages + statuses.group('stage').select(:stage) + .order('max(stage_idx)') + end + + def stages_with_statuses + status_sql = statuses.latest.where('stage=sg.stage').status_sql + + stages_with_statuses = CommitStatus.from(self.stages, :sg). + pluck('sg.stage', status_sql) + + stages_with_statuses.map do |stage| + OpenStruct.new( + name: stage.first, + status: stage.last, + pipeline: self + ) + end + end + def stages_with_latest_statuses statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage) end + def artifacts + builds.latest.with_artifacts_not_expired + end + def project_id project.id end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index d159fc6c5c7..c0b7f44fa9e 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -119,16 +119,7 @@ class CommitStatus < ActiveRecord::Base def self.stages # We group by stage name, but order stages by theirs' index - unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage') - end - - def self.stages_status - # We execute subquery for each stage to calculate a stage status - statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql) - statuses.inject({}) do |h, k| - h[k.first] = k.last - h - end + unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').select('sg.stage') end def failed_but_allowed? diff --git a/app/serializers/pipeline_action_entity.rb b/app/serializers/pipeline_action_entity.rb new file mode 100644 index 00000000000..d45341f09d2 --- /dev/null +++ b/app/serializers/pipeline_action_entity.rb @@ -0,0 +1,14 @@ +class PipelineActionEntity < Grape::Entity + include RequestAwareEntity + + expose :name do |build| + build.name.humanize + end + + expose :url do |build| + play_namespace_project_build_path( + pipeline.project.namespace, + pipeline.project, + build) + end +end diff --git a/app/serializers/pipeline_artifact_entity.rb b/app/serializers/pipeline_artifact_entity.rb new file mode 100644 index 00000000000..01393dbea2d --- /dev/null +++ b/app/serializers/pipeline_artifact_entity.rb @@ -0,0 +1,14 @@ +class PipelineArtifactEntity < Grape::Entity + include RequestAwareEntity + + expose :name do |build| + build.name + end + + expose :url do |build| + download_namespace_project_build_artifacts_path( + pipeline.project.namespace, + pipeline.project, + build) + end +end diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb new file mode 100644 index 00000000000..7afce8fd15b --- /dev/null +++ b/app/serializers/pipeline_entity.rb @@ -0,0 +1,84 @@ +class PipelineEntity < Grape::Entity + include RequestAwareEntity + + expose :id + expose :user, if: -> (pipeline, opts) { created?(pipeline, opts) }, using: UserEntity + + expose :status + expose :duration + expose :finished_at + expose :stages_with_statuses, as: :stages, if: -> (pipeline, opts) { updated?(pipeline, opts) }, using: PipelineStageEntity + expose :artifacts, if: -> (pipeline, opts) { updated?(pipeline, opts) }, using: PipelineArtifactEntity + expose :manual_actions, if: -> (pipeline, opts) { updated?(pipeline, opts) }, using: PipelineActionEntity + + expose :flags, if: -> (pipeline, opts) { created?(pipeline, opts) } do + expose :latest?, as: :latest + expose :triggered?, as: :triggered + expose :yaml_errors?, as: :yaml_errors do |pipeline| + pipeline.yaml_errors.present? + end + expose :stuck?, as: :stuck do |pipeline| + pipeline.builds.any?(&:stuck?) + end + end + + expose :ref, if: -> (pipeline, opts) { created?(pipeline, opts) } do + expose :name do |pipeline| + pipeline.ref + end + + expose :ref_url do |pipeline| + namespace_project_tree_url( + pipeline.project.namespace, + pipeline.project, + id: pipeline.ref) + end + + expose :tag? + end + + expose :commit, if: -> (pipeline, opts) { created?(pipeline, opts) } do + expose :short_sha + + expose :sha_url do |pipeline| + namespace_project_commit_path( + pipeline.project.namespace, + pipeline.project, + pipeline.sha) + end + + expose :title do |pipeline| + pipeline.commit.try(:title) + end + + expose :author, using: UserEntity do |pipeline| + pipeline.commit.try(:author) + end + end + + expose :retry_url, if: -> (pipeline, opts) { updated?(pipeline, opts) } do |pipeline| + can?(current_user, :update_pipeline, pipeline.project) && + pipeline.retryable? && + retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) + end + + expose :cancel_url, if: -> (pipeline, opts) { updated?(pipeline, opts) } do |pipeline| + can?(current_user, :update_pipeline, pipeline.project) && + pipeline.cancelable? && + cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) + end + + private + + def last_updated(opts) + opts.fetch(:last_updated) + end + + def created?(pipeline, opts) + !last_updated(opts) || pipeline.created_at > last_updated(opts) + end + + def updated?(pipeline, opts) + !last_updated(opts) || pipeline.updated_at > last_updated(opts) + end +end diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb new file mode 100644 index 00000000000..f7abbec7d45 --- /dev/null +++ b/app/serializers/pipeline_serializer.rb @@ -0,0 +1,3 @@ +class PipelineSerializer < BaseSerializer + entity PipelineEntity +end diff --git a/app/serializers/pipeline_stage_entity.rb b/app/serializers/pipeline_stage_entity.rb new file mode 100644 index 00000000000..230ef8a22da --- /dev/null +++ b/app/serializers/pipeline_stage_entity.rb @@ -0,0 +1,19 @@ +class PipelineStageEntity < Grape::Entity + include RequestAwareEntity + + expose :name do |stage| + stage.name + end + + expose :status do |stage| + stage.status || 'not found' + end + + expose :url do |stage| + namespace_project_pipeline_path( + stage.pipeline.project.namespace, + stage.pipeline.project, + stage.pipeline.id, + anchor: stage.name) + end +end diff --git a/app/serializers/request_aware_entity.rb b/app/serializers/request_aware_entity.rb index ff8c1142abc..7a096d9d5a8 100644 --- a/app/serializers/request_aware_entity.rb +++ b/app/serializers/request_aware_entity.rb @@ -8,4 +8,12 @@ module RequestAwareEntity def request @options.fetch(:request) end + + def current_user + @options.fetch(:current_user) + end + + def can?(object, action, subject) + Ability.allowed?(object, action, subject) + end end diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 2a2d24be736..50817f28d78 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -43,9 +43,10 @@ - stages_status = pipeline.statuses.latest.stages_status %td.stage-cell - - stages.each do |stage| - - status = stages_status[stage] - - tooltip = "#{stage.titleize}: #{status || 'not found'}" + - pipeline.statuses.latest.stages_status.each do |stage| + - name = stage.first + - status = stage.last + - tooltip = "#{name.titleize}: #{status || 'not found'}" - if status .stage-container = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index d6916fb7f1a..516893fc6e5 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -66,5 +66,5 @@ - if pipeline.project.build_coverage_enabled? %th Coverage %th - - pipeline.statuses.relevant.stages.each do |stage| + - pipeline.stages.each do |stage| = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage) diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index 2dc91a9b762..7f42fde0fea 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -12,4 +12,4 @@ %th Stages %th %th - = render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, show_commit: false + = render pipelines, commit_sha: true, stage: true, allow_retry: true, show_commit: false diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 6f70b239826..4f9fb699abc 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -37,7 +37,6 @@ %span CI Lint %div.content-list.pipelines{"data-project-id": "#{@project.id}", "data-count": "#{@pipelines_count}"} - - stages = @pipelines.stages - if @pipelines.blank? %div .nothing-here-block No pipelines to show @@ -52,7 +51,7 @@ %th %th.hidden-xs - = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages + = render @pipelines, commit_sha: true, stage: true, allow_retry: true = paginate @pipelines, theme: 'gitlab' - else .vue-pipelines-index From 8012abf4ccee90d02f3ff386115f73eb24a87c20 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 10 Nov 2016 17:50:55 +0100 Subject: [PATCH 070/277] Fix failures [ci skip] --- app/serializers/pipeline_action_entity.rb | 4 ++-- app/serializers/pipeline_artifact_entity.rb | 4 ++-- app/serializers/pipeline_entity.rb | 14 ++++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/serializers/pipeline_action_entity.rb b/app/serializers/pipeline_action_entity.rb index d45341f09d2..475b5a55316 100644 --- a/app/serializers/pipeline_action_entity.rb +++ b/app/serializers/pipeline_action_entity.rb @@ -7,8 +7,8 @@ class PipelineActionEntity < Grape::Entity expose :url do |build| play_namespace_project_build_path( - pipeline.project.namespace, - pipeline.project, + build.project.namespace, + build.project, build) end end diff --git a/app/serializers/pipeline_artifact_entity.rb b/app/serializers/pipeline_artifact_entity.rb index 01393dbea2d..9e9f245a6f9 100644 --- a/app/serializers/pipeline_artifact_entity.rb +++ b/app/serializers/pipeline_artifact_entity.rb @@ -7,8 +7,8 @@ class PipelineArtifactEntity < Grape::Entity expose :url do |build| download_namespace_project_build_artifacts_path( - pipeline.project.namespace, - pipeline.project, + build.project.namespace, + build.project, build) end end diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 7afce8fd15b..c57c08b4830 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -4,12 +4,14 @@ class PipelineEntity < Grape::Entity expose :id expose :user, if: -> (pipeline, opts) { created?(pipeline, opts) }, using: UserEntity - expose :status - expose :duration - expose :finished_at - expose :stages_with_statuses, as: :stages, if: -> (pipeline, opts) { updated?(pipeline, opts) }, using: PipelineStageEntity - expose :artifacts, if: -> (pipeline, opts) { updated?(pipeline, opts) }, using: PipelineArtifactEntity - expose :manual_actions, if: -> (pipeline, opts) { updated?(pipeline, opts) }, using: PipelineActionEntity + expose :details, if: -> (pipeline, opts) { updated?(pipeline, opts) } do + expose :status + expose :duration + expose :finished_at + expose :stages_with_statuses, as: :stages, using: PipelineStageEntity + expose :artifacts, using: PipelineArtifactEntity + expose :manual_actions, using: PipelineActionEntity + end expose :flags, if: -> (pipeline, opts) { created?(pipeline, opts) } do expose :latest?, as: :latest From f2e64fddc66e456029928c094d1af19ed88c7ab6 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 10 Nov 2016 09:51:45 -0700 Subject: [PATCH 071/277] attemp --- app/assets/javascripts/vue_pipelines_index/index.js.es6 | 2 +- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 4 +++- app/views/projects/pipelines/index.html.haml | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index c72899f1c19..c1abc6602cd 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -23,7 +23,7 @@ return new Vue({ el: '.vue-pipelines-index', data: { - scope: project.dataset.projectId, + scope: project.dataset.url, count: project.dataset.count, store: new gl.PipelineStore(), }, diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 1a9d13f15a2..b393a347fba 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -8,8 +8,10 @@ gl.PipelineStore = class { fetchDataLoop(Vue, pageNum) { const goFetch = () => - this.$http.get(`${api}/${this.scope}/pipelines${paginate}${pageNum}`) + // const url = `${api}/${this.scope}/pipelines${paginate}${pageNum}` + this.$http.get('/gitlab-org/gitlab-shell/pipelines.json?page=1') .then((response) => { + debugger Vue.set(this, 'pipelines', JSON.parse(response.body)); }, () => new Flash( 'Something went wrong on our end.' diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 4f9fb699abc..1515728c563 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -35,8 +35,7 @@ = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint - - %div.content-list.pipelines{"data-project-id": "#{@project.id}", "data-count": "#{@pipelines_count}"} + %div.content-list.pipelines{data: {url: namespace_project_pipelines_path(@project.namespace, @project, format: :json), "data-count": "#{@pipelines_count}"}} - if @pipelines.blank? %div .nothing-here-block No pipelines to show From ba2d1fde773c33044c7fe7b298abd09b640bcfc5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 10 Nov 2016 18:15:16 +0100 Subject: [PATCH 072/277] Add `pipeline.url` to API [ci skip] --- app/serializers/pipeline_entity.rb | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index c57c08b4830..6191d63fd7f 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -3,6 +3,12 @@ class PipelineEntity < Grape::Entity expose :id expose :user, if: -> (pipeline, opts) { created?(pipeline, opts) }, using: UserEntity + expose :url do |pipeline| + namespace_project_pipeline_path( + pipeline.project.namespace, + pipeline.project, + pipeline) + end expose :details, if: -> (pipeline, opts) { updated?(pipeline, opts) } do expose :status @@ -29,7 +35,7 @@ class PipelineEntity < Grape::Entity pipeline.ref end - expose :ref_url do |pipeline| + expose :url do |pipeline| namespace_project_tree_url( pipeline.project.namespace, pipeline.project, @@ -39,24 +45,7 @@ class PipelineEntity < Grape::Entity expose :tag? end - expose :commit, if: -> (pipeline, opts) { created?(pipeline, opts) } do - expose :short_sha - - expose :sha_url do |pipeline| - namespace_project_commit_path( - pipeline.project.namespace, - pipeline.project, - pipeline.sha) - end - - expose :title do |pipeline| - pipeline.commit.try(:title) - end - - expose :author, using: UserEntity do |pipeline| - pipeline.commit.try(:author) - end - end + expose :commit, if: -> (pipeline, opts) { created?(pipeline, opts) }, using: CommitEntity expose :retry_url, if: -> (pipeline, opts) { updated?(pipeline, opts) } do |pipeline| can?(current_user, :update_pipeline, pipeline.project) && From a0a0a57092858148f506d98085b9121e562d347e Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 10 Nov 2016 10:16:27 -0700 Subject: [PATCH 073/277] about to pull pipeline_url change --- .../javascripts/vue_pipelines_index/pipelines.js.es6 | 10 ++++------ .../javascripts/vue_pipelines_index/store.js.es6 | 11 ++++------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index b1ef73d4caa..f2a2fe53cf7 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -30,12 +30,9 @@ created() { const url = window.location.toString(); if (~url.indexOf('?')) this.pagenum = url.split('?')[1].split('=')[1]; - this.store.fetchDataLoop.call(this, Vue, this.pagenum); + this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope); }, methods: { - shortsha(pipeline) { - return pipeline.sha.slice(0, 8); - }, changepage(event, last) { const text = event.target.innerText; if (text === '...') return; @@ -47,7 +44,8 @@ window.history.pushState({}, null, `?p=${this.pagenum}`); clearInterval(this.intervalId); - this.store.fetchDataLoop.call(this, Vue, this.pagenum); + debugger + this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope); }, pipelineurl(id) { return `pipelines/${id}`; @@ -72,7 +70,7 @@ diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index b393a347fba..67da7270a58 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -2,17 +2,14 @@ /* eslint-disable no-param-reassign */ ((gl) => { - const api = '/api/v3/projects'; - const paginate = '?per_page=5&page='; - gl.PipelineStore = class { - fetchDataLoop(Vue, pageNum) { + fetchDataLoop(Vue, pageNum, url) { const goFetch = () => - // const url = `${api}/${this.scope}/pipelines${paginate}${pageNum}` - this.$http.get('/gitlab-org/gitlab-shell/pipelines.json?page=1') + this.$http.get(`${url}?page=${pageNum}`) .then((response) => { debugger - Vue.set(this, 'pipelines', JSON.parse(response.body)); + const res = JSON.parse(response.body) + Vue.set(this, 'pipelines', res.pipelines); }, () => new Flash( 'Something went wrong on our end.' )); From 1a5027a03d15c21ad5b6b8a67fc762378009a769 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 10 Nov 2016 12:22:16 -0700 Subject: [PATCH 074/277] re-wired a lot of the application - need a few more API values --- .../vue_pipelines_index/branch_commit.js.es6 | 37 +++--- .../vue_pipelines_index/index.js.es6 | 1 + .../vue_pipelines_index/pipeline_url.js.es6 | 11 +- .../vue_pipelines_index/pipelines.js.es6 | 24 +--- .../vue_pipelines_index/stage.js.es6 | 31 +++++ .../vue_pipelines_index/stages.js.es6 | 107 ++---------------- .../vue_pipelines_index/store.js.es6 | 2 +- .../vue_pipelines_index/time_ago.js.es6 | 4 +- .../vue_pipelines_status/failed.js.es6 | 5 +- .../vue_pipelines_status/pending.js.es6 | 5 +- .../vue_pipelines_status/running.js.es6 | 5 +- .../vue_pipelines_status/status.js.es6 | 18 ++- 12 files changed, 85 insertions(+), 165 deletions(-) create mode 100644 app/assets/javascripts/vue_pipelines_index/stage.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 index 1f743999f4d..d41ff4d9b5b 100644 --- a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 @@ -3,10 +3,13 @@ ((gl) => { gl.VueBranchCommit = Vue.extend({ - props: ['pipeline', 'shortsha'], - methods: { - commiturl(sha) { - return `./commit/${sha}`; + props: ['pipeline'], + computed: { + mailto() { + return `mailto:${this.pipeline.commit.author_email}`; + }, + alt() { + return `${this.pipeline.commit.author_name}'s avatar` }, }, template: ` @@ -14,7 +17,7 @@

      - master + {{pipeline.ref.name}}
      @@ -22,36 +25,28 @@
      - {{shortsha(pipeline)}} + {{pipeline.commit.short_id}}

      - + - - fix broken repo 500 errors in UI and added relevant specs + {{pipeline.commit.title}}

      diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index c1abc6602cd..f84d2eb44c3 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -6,6 +6,7 @@ //= require ./store.js.es6 //= require ./pipeline_url.js.es6 //= require ./pipeline_head.js.es6 +//= require ./stage.js.es6 //= require ./stages.js.es6 //= require ./pipeline_actions.js.es6 //= require ./branch_commit.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 index 148fe58d9ce..d0cbf99a344 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 @@ -8,15 +8,20 @@ }, props: [ 'pipeline', - 'pipelineurl', ], + computed: { + user() { + if (!this.pipeline.user) return 'API'; + return this.pipeline.user; + }, + }, template: ` - + #{{pipeline.id}} by - {{pipeline.user}} + {{user}} `, }); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index f2a2fe53cf7..e523105a7a5 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -44,12 +44,8 @@ window.history.pushState({}, null, `?p=${this.pagenum}`); clearInterval(this.intervalId); - debugger this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope); }, - pipelineurl(id) { - return `pipelines/${id}`; - }, }, template: `
      @@ -58,22 +54,10 @@ - - - - - - - + + + + diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 new file mode 100644 index 00000000000..f983226a4ce --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -0,0 +1,31 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueStage = Vue.extend({ + components: { + 'running-icon': gl.VueRunningIcon, + 'pending-icon': gl.VuePendingIcon, + 'failed-icon': gl.VueFailedIcon, + 'success-icon': gl.VueSuccessIcon, + }, + props: ['stage'], + computed: { + buildStatus() { + return `Build: ${this.stage.status}`; + }, + }, + template: ` + + + + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/stages.js.es6 b/app/assets/javascripts/vue_pipelines_index/stages.js.es6 index da8c9280486..f91c44174de 100644 --- a/app/assets/javascripts/vue_pipelines_index/stages.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stages.js.es6 @@ -3,106 +3,17 @@ ((gl) => { gl.VueStages = Vue.extend({ + components: { + 'vue-stage': gl.VueStage, + }, + props: ['pipeline'], template: ` - - - - - - - - -
      - - - - - - - - +
      +
      `, diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 67da7270a58..66d9688a249 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -7,8 +7,8 @@ const goFetch = () => this.$http.get(`${url}?page=${pageNum}`) .then((response) => { + const res = JSON.parse(response.body); debugger - const res = JSON.parse(response.body) Vue.set(this, 'pipelines', res.pipelines); }, () => new Flash( 'Something went wrong on our end.' diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index fe325ded088..f9318130356 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -24,7 +24,7 @@ finishdate() { const date = new Date( new Date( - this.pipeline.finished_at + this.pipeline.details.finished_at ).getTime() - new Date( this.pipeline.started_at ).getTime() @@ -51,7 +51,7 @@ options.timeZoneName = 'short'; - const finished = this.pipeline.finished_at; + const finished = this.pipeline.details.finished_at; if (!finished) return false; diff --git a/app/assets/javascripts/vue_pipelines_status/failed.js.es6 b/app/assets/javascripts/vue_pipelines_status/failed.js.es6 index a877cfc688e..0988e2c04ab 100644 --- a/app/assets/javascripts/vue_pipelines_status/failed.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/failed.js.es6 @@ -7,12 +7,11 @@ 'vue-failed-icon': gl.VueFailedIcon, }, props: [ - 'scope', - 'scopeurl', + 'pipeline', ], template: ` - +  failed diff --git a/app/assets/javascripts/vue_pipelines_status/pending.js.es6 b/app/assets/javascripts/vue_pipelines_status/pending.js.es6 index 1742e52a2bb..30e430c1636 100644 --- a/app/assets/javascripts/vue_pipelines_status/pending.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/pending.js.es6 @@ -7,12 +7,11 @@ 'vue-pending-icon': gl.VuePendingIcon, }, props: [ - 'scope', - 'scopeurl', + 'pipeline', ], template: ` - +  pending diff --git a/app/assets/javascripts/vue_pipelines_status/running.js.es6 b/app/assets/javascripts/vue_pipelines_status/running.js.es6 index bc2b2b0b1e9..57274422b15 100644 --- a/app/assets/javascripts/vue_pipelines_status/running.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/running.js.es6 @@ -7,12 +7,11 @@ 'vue-running-icon': gl.VueRunningIcon, }, props: [ - 'scope', - 'scopeurl', + 'pipeline', ], template: ` - +  running diff --git a/app/assets/javascripts/vue_pipelines_status/status.js.es6 b/app/assets/javascripts/vue_pipelines_status/status.js.es6 index 6695b600488..3f8cb1164ec 100644 --- a/app/assets/javascripts/vue_pipelines_status/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/status.js.es6 @@ -9,27 +9,23 @@ 'vue-failed-scope': gl.VueFailedScope, }, props: [ - 'scope', - 'scopeurl', + 'pipeline', ], template: ` From 38bb34889c5180b9dad301f3a8d478e271fb2aaf Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 10 Nov 2016 12:46:29 -0700 Subject: [PATCH 075/277] re-wire count for pagination - refactor --- app/assets/javascripts/vue_pagination/index.js.es6 | 2 +- .../javascripts/vue_pipelines_index/branch_commit.js.es6 | 2 +- app/assets/javascripts/vue_pipelines_index/index.js.es6 | 2 -- .../javascripts/vue_pipelines_index/pipelines.js.es6 | 8 ++++++-- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 2 +- .../javascripts/vue_pipelines_status/pending.js.es6 | 2 +- app/views/projects/pipelines/index.html.haml | 2 +- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 089f7ca0281..5f844ae8923 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -10,7 +10,7 @@ ], computed: { last() { - return Math.ceil(+this.count / 5); + return Math.ceil(+this.count / 30); }, getItems() { const total = +this.last; diff --git a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 index d41ff4d9b5b..d0459f2ae6b 100644 --- a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 @@ -9,7 +9,7 @@ return `mailto:${this.pipeline.commit.author_email}`; }, alt() { - return `${this.pipeline.commit.author_name}'s avatar` + return `${this.pipeline.commit.author_name}'s avatar`; }, }, template: ` diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index f84d2eb44c3..3a7842f305b 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -25,7 +25,6 @@ el: '.vue-pipelines-index', data: { scope: project.dataset.url, - count: project.dataset.count, store: new gl.PipelineStore(), }, components: { @@ -36,7 +35,6 @@
      diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index e523105a7a5..4a3750be81a 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -20,12 +20,15 @@ currentPage: '', intervalId: '', pagenum: 1, + count: { + all: 0, + running_or_pending: 0, + }, }; }, props: [ 'scope', 'store', - 'count', ], created() { const url = window.location.toString(); @@ -65,9 +68,10 @@
      diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 66d9688a249..75796c936d7 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -8,8 +8,8 @@ this.$http.get(`${url}?page=${pageNum}`) .then((response) => { const res = JSON.parse(response.body); - debugger Vue.set(this, 'pipelines', res.pipelines); + Vue.set(this, 'count', res.count); }, () => new Flash( 'Something went wrong on our end.' )); diff --git a/app/assets/javascripts/vue_pipelines_status/pending.js.es6 b/app/assets/javascripts/vue_pipelines_status/pending.js.es6 index 30e430c1636..579c6f71a2e 100644 --- a/app/assets/javascripts/vue_pipelines_status/pending.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/pending.js.es6 @@ -20,4 +20,4 @@ `, }); -})(window.gl || (window.gl = {})); \ No newline at end of file +})(window.gl || (window.gl = {})); diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 1515728c563..569219b7669 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -35,7 +35,7 @@ = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint - %div.content-list.pipelines{data: {url: namespace_project_pipelines_path(@project.namespace, @project, format: :json), "data-count": "#{@pipelines_count}"}} + %div.content-list.pipelines{data: {url: namespace_project_pipelines_path(@project.namespace, @project, format: :json)}} - if @pipelines.blank? %div .nothing-here-block No pipelines to show From f41c3c02b1c20f7b13013ab135f8d2ff056aea05 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 10 Nov 2016 13:17:33 -0700 Subject: [PATCH 076/277] add update pipelines endpoint checker --- .../vue_pipelines_index/pipelines.js.es6 | 2 +- .../vue_pipelines_index/store.js.es6 | 38 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 4a3750be81a..7b0e9b41be0 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -17,8 +17,8 @@ data() { return { pipelines: [], - currentPage: '', intervalId: '', + updatedAt: '', pagenum: 1, count: { all: 0, diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 75796c936d7..abe8a4d0a52 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -2,22 +2,58 @@ /* eslint-disable no-param-reassign */ ((gl) => { + class PipelineUpdater { + constructor(pipelines) { + this.pipelines = pipelines; + this.updateClone = (update, newPipe) => { + update.forEach((pipe) => { + if (pipe.id === newPipe.id) pipe = Object.assign(pipe, newPipe); + }); + }; + } + + updatePipelines(apiResponse) { + const update = this.pipelines.map(e => e); + apiResponse.pipelines.forEach((newPipe) => { + if (Object.keys(newPipe).length <= 2) return; + if (Object.keys(newPipe).length > 3) { + update.unshift(newPipe); + } else { + this.updateClone(update, newPipe); + } + }); + this.pipelines = update; + return this.pipelines; + } + } + gl.PipelineStore = class { fetchDataLoop(Vue, pageNum, url) { const goFetch = () => this.$http.get(`${url}?page=${pageNum}`) .then((response) => { const res = JSON.parse(response.body); + Vue.set(this, 'updatedAt', res.updated_at); Vue.set(this, 'pipelines', res.pipelines); Vue.set(this, 'count', res.count); }, () => new Flash( 'Something went wrong on our end.' )); + const goUpdate = () => + this.$http.get(`${url}?page=${pageNum}&updated_at=${this.updatedAt}`) + .then((response) => { + const res = JSON.parse(response.body); + const p = new PipelineUpdater(this.pipelines); + Vue.set(this, 'pipelines', p.updatePipelines(res)); + }, () => new Flash( + 'Something went wrong on our end.' + )); + goFetch(); this.intervalId = setInterval(() => { - goFetch(); + goUpdate(); }, 3000); } }; From b0c6037a3af1724ffb18de7442495576e90af396 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 10 Nov 2016 21:16:54 +0100 Subject: [PATCH 077/277] Refine incremental pipeline serializer --- .../projects/pipelines_controller.rb | 8 ++- app/serializers/pipeline_entity.rb | 53 ++++++++++++------- app/serializers/pipeline_serializer.rb | 5 ++ spec/serializers/pipeline_serializer_spec.rb | 36 +++++++++++++ 4 files changed, 81 insertions(+), 21 deletions(-) create mode 100644 spec/serializers/pipeline_serializer_spec.rb diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 0b3503c6848..d50f05111ca 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -17,8 +17,12 @@ class Projects::PipelinesController < Projects::ApplicationController format.html format.json do render json: { - pipelines: PipelineSerializer.new(project: @project). - represent(@pipelines, current_user: current_user, last_updated: @last_updated), + pipelines: PipelineSerializer + .incremental( + project: @project, + user: @current_user, + last_updated: @last_updated) + .represent updated_at: Time.now, count: { all: @pipelines_count, diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 6191d63fd7f..234ec74c2cd 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -2,7 +2,8 @@ class PipelineEntity < Grape::Entity include RequestAwareEntity expose :id - expose :user, if: -> (pipeline, opts) { created?(pipeline, opts) }, using: UserEntity + expose :user, if: proc { created_exposure? }, using: UserEntity + expose :url do |pipeline| namespace_project_pipeline_path( pipeline.project.namespace, @@ -10,7 +11,7 @@ class PipelineEntity < Grape::Entity pipeline) end - expose :details, if: -> (pipeline, opts) { updated?(pipeline, opts) } do + expose :details, if: proc { updated_exposure? } do expose :status expose :duration expose :finished_at @@ -19,18 +20,20 @@ class PipelineEntity < Grape::Entity expose :manual_actions, using: PipelineActionEntity end - expose :flags, if: -> (pipeline, opts) { created?(pipeline, opts) } do + expose :flags, if: proc { created_exposure? } do expose :latest?, as: :latest expose :triggered?, as: :triggered + expose :yaml_errors?, as: :yaml_errors do |pipeline| pipeline.yaml_errors.present? end + expose :stuck?, as: :stuck do |pipeline| pipeline.builds.any?(&:stuck?) end end - expose :ref, if: -> (pipeline, opts) { created?(pipeline, opts) } do + expose :ref, if: proc { updated_exposure? } do expose :name do |pipeline| pipeline.ref end @@ -45,31 +48,43 @@ class PipelineEntity < Grape::Entity expose :tag? end - expose :commit, if: -> (pipeline, opts) { created?(pipeline, opts) }, using: CommitEntity + expose :commit, if: proc { created_exposure? }, using: CommitEntity - expose :retry_url, if: -> (pipeline, opts) { updated?(pipeline, opts) } do |pipeline| - can?(current_user, :update_pipeline, pipeline.project) && + expose :retry_url, if: proc { updated_exposure? } do |pipeline| + can?(request.user, :update_pipeline, pipeline.project) && pipeline.retryable? && - retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) + retry_namespace_project_pipeline_path(pipeline.project.namespace, + pipeline.project, pipeline.id) end - expose :cancel_url, if: -> (pipeline, opts) { updated?(pipeline, opts) } do |pipeline| - can?(current_user, :update_pipeline, pipeline.project) && + expose :cancel_url, if: proc { updated_exposure? } do |pipeline| + can?(request.user, :update_pipeline, pipeline.project) && pipeline.cancelable? && - cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) + cancel_namespace_project_pipeline_path(pipeline.project.namespace, + pipeline.project, pipeline.id) end - private - - def last_updated(opts) - opts.fetch(:last_updated) + def created_exposure? + !incremental? || created? end - def created?(pipeline, opts) - !last_updated(opts) || pipeline.created_at > last_updated(opts) + def updated_exposure? + !incremental? || updated? end - def updated?(pipeline, opts) - !last_updated(opts) || pipeline.updated_at > last_updated(opts) + def incremental? + options[:incremental] + end + + def last_updated + options.fetch(:last_updated) + end + + def updated? + @object.updated_at > last_updated + end + + def created? + @object.created_at > last_updated end end diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index f7abbec7d45..8d3182d9766 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -1,3 +1,8 @@ class PipelineSerializer < BaseSerializer entity PipelineEntity + + def incremental(resource, last_updated) + represent(resource, incremental: true, + last_updated: last_updated) + end end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb new file mode 100644 index 00000000000..c4eba2f2537 --- /dev/null +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe PipelineSerializer do + let(:serializer) do + described_class.new(user: user) + end + + let(:pipelines) do + create_list(:ci_pipeline, 2) + end + + let(:user) { create(:user) } + + context 'when using incremental serializer' do + let(:json) do + serializer.incremental(pipelines, time).as_json + end + + context 'when pipeline has been already updated' do + let(:time) { Time.now } + + it 'exposes only minimal information' do + expect(json.first.keys).to contain_exactly(:id, :url) + expect(json.second.keys).to contain_exactly(:id, :url) + end + end + + context 'when pipeline updated in the meantime' do + let(:time) { Time.now - 10.minutes } + + it 'exposes new data incrementally' do + expect(json.first.keys.count).to eq 9 + end + end + end +end From 6b52adc661896434d3fea1fd7f83c62bef2456a6 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 10 Nov 2016 21:16:54 +0100 Subject: [PATCH 078/277] Refine incremental pipeline serializer --- .../projects/pipelines_controller.rb | 5 +- app/serializers/pipeline_entity.rb | 53 ++++++++++++------- app/serializers/pipeline_serializer.rb | 5 ++ spec/serializers/pipeline_serializer_spec.rb | 36 +++++++++++++ 4 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 spec/serializers/pipeline_serializer_spec.rb diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 0b3503c6848..62cef8d4a4d 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -17,8 +17,9 @@ class Projects::PipelinesController < Projects::ApplicationController format.html format.json do render json: { - pipelines: PipelineSerializer.new(project: @project). - represent(@pipelines, current_user: current_user, last_updated: @last_updated), + pipelines: PipelineSerializer + .new(project: @project, user: @current_user) + .incremental(@pipelines, @last_updated), updated_at: Time.now, count: { all: @pipelines_count, diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 6191d63fd7f..234ec74c2cd 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -2,7 +2,8 @@ class PipelineEntity < Grape::Entity include RequestAwareEntity expose :id - expose :user, if: -> (pipeline, opts) { created?(pipeline, opts) }, using: UserEntity + expose :user, if: proc { created_exposure? }, using: UserEntity + expose :url do |pipeline| namespace_project_pipeline_path( pipeline.project.namespace, @@ -10,7 +11,7 @@ class PipelineEntity < Grape::Entity pipeline) end - expose :details, if: -> (pipeline, opts) { updated?(pipeline, opts) } do + expose :details, if: proc { updated_exposure? } do expose :status expose :duration expose :finished_at @@ -19,18 +20,20 @@ class PipelineEntity < Grape::Entity expose :manual_actions, using: PipelineActionEntity end - expose :flags, if: -> (pipeline, opts) { created?(pipeline, opts) } do + expose :flags, if: proc { created_exposure? } do expose :latest?, as: :latest expose :triggered?, as: :triggered + expose :yaml_errors?, as: :yaml_errors do |pipeline| pipeline.yaml_errors.present? end + expose :stuck?, as: :stuck do |pipeline| pipeline.builds.any?(&:stuck?) end end - expose :ref, if: -> (pipeline, opts) { created?(pipeline, opts) } do + expose :ref, if: proc { updated_exposure? } do expose :name do |pipeline| pipeline.ref end @@ -45,31 +48,43 @@ class PipelineEntity < Grape::Entity expose :tag? end - expose :commit, if: -> (pipeline, opts) { created?(pipeline, opts) }, using: CommitEntity + expose :commit, if: proc { created_exposure? }, using: CommitEntity - expose :retry_url, if: -> (pipeline, opts) { updated?(pipeline, opts) } do |pipeline| - can?(current_user, :update_pipeline, pipeline.project) && + expose :retry_url, if: proc { updated_exposure? } do |pipeline| + can?(request.user, :update_pipeline, pipeline.project) && pipeline.retryable? && - retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) + retry_namespace_project_pipeline_path(pipeline.project.namespace, + pipeline.project, pipeline.id) end - expose :cancel_url, if: -> (pipeline, opts) { updated?(pipeline, opts) } do |pipeline| - can?(current_user, :update_pipeline, pipeline.project) && + expose :cancel_url, if: proc { updated_exposure? } do |pipeline| + can?(request.user, :update_pipeline, pipeline.project) && pipeline.cancelable? && - cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) + cancel_namespace_project_pipeline_path(pipeline.project.namespace, + pipeline.project, pipeline.id) end - private - - def last_updated(opts) - opts.fetch(:last_updated) + def created_exposure? + !incremental? || created? end - def created?(pipeline, opts) - !last_updated(opts) || pipeline.created_at > last_updated(opts) + def updated_exposure? + !incremental? || updated? end - def updated?(pipeline, opts) - !last_updated(opts) || pipeline.updated_at > last_updated(opts) + def incremental? + options[:incremental] + end + + def last_updated + options.fetch(:last_updated) + end + + def updated? + @object.updated_at > last_updated + end + + def created? + @object.created_at > last_updated end end diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index f7abbec7d45..8d3182d9766 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -1,3 +1,8 @@ class PipelineSerializer < BaseSerializer entity PipelineEntity + + def incremental(resource, last_updated) + represent(resource, incremental: true, + last_updated: last_updated) + end end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb new file mode 100644 index 00000000000..c4eba2f2537 --- /dev/null +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe PipelineSerializer do + let(:serializer) do + described_class.new(user: user) + end + + let(:pipelines) do + create_list(:ci_pipeline, 2) + end + + let(:user) { create(:user) } + + context 'when using incremental serializer' do + let(:json) do + serializer.incremental(pipelines, time).as_json + end + + context 'when pipeline has been already updated' do + let(:time) { Time.now } + + it 'exposes only minimal information' do + expect(json.first.keys).to contain_exactly(:id, :url) + expect(json.second.keys).to contain_exactly(:id, :url) + end + end + + context 'when pipeline updated in the meantime' do + let(:time) { Time.now - 10.minutes } + + it 'exposes new data incrementally' do + expect(json.first.keys.count).to eq 9 + end + end + end +end From ac6b988e86eaf1e21af4a850e7db034b52063dc4 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 10 Nov 2016 13:48:23 -0700 Subject: [PATCH 079/277] fix conflict --- app/controllers/projects/pipelines_controller.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 9c5bb92ee42..62cef8d4a4d 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -18,16 +18,8 @@ class Projects::PipelinesController < Projects::ApplicationController format.json do render json: { pipelines: PipelineSerializer -<<<<<<< HEAD - .incremental( - project: @project, - user: @current_user, - last_updated: @last_updated) - .represent -======= .new(project: @project, user: @current_user) .incremental(@pipelines, @last_updated), ->>>>>>> 6b52adc661896434d3fea1fd7f83c62bef2456a6 updated_at: Time.now, count: { all: @pipelines_count, From 5067d034571dae51e4143dc365472828f54537bb Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 10 Nov 2016 21:57:15 +0100 Subject: [PATCH 080/277] Make updated_at optional in pipelines serializer --- app/serializers/pipeline_entity.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 234ec74c2cd..24fe1602c2d 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -73,7 +73,7 @@ class PipelineEntity < Grape::Entity end def incremental? - options[:incremental] + options[:incremental] && last_updated end def last_updated From 84764238e59060932774d1ca1814366f74d03380 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 10 Nov 2016 15:15:08 -0700 Subject: [PATCH 081/277] comments about what is needed for commits --- .../javascripts/vue_pipelines_index/branch_commit.js.es6 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 index d0459f2ae6b..c46b571d409 100644 --- a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 @@ -17,7 +17,12 @@
      -
      {{pipeline.ref.name}} + +
      @@ -33,7 +38,7 @@ - + Date: Thu, 10 Nov 2016 15:16:31 -0700 Subject: [PATCH 082/277] added comments about need values for time_ago in Vue --- app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index f9318130356..2fbfa486f88 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -22,6 +22,7 @@ }, computed: { finishdate() { + // need started_at or created_at here const date = new Date( new Date( this.pipeline.details.finished_at @@ -34,6 +35,7 @@ ); }, runningdate() { + // need started_at or created_at at here const date = new Date( new Date().getTime() - new Date(this.pipeline.started_at).getTime() ); From 4d45ef5d9578a8c69f14de029af303149fdd8d83 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 10 Nov 2016 19:33:26 -0700 Subject: [PATCH 083/277] refactor object merge-add --- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index abe8a4d0a52..7d775bba0d3 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -15,8 +15,7 @@ updatePipelines(apiResponse) { const update = this.pipelines.map(e => e); apiResponse.pipelines.forEach((newPipe) => { - if (Object.keys(newPipe).length <= 2) return; - if (Object.keys(newPipe).length > 3) { + if (newPipe.commit) { update.unshift(newPipe); } else { this.updateClone(update, newPipe); From 86be72ae7aa0f5a90b88f368ea13cefc3469302f Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 10 Nov 2016 20:08:55 -0700 Subject: [PATCH 084/277] add artifact and action functionality - branch name back in --- .../vue_pipelines_index/branch_commit.js.es6 | 1 + .../pipeline_actions.js.es6 | 51 +++++++++++-------- .../vue_pipelines_index/pipelines.js.es6 | 36 ++++++------- .../vue_pipelines_index/store.js.es6 | 1 + 4 files changed, 51 insertions(+), 38 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 index c46b571d409..e1f404befa1 100644 --- a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 @@ -21,6 +21,7 @@ {{pipeline.ref.name}}
      diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index a6ff2f10ccf..2ea2ef216d1 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -3,7 +3,14 @@ ((gl) => { gl.VuePipelineActions = Vue.extend({ - // props: ['builds'], + props: [ + 'pipeline', + ], + methods: { + download(name) { + return `Download ${name} artifacts`; + }, + }, template: `
      diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 7b0e9b41be0..7dcc319390e 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -4,15 +4,15 @@ ((gl) => { gl.VuePipeLines = Vue.extend({ components: { - 'vue-running-pipeline': gl.VueRunningPipeline, - 'vue-stages': gl.VueStages, - 'vue-pipeline-actions': gl.VuePipelineActions, - 'vue-branch-commit': gl.VueBranchCommit, - 'vue-pipeline-url': gl.VuePipelineUrl, - 'vue-pipeline-head': gl.VuePipelineHead, - 'vue-gl-pagination': gl.VueGlPagination, - 'vue-status-scope': gl.VueStatusScope, - 'vue-time-ago': gl.VueTimeAgo, + 'running-pipeline': gl.VueRunningPipeline, + 'stages': gl.VueStages, + 'pipeline-actions': gl.VuePipelineActions, + 'branch-commit': gl.VueBranchCommit, + 'pipeline-url': gl.VuePipelineUrl, + 'pipeline-head': gl.VuePipelineHead, + 'gl-pagination': gl.VueGlPagination, + 'status-scope': gl.VueStatusScope, + 'time-ago': gl.VueTimeAgo, }, data() { return { @@ -54,26 +54,26 @@
      - + - - - - - - + + + + + +
      - - +
      `, }); diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 7d775bba0d3..5433c93d407 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -32,6 +32,7 @@ this.$http.get(`${url}?page=${pageNum}`) .then((response) => { const res = JSON.parse(response.body); + debugger Vue.set(this, 'updatedAt', res.updated_at); Vue.set(this, 'pipelines', res.pipelines); Vue.set(this, 'count', res.count); From aa6fa88baed41736611360ec6a7c4d9abab61935 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 10 Nov 2016 20:37:31 -0700 Subject: [PATCH 085/277] api call for retry almost done --- .../vue_pipelines_index/pipeline_actions.js.es6 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index 2ea2ef216d1..0b469e8ffb6 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -1,4 +1,4 @@ -/* global Vue, gl */ +/* global Vue, Flash, gl */ /* eslint-disable no-param-reassign */ ((gl) => { @@ -10,6 +10,14 @@ download(name) { return `Download ${name} artifacts`; }, + // retry(e) { + // e.preventDefault(); + // this.$http.post(this.pipeline.retry_url, { + // pipeline: { id: this.pipeline.id }, + // }) + // .then(() => {}) + // .catch(() => new Flash('Something went wrong on our end.')); + // }, }, template: ` @@ -60,6 +68,7 @@
    + Date: Thu, 10 Nov 2016 21:18:33 -0700 Subject: [PATCH 086/277] conditional action buttons - spans for pipeline tags --- .../vue_pipelines_index/pipeline_actions.js.es6 | 14 ++++++++++++-- .../vue_pipelines_index/pipeline_url.js.es6 | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index 0b469e8ffb6..583069aea2a 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -24,7 +24,12 @@
    - + diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 index d0cbf99a344..9ebbe3ccf9e 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 @@ -22,6 +22,22 @@ by {{user}} + + latest + + + yaml invalid + `, }); From df1b15e327877066364271384975a8307d5f6c22 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 10 Nov 2016 22:21:09 -0700 Subject: [PATCH 087/277] loading icon - rename component keys --- .../vue_pipelines_index/pipelines.js.es6 | 28 ++++++++++++------- .../vue_pipelines_index/store.js.es6 | 2 +- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 7dcc319390e..421e4111187 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -4,15 +4,15 @@ ((gl) => { gl.VuePipeLines = Vue.extend({ components: { - 'running-pipeline': gl.VueRunningPipeline, - 'stages': gl.VueStages, - 'pipeline-actions': gl.VuePipelineActions, - 'branch-commit': gl.VueBranchCommit, - 'pipeline-url': gl.VuePipelineUrl, - 'pipeline-head': gl.VuePipelineHead, - 'gl-pagination': gl.VueGlPagination, - 'status-scope': gl.VueStatusScope, - 'time-ago': gl.VueTimeAgo, + runningPipeline: gl.VueRunningPipeline, + pipelineActions: gl.VuePipelineActions, + stages: gl.VueStages, + branchCommit: gl.VueBranchCommit, + pipelineUrl: gl.VuePipelineUrl, + pipelineHead: gl.VuePipelineHead, + glPagination: gl.VueGlPagination, + statusScope: gl.VueStatusScope, + timeAgo: gl.VueTimeAgo, }, data() { return { @@ -24,6 +24,7 @@ all: 0, running_or_pending: 0, }, + pageRequest: false, }; }, props: [ @@ -47,12 +48,16 @@ window.history.pushState({}, null, `?p=${this.pagenum}`); clearInterval(this.intervalId); + this.pageRequest = true; this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope); }, }, template: `
    -
    +
    + +
    +
    @@ -67,6 +72,9 @@
    +
    + +
    { const res = JSON.parse(response.body); - debugger Vue.set(this, 'updatedAt', res.updated_at); Vue.set(this, 'pipelines', res.pipelines); Vue.set(this, 'count', res.count); + this.pageRequest = false; }, () => new Flash( 'Something went wrong on our end.' )); From 31e34715bf62993a42ca7636edf1408891b0595b Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 11 Nov 2016 12:05:07 -0700 Subject: [PATCH 088/277] scoped polyfill for now --- .../vue_pipelines_index/index.js.es6 | 1 + .../vue_pipelines_index/object_assign.js.es6 | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 app/assets/javascripts/vue_pipelines_index/object_assign.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 3a7842f305b..ab53b12d3d0 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -3,6 +3,7 @@ //= require vue-resource +//= require ./object_assign.js.es6 //= require ./store.js.es6 //= require ./pipeline_url.js.es6 //= require ./pipeline_head.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/object_assign.js.es6 b/app/assets/javascripts/vue_pipelines_index/object_assign.js.es6 new file mode 100644 index 00000000000..05a5a7da1c3 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/object_assign.js.es6 @@ -0,0 +1,30 @@ +/* eslint-disable */ + +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ +(() => { + if (typeof Object.assign != 'function') { + (function () { + Object.assign = function (target) { + 'use strict'; + // We must check against these specific cases. + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + var output = Object(target); + for (var index = 1; index < arguments.length; index++) { + var source = arguments[index]; + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output; + }; + })(); + } +})(); From b64ee664a69eaa0474a8befeb2379e948eb647cb Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 11 Nov 2016 12:25:10 -0700 Subject: [PATCH 089/277] add stuck icon --- .../javascripts/vue_pipelines_index/pipeline_url.js.es6 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 index 9ebbe3ccf9e..efeef974960 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 @@ -38,6 +38,12 @@ > yaml invalid + + stuck + `, }); From af4459e5314b8d7d054995e6cf244e91feacf78a Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 11 Nov 2016 12:35:57 -0700 Subject: [PATCH 090/277] update stage icons with created --- app/assets/javascripts/vue_icons/index.js.es6 | 9 ++++++++ .../vue_pipelines_index/stage.js.es6 | 7 +++++- .../vue_pipelines_status/created.js.es6 | 23 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/vue_pipelines_status/created.js.es6 diff --git a/app/assets/javascripts/vue_icons/index.js.es6 b/app/assets/javascripts/vue_icons/index.js.es6 index 413f9b45393..66b4a58a4c0 100644 --- a/app/assets/javascripts/vue_icons/index.js.es6 +++ b/app/assets/javascripts/vue_icons/index.js.es6 @@ -46,4 +46,13 @@ `, }); + + gl.VueCreatedIcon = Vue.extend({ + template: ` + + + + + `, + }); })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index f983226a4ce..8a77f213b7f 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -8,16 +8,20 @@ 'pending-icon': gl.VuePendingIcon, 'failed-icon': gl.VueFailedIcon, 'success-icon': gl.VueSuccessIcon, + 'created-icon': gl.VueCreatedIcon, }, props: ['stage'], computed: { buildStatus() { return `Build: ${this.stage.status}`; }, + tooltip() { + return `has-tooltip ci-status-icon-${this.stage.status}`; + }, }, template: ` @@ -25,6 +29,7 @@ + `, }); diff --git a/app/assets/javascripts/vue_pipelines_status/created.js.es6 b/app/assets/javascripts/vue_pipelines_status/created.js.es6 new file mode 100644 index 00000000000..b74566b7341 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_status/created.js.es6 @@ -0,0 +1,23 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueCreatedScope = Vue.extend({ + components: { + 'vue-created-icon': gl.VueCreatedIcon, + }, + props: [ + 'pipeline', + ], + template: ` + + + + +  failed + + + + `, + }); +})(window.gl || (window.gl = {})); From e5af16a3e097a6e7ea79c3120b2cc95c64b769ac Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 11 Nov 2016 12:37:24 -0700 Subject: [PATCH 091/277] add created to pipeline scope status --- app/assets/javascripts/vue_pipelines_status/status.js.es6 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/javascripts/vue_pipelines_status/status.js.es6 b/app/assets/javascripts/vue_pipelines_status/status.js.es6 index 3f8cb1164ec..394c6349b91 100644 --- a/app/assets/javascripts/vue_pipelines_status/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/status.js.es6 @@ -7,6 +7,7 @@ 'vue-running-scope': gl.VueRunningScope, 'vue-pending-scope': gl.VuePendingScope, 'vue-failed-scope': gl.VueFailedScope, + 'vue-created-scope': gl.VueCreatedScope, }, props: [ 'pipeline', @@ -28,6 +29,11 @@ :pipeline='pipeline' > + + `, }); From 35c8e533c05a68feacc52cf8b8e173ef6290c641 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 11 Nov 2016 12:40:29 -0700 Subject: [PATCH 092/277] created component instead of failed --- app/assets/javascripts/vue_pipelines_status/status.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_status/status.js.es6 b/app/assets/javascripts/vue_pipelines_status/status.js.es6 index 394c6349b91..0a722ba7ca5 100644 --- a/app/assets/javascripts/vue_pipelines_status/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/status.js.es6 @@ -29,11 +29,11 @@ :pipeline='pipeline' > - - + `, }); From 04a4520c2d389bae4358c9429aa22cf3a53ba9a2 Mon Sep 17 00:00:00 2001 From: Regis Date: Sat, 12 Nov 2016 01:57:23 -0700 Subject: [PATCH 093/277] fix cancel url - clearInterval on refresh and page close - fix branch url --- .../javascripts/vue_pipelines_index/branch_commit.js.es6 | 2 +- .../javascripts/vue_pipelines_index/pipeline_actions.js.es6 | 4 ++-- app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 | 4 ++-- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 6 +++++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 index e1f404befa1..43bad6e36e7 100644 --- a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 @@ -20,7 +20,7 @@ {{pipeline.ref.name}} diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index 583069aea2a..c86b49c15fa 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -80,7 +80,7 @@
    diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 421e4111187..7872e7d4ac6 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -37,8 +37,8 @@ this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope); }, methods: { - changepage(event, last) { - const text = event.target.innerText; + changepage(e, last) { + const text = e.target.innerText; if (text === '...') return; if (/^-?[\d.]+(?:e-?\d+)?$/.test(text)) this.pagenum = +text; if (text === 'Last >>') this.pagenum = last; diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index d00e30760a8..861df939451 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -53,8 +53,12 @@ goFetch(); this.intervalId = setInterval(() => { - goUpdate(); + if (this.updatedAt) goUpdate(); }, 3000); + + window.onbeforeunload = function removePipelineInterval() { + clearInterval(this.intervalId); + }; } }; })(window.gl || (window.gl = {})); From 7748282996d7754a342b5956b1a277a54a9b26f5 Mon Sep 17 00:00:00 2001 From: Regis Date: Sat, 12 Nov 2016 02:00:35 -0700 Subject: [PATCH 094/277] better comment [ci skip] --- app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index 2fbfa486f88..159c7585580 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -21,8 +21,8 @@ }, }, computed: { + // need started_at or created_at for finish and running finishdate() { - // need started_at or created_at here const date = new Date( new Date( this.pipeline.details.finished_at @@ -35,7 +35,6 @@ ); }, runningdate() { - // need started_at or created_at at here const date = new Date( new Date().getTime() - new Date(this.pipeline.started_at).getTime() ); From 6186ac2c3d0db6932993ac681cbed6f453499503 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 12 Nov 2016 10:05:57 +0100 Subject: [PATCH 095/277] Expose created_at and updated_at in pipeline entity --- app/serializers/pipeline_entity.rb | 2 ++ app/serializers/request_aware_entity.rb | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 24fe1602c2d..08346b4ed77 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -64,6 +64,8 @@ class PipelineEntity < Grape::Entity pipeline.project, pipeline.id) end + expose :created_at, :updated_at + def created_exposure? !incremental? || created? end diff --git a/app/serializers/request_aware_entity.rb b/app/serializers/request_aware_entity.rb index 7a096d9d5a8..e159d750cb7 100644 --- a/app/serializers/request_aware_entity.rb +++ b/app/serializers/request_aware_entity.rb @@ -9,10 +9,6 @@ module RequestAwareEntity @options.fetch(:request) end - def current_user - @options.fetch(:current_user) - end - def can?(object, action, subject) Ability.allowed?(object, action, subject) end From 0541bffa85cde86d4ce142b6fe2d3e1357b1acc5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 12 Nov 2016 10:53:03 +0100 Subject: [PATCH 096/277] Expose commit author's gravatar if not gitlab user --- app/serializers/commit_entity.rb | 4 ++++ spec/serializers/commit_entity_spec.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb index f7eba6fc1e3..121ca76a16f 100644 --- a/app/serializers/commit_entity.rb +++ b/app/serializers/commit_entity.rb @@ -3,6 +3,10 @@ class CommitEntity < API::Entities::RepoCommit expose :author, using: UserEntity + expose :author_gravatar_url do |commit| + GravatarService.new.execute(commit.author_email) + end + expose :commit_url do |commit| namespace_project_tree_url( request.project.namespace, diff --git a/spec/serializers/commit_entity_spec.rb b/spec/serializers/commit_entity_spec.rb index 628e35c9a28..5b2a6c6c294 100644 --- a/spec/serializers/commit_entity_spec.rb +++ b/spec/serializers/commit_entity_spec.rb @@ -41,4 +41,8 @@ describe CommitEntity do subject end + + it 'exposes gravatar url that belongs to author' do + expect(subject.fetch(:author_gravatar_url)).to match /gravatar/ + end end From a3c93ee8ad2caae0d7ace8c4a0c796840209f20e Mon Sep 17 00:00:00 2001 From: Regis Date: Sat, 12 Nov 2016 03:21:00 -0700 Subject: [PATCH 097/277] waiting for api changes --- .../vue_pipelines_index/branch_commit.js.es6 | 8 ++++++-- .../vue_pipelines_index/time_ago.js.es6 | 18 ++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 index 43bad6e36e7..b594d08e96d 100644 --- a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 @@ -11,13 +11,17 @@ alt() { return `${this.pipeline.commit.author_name}'s avatar`; }, + avatarUrl() { + const author = this.pipeline.commit.author; + if (author) return author.avatar_url; + return this.pipeline.commit.author_gravatar_url; + }, }, template: `
    -
    - {{duration}} + {{duration()}}

    -

    +

    From 4f290de7c14131f9241834136ae4f0b7de0aa394 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 14 Nov 2016 09:58:20 -0700 Subject: [PATCH 098/277] add cancelled icon --- app/assets/javascripts/vue_icons/index.js.es6 | 11 +++++++++ .../vue_pipelines_index/stage.js.es6 | 2 ++ .../vue_pipelines_status/canceled.js.es6 | 23 +++++++++++++++++++ .../vue_pipelines_status/index.js.es6 | 1 + .../vue_pipelines_status/status.js.es6 | 6 +++++ 5 files changed, 43 insertions(+) create mode 100644 app/assets/javascripts/vue_pipelines_status/canceled.js.es6 diff --git a/app/assets/javascripts/vue_icons/index.js.es6 b/app/assets/javascripts/vue_icons/index.js.es6 index 66b4a58a4c0..b7da80a6377 100644 --- a/app/assets/javascripts/vue_icons/index.js.es6 +++ b/app/assets/javascripts/vue_icons/index.js.es6 @@ -55,4 +55,15 @@ `, }); + + gl.VueCanceledIcon = Vue.extend({ + template: ` + + + + + + + `, + }); })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 8a77f213b7f..e5f9178a796 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -9,6 +9,7 @@ 'failed-icon': gl.VueFailedIcon, 'success-icon': gl.VueSuccessIcon, 'created-icon': gl.VueCreatedIcon, + 'canceled-icon': gl.VueCanceledIcon, }, props: ['stage'], computed: { @@ -30,6 +31,7 @@ +
    `, }); diff --git a/app/assets/javascripts/vue_pipelines_status/canceled.js.es6 b/app/assets/javascripts/vue_pipelines_status/canceled.js.es6 new file mode 100644 index 00000000000..ed677fea93c --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_status/canceled.js.es6 @@ -0,0 +1,23 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueCanceledScope = Vue.extend({ + components: { + 'vue-canceled-icon': gl.VueCanceledIcon, + }, + props: [ + 'pipeline', + ], + template: ` + + + + +  cancelled + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/index.js.es6 b/app/assets/javascripts/vue_pipelines_status/index.js.es6 index cea02b5d6af..ab2755eb323 100644 --- a/app/assets/javascripts/vue_pipelines_status/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/index.js.es6 @@ -1,4 +1,5 @@ //= require ./pending.js.es6 //= require ./failed.js.es6 //= require ./running.js.es6 +//= require ./canceled.js.es6 //= require ./status.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_status/status.js.es6 b/app/assets/javascripts/vue_pipelines_status/status.js.es6 index 0a722ba7ca5..8732337c3ef 100644 --- a/app/assets/javascripts/vue_pipelines_status/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/status.js.es6 @@ -8,6 +8,7 @@ 'vue-pending-scope': gl.VuePendingScope, 'vue-failed-scope': gl.VueFailedScope, 'vue-created-scope': gl.VueCreatedScope, + 'vue-canceled-scope': gl.VueCanceledScope, }, props: [ 'pipeline', @@ -34,6 +35,11 @@ :pipeline='pipeline' > + + `, }); From 2d1effb11e74883ed16e3577fcdfc05d2692c12e Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 14 Nov 2016 10:08:14 -0700 Subject: [PATCH 099/277] change class name for css to take effect --- app/assets/javascripts/vue_pipelines_status/canceled.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_status/canceled.js.es6 b/app/assets/javascripts/vue_pipelines_status/canceled.js.es6 index ed677fea93c..20ca8ad5ebb 100644 --- a/app/assets/javascripts/vue_pipelines_status/canceled.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/canceled.js.es6 @@ -12,9 +12,9 @@ template: ` - + -  cancelled +  canceled From 40a503650e319cefb285c63f8cbfc39f3422a837 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 14 Nov 2016 12:43:08 -0700 Subject: [PATCH 100/277] more progress --- app/assets/javascripts/vue_icons/index.js.es6 | 21 +++++++++++++++++ .../pipeline_actions.js.es6 | 2 +- .../vue_pipelines_index/time_ago.js.es6 | 1 - .../vue_pipelines_status/index.js.es6 | 2 ++ .../vue_pipelines_status/skipped.js.es6 | 23 +++++++++++++++++++ .../vue_pipelines_status/status.js.es6 | 6 +++++ .../vue_pipelines_status/unstable.js.es6 | 23 +++++++++++++++++++ 7 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/vue_pipelines_status/skipped.js.es6 create mode 100644 app/assets/javascripts/vue_pipelines_status/unstable.js.es6 diff --git a/app/assets/javascripts/vue_icons/index.js.es6 b/app/assets/javascripts/vue_icons/index.js.es6 index b7da80a6377..84a19348862 100644 --- a/app/assets/javascripts/vue_icons/index.js.es6 +++ b/app/assets/javascripts/vue_icons/index.js.es6 @@ -66,4 +66,25 @@ `, }); + + gl.VueSkippedIcon = Vue.extend({ + template: ` + + + + + + `, + }); + + gl.VueUnstableIcon = Vue.extend({ + template: ` + + + + + + { + gl.VueSkippedScope = Vue.extend({ + components: { + 'vue-skipped-icon': gl.VueSkippedIcon, + }, + props: [ + 'pipeline', + ], + template: ` + + + + +  skipped + + + + `, + }); +})(window.gl || (window.gl = {})); \ No newline at end of file diff --git a/app/assets/javascripts/vue_pipelines_status/status.js.es6 b/app/assets/javascripts/vue_pipelines_status/status.js.es6 index 8732337c3ef..dfef19e21da 100644 --- a/app/assets/javascripts/vue_pipelines_status/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/status.js.es6 @@ -9,6 +9,7 @@ 'vue-failed-scope': gl.VueFailedScope, 'vue-created-scope': gl.VueCreatedScope, 'vue-canceled-scope': gl.VueCanceledScope, + 'vue-unstable-scope': gl.VueUnstableScope, }, props: [ 'pipeline', @@ -40,6 +41,11 @@ :pipeline='pipeline' > + + `, }); diff --git a/app/assets/javascripts/vue_pipelines_status/unstable.js.es6 b/app/assets/javascripts/vue_pipelines_status/unstable.js.es6 new file mode 100644 index 00000000000..27fd29b9d80 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_status/unstable.js.es6 @@ -0,0 +1,23 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueUnstableScope = Vue.extend({ + components: { + 'vue-unstable-icon': gl.VueUnstableIcon, + }, + props: [ + 'pipeline', + ], + template: ` + + + + +  unstable + + + + `, + }); +})(window.gl || (window.gl = {})); From fa1a99a17388c3880e84385255566d776960cf4c Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 14 Nov 2016 12:44:50 -0700 Subject: [PATCH 101/277] rid of extra api calls --- .../vue_pipelines_index/pipeline_actions.js.es6 | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index d1519ec83f7..223fcce62d8 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -10,14 +10,6 @@ download(name) { return `Download ${name} artifacts`; }, - // retry(e) { - // e.preventDefault(); - // this.$http.post(this.pipeline.retry_url, { - // pipeline: { id: this.pipeline.id }, - // }) - // .then(() => {}) - // .catch(() => new Flash('Something went wrong on our end.')); - // }, }, template: ` @@ -78,7 +70,6 @@
    - Date: Tue, 15 Nov 2016 15:20:37 +0100 Subject: [PATCH 102/277] Fix broken pipeline rendering [ci skip] --- app/controllers/projects/pipelines_controller.rb | 2 +- app/models/ci/pipeline.rb | 10 ++++++---- app/views/projects/ci/pipelines/_pipeline.html.haml | 13 +++++-------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 2d8641871da..3d095e2b690 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -20,7 +20,7 @@ class Projects::PipelinesController < Projects::ApplicationController pipelines: PipelineSerializer .new(project: @project, user: @current_user) .incremental(@pipelines, @last_updated), - updated_at: Time.now, + updated_at: Time.now.utc, count: { all: @pipelines_count, running_or_pending: @running_or_pending_count diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 37c2c86e3a3..db573fb6562 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -21,8 +21,6 @@ module Ci after_create :keep_around_commits, unless: :importing? - delegate :stages, to: :statuses - state_machine :status, initial: :created do event :enqueue do transition created: :pending @@ -102,15 +100,19 @@ module Ci where.not(duration: nil).sum(:duration) end - def stages + def stages_query statuses.group('stage').select(:stage) .order('max(stage_idx)') end + def stages + self.stages_query.pluck(:stage) + end + def stages_with_statuses status_sql = statuses.latest.where('stage=sg.stage').status_sql - stages_with_statuses = CommitStatus.from(self.stages, :sg). + stages_with_statuses = CommitStatus.from(self.stages_query, :sg). pluck('sg.stage', status_sql) stages_with_statuses.map do |stage| diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 50817f28d78..41189724f65 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -41,16 +41,13 @@ - else Cant find HEAD commit for this branch - - stages_status = pipeline.statuses.latest.stages_status %td.stage-cell - - pipeline.statuses.latest.stages_status.each do |stage| - - name = stage.first - - status = stage.last - - tooltip = "#{name.titleize}: #{status || 'not found'}" - - if status + - pipeline.stages_with_statuses.each do |stage| + - if stage.status + - tooltip = "#{stage.name.titleize}: #{stage.status || 'not found'}" .stage-container - = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do - = ci_icon_for_status(status) + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage.name), class: "has-tooltip ci-status-icon-#{stage.status}", title: tooltip do + = ci_icon_for_status(stage.status) %td - if pipeline.duration From 2b9a9e37dcb08af85f57508808243deec21eaa2f Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 15 Nov 2016 07:53:46 -0700 Subject: [PATCH 103/277] prep for testing - update all pipeline api response attributes --- .../vue_pipelines_index/index.js.es6 | 2 +- .../pipeline_actions.js.es6 | 4 ++-- .../vue_pipelines_index/store.js.es6 | 19 ++++++++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index ab53b12d3d0..90c8b47062c 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -1,5 +1,5 @@ /* global Vue, VueResource, gl */ -/* eslint-disable no-bitwise*/ +/* eslint-disable no-bitwise, no-plusplus*/ //= require vue-resource diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index 223fcce62d8..1e81862ec83 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -33,7 +33,7 @@ diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index d8058386898..c1c153b0604 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -35,7 +35,7 @@ gl.PipelineStore = class { fetchDataLoop(Vue, pageNum, url) { Vue.activeResources = 0; - const updateNumberOfPipelines = (total, running) => { + const updatePipelineNums = (total, running) => { document.querySelector('.js-totalbuilds-count').innerHTML = total; document.querySelector('.js-running-count').innerHTML = running; }; @@ -55,7 +55,7 @@ Vue.set(this, 'updatedAt', res.updated_at); Vue.set(this, 'pipelines', res.pipelines); Vue.set(this, 'count', res.count); - updateNumberOfPipelines(this.count.all, this.count.running_or_pending); + updatePipelineNums(this.count.all, this.count.running_or_pending); this.pageRequest = false; Vue.activeResources -= 1; }, () => new Flash( @@ -70,7 +70,7 @@ Vue.set(this, 'updatedAt', res.updated_at); Vue.set(this, 'pipelines', p.updatePipelines(res)); Vue.set(this, 'count', res.count); - updateNumberOfPipelines(this.count.all, this.count.running_or_pending); + updatePipelineNums(this.count.all, this.count.running_or_pending); Vue.activeResources -= 1; }, () => new Flash( 'Something went wrong on our end.' diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines_spec.rb index e0c8aedb230..b16cc36fd93 100644 --- a/spec/features/projects/pipelines_spec.rb +++ b/spec/features/projects/pipelines_spec.rb @@ -16,13 +16,25 @@ describe "Pipelines", feature: true, js: true do describe 'GET /:project/pipelines', feature: true, js: true do include WaitForVueResource - let!(:pipeline) { create(:ci_empty_pipeline, project: project, ref: 'master', status: 'running') } + let!(:pipeline) do + create( + :ci_empty_pipeline, + project: project, + ref: 'master', + status: 'running' + ) + end [:pipelines].each do |scope| context "displaying #{scope}" do let(:project) { create(:project) } - before { visit namespace_project_pipelines_path(project.namespace, project, scope: scope) } + before do + visit namespace_project_pipelines_path( + project.namespace, project, + scope: scope + ) + end it do wait_for_vue_resource @@ -35,20 +47,29 @@ describe "Pipelines", feature: true, js: true do context "displaying #{scope}" do let(:project) { create(:project) } - before { visit namespace_project_pipelines_path(project.namespace, project, scope: scope) } + before do + visit namespace_project_pipelines_path( + project.namespace, + project, scope: scope + ) + end it { expect(page).to have_content(pipeline.short_sha) } end end context 'anonymous access' do - before { visit namespace_project_pipelines_path(project.namespace, project) } + before do + visit namespace_project_pipelines_path(project.namespace, project) + end it { expect(page).to have_http_status(:success) } end context 'cancelable pipeline' do - let!(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') } + let!(:build) do + create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') + end before do build.run @@ -84,7 +105,9 @@ describe "Pipelines", feature: true, js: true do end context 'retryable pipelines' do - let!(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') } + let!(:build) do + create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') + end before do build.drop @@ -103,9 +126,20 @@ describe "Pipelines", feature: true, js: true do end context 'with manual actions' do - let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'manual build', stage: 'test', commands: 'test') } + let!(:manual) do + create( + :ci_build, + :manual, + pipeline: pipeline, + name: 'manual build', + stage: 'test', + commands: 'test' + ) + end - before { visit namespace_project_pipelines_path(project.namespace, project) } + before do + visit namespace_project_pipelines_path(project.namespace, project) + end it do wait_for_vue_resource @@ -127,7 +161,14 @@ describe "Pipelines", feature: true, js: true do context 'for generic statuses' do context 'when running' do - let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') } + let!(:running) do + create( + :generic_commit_status, + status: 'running', + pipeline: pipeline, + stage: 'test' + ) + end before do visit namespace_project_pipelines_path(project.namespace, project) @@ -143,7 +184,14 @@ describe "Pipelines", feature: true, js: true do end context 'when failed' do - let!(:status) { create(:generic_commit_status, :pending, pipeline: pipeline, stage: 'test') } + let!(:status) do + create( + :generic_commit_status, + :pending, + pipeline: pipeline, + stage: 'test' + ) + end before do status.drop @@ -162,9 +210,20 @@ describe "Pipelines", feature: true, js: true do context 'downloadable pipelines' do context 'with artifacts' do - let!(:with_artifacts) { create(:ci_build, :artifacts, :success, pipeline: pipeline, name: 'rspec tests', stage: 'test') } + let!(:with_artifacts) do + create( + :ci_build, + :artifacts, + :success, + pipeline: pipeline, + name: 'rspec tests', + stage: 'test' + ) + end - before { visit namespace_project_pipelines_path(project.namespace, project) } + before do + visit namespace_project_pipelines_path(project.namespace, project) + end it do wait_for_vue_resource @@ -178,17 +237,38 @@ describe "Pipelines", feature: true, js: true do end context 'with artifacts expired' do - let!(:with_artifacts_expired) { create(:ci_build, :artifacts_expired, :success, pipeline: pipeline, name: 'rspec', stage: 'test') } + let!(:with_artifacts_expired) do + create( + :ci_build, + :artifacts_expired, + :success, + pipeline: pipeline, + name: 'rspec', + stage: 'test' + ) + end - before { visit namespace_project_pipelines_path(project.namespace, project) } + before do + visit namespace_project_pipelines_path(project.namespace, project) + end it { expect(page).not_to have_selector('.build-artifacts') } end context 'without artifacts' do - let!(:without_artifacts) { create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage: 'test') } + let!(:without_artifacts) do + create( + :ci_build, + :success, + pipeline: pipeline, + name: 'rspec', + stage: 'test' + ) + end - before { visit namespace_project_pipelines_path(project.namespace, project) } + before do + visit namespace_project_pipelines_path(project.namespace, project) + end it { expect(page).not_to have_selector('.build-artifacts') } end @@ -199,14 +279,49 @@ describe "Pipelines", feature: true, js: true do let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') } before do - @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') - @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') - @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') - @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build') - @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external') + @success = create( + :ci_build, + :success, + pipeline: pipeline, + stage: 'build', + name: 'build' + ) + @failed = create( + :ci_build, + :failed, + pipeline: pipeline, + stage: 'test', + name: 'test', + commands: 'test' + ) + @running = create( + :ci_build, + :running, + pipeline: pipeline, + stage: 'deploy', + name: 'deploy' + ) + @manual = create( + :ci_build, + :manual, + pipeline: pipeline, + stage: 'deploy', + name: 'manual build' + ) + @external = create( + :generic_commit_status, + status: 'success', + pipeline: pipeline, + name: 'jenkins', + stage: 'external' + ) end - before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } + before do + visit namespace_project_pipeline_path( + project.namespace, project, pipeline + ) + end it 'shows a list of builds' do expect(page).to have_content('Test') @@ -256,7 +371,9 @@ describe "Pipelines", feature: true, js: true do describe 'POST /:project/pipelines', feature: true, js: true do let(:project) { create(:project) } - before { visit new_namespace_project_pipeline_path(project.namespace, project) } + before do + visit new_namespace_project_pipeline_path(project.namespace, project) + end context 'for valid commit' do before { fill_in('pipeline[ref]', with: 'master') } @@ -264,7 +381,13 @@ describe "Pipelines", feature: true, js: true do context 'with gitlab-ci.yml' do before { stub_ci_pipeline_to_return_yaml_file } - it { expect{ click_on 'Create pipeline' }.to change{ Ci::Pipeline.count }.by(1) } + it do + expect{ + click_on 'Create pipeline' + }.to change{ + Ci::Pipeline.count + }.by(1) + end end context 'without gitlab-ci.yml' do From fc5eb253dbd1fac0674c1a164847dabfd1229c9e Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 17 Nov 2016 12:06:45 -0700 Subject: [PATCH 108/277] refactor store --- .../javascripts/vue_pipelines_index/store.js.es6 | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index c1c153b0604..8226aef9c05 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -34,7 +34,13 @@ gl.PipelineStore = class { fetchDataLoop(Vue, pageNum, url) { - Vue.activeResources = 0; + const setVueResources = () => { Vue.activeResources = 1; }; + const resetVueResources = () => { Vue.activeResources = 0; }; + const addToVueResources = () => { Vue.activeResources += 1; }; + const subtractFromVueResources = () => { Vue.activeResources -= 1; }; + + resetVueResources(); // set Vue.resources to 0 + const updatePipelineNums = (total, running) => { document.querySelector('.js-totalbuilds-count').innerHTML = total; document.querySelector('.js-running-count').innerHTML = running; @@ -42,9 +48,9 @@ const resourceChecker = () => { if (Vue.activeResources === 0) { - Vue.activeResources = 1; + setVueResources(); } else { - Vue.activeResources += 1; + addToVueResources(); } }; @@ -57,7 +63,7 @@ Vue.set(this, 'count', res.count); updatePipelineNums(this.count.all, this.count.running_or_pending); this.pageRequest = false; - Vue.activeResources -= 1; + subtractFromVueResources(); }, () => new Flash( 'Something went wrong on our end.' )); @@ -71,7 +77,7 @@ Vue.set(this, 'pipelines', p.updatePipelines(res)); Vue.set(this, 'count', res.count); updatePipelineNums(this.count.all, this.count.running_or_pending); - Vue.activeResources -= 1; + subtractFromVueResources(); }, () => new Flash( 'Something went wrong on our end.' )); From 398be9cadd5c50c1de3d31facba7e62d54437f37 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 18 Nov 2016 08:10:32 -0600 Subject: [PATCH 109/277] performance and diff updates --- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 8226aef9c05..053a068396f 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -7,7 +7,7 @@ this.pipelines = pipelines; this.updateClone = (update, newPipe) => { update.forEach((pipe) => { - if (pipe.id === newPipe.id) pipe = Object.assign(pipe, newPipe); + if (pipe.id === newPipe.id) pipe = Object.assign({}, pipe, newPipe); }); }; } @@ -20,7 +20,7 @@ } updatePipelines(apiResponse) { - const update = this.pipelines.map(e => e); + const update = this.pipelines.slice(0); apiResponse.pipelines.forEach((newPipe) => { if (newPipe.commit) { update.unshift(newPipe); From a993e0af7f5149d73385a10d3c91e06b772f8ea3 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 18 Nov 2016 09:54:16 -0600 Subject: [PATCH 110/277] working on tests [ci skip] --- spec/features/projects/pipelines_spec.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines_spec.rb index 66034d7200b..059a5972833 100644 --- a/spec/features/projects/pipelines_spec.rb +++ b/spec/features/projects/pipelines_spec.rb @@ -30,10 +30,7 @@ describe "Pipelines", feature: true, js: true do let(:project) { create(:project) } before do - visit namespace_project_pipelines_path( - project.namespace, project, - scope: scope - ) + visit namespace_project_pipelines_path(project.namespace, project) end it do @@ -283,7 +280,7 @@ describe "Pipelines", feature: true, js: true do project: project, ref: 'master', sha: project.commit.id - ) + ) end before do From 061b4dd82a0736cf9b92d4633c3ef5af61a11cc3 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 18 Nov 2016 11:33:51 -0600 Subject: [PATCH 111/277] much more performant pageSlicer - [ci skip] --- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 053a068396f..396114e05b6 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -13,10 +13,8 @@ } currentPageSlicer(update) { - const length = update.length; - if (this.pipelines.length === update.length) return update; if (update.length <= 30) return update; - return update.slice(0, (length - 1)); + return update.slice(0, 29); } updatePipelines(apiResponse) { From 758cdf54fe61ff7e62c9e3aac70819fb824a8b5c Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 18 Nov 2016 11:35:35 -0600 Subject: [PATCH 112/277] no this - define in constructor - [ci skip] --- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 396114e05b6..ffe6292782a 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -10,11 +10,10 @@ if (pipe.id === newPipe.id) pipe = Object.assign({}, pipe, newPipe); }); }; - } - - currentPageSlicer(update) { - if (update.length <= 30) return update; - return update.slice(0, 29); + this.currentPageSlicer = (update) => { + if (update.length <= 30) return update; + return update.slice(0, 29); + }; } updatePipelines(apiResponse) { From d0a5428a3865f59814528fd950a1e00ccb65af3d Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 18 Nov 2016 16:26:49 -0600 Subject: [PATCH 113/277] constant variable hardcoding --- .../javascripts/vue_pagination/index.js.es6 | 38 ++++++++++++------- .../vue_pipelines_index/index.js.es6 | 2 +- .../vue_pipelines_index/pipelines.js.es6 | 18 ++++++--- .../vue_pipelines_index/store.js.es6 | 7 +++- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 5f844ae8923..4f83671e83d 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -2,6 +2,14 @@ /* eslint-disable no-param-reassign, no-plusplus */ ((gl) => { + const PAGINATION_SIZE = 30; + const PAGINATION_UI_BUTTON_LIMIT = 4; + const SPREAD = '...'; + const PREV = 'Prev'; + const NEXT = 'Next'; + const FIRST = '<< First'; + const LAST = 'Last >>'; + gl.VueGlPagination = Vue.extend({ props: [ 'changepage', @@ -10,40 +18,42 @@ ], computed: { last() { - return Math.ceil(+this.count / 30); + return Math.ceil(+this.count / PAGINATION_SIZE); }, getItems() { const total = +this.last; const page = +this.pagenum; const items = []; - if (page > 1) items.push({ title: '<< First', where: 1 }); + if (page > 1) items.push({ title: FIRST, where: 1 }); if (page > 1) { - items.push({ title: 'Prev', where: page - 1 }); + items.push({ title: PREV, where: page - 1 }); } else { - items.push({ title: 'Prev', where: page - 1, disabled: true }); + items.push({ title: PREV, where: page - 1, disabled: true }); } - if (page > 6) items.push({ title: '...', separator: true }); + if (page > 6) items.push({ title: SPREAD, separator: true }); - const start = Math.max(page - 4, 1); - const end = Math.min(page + 4, total); + const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1); + const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total); for (let i = start; i <= end; i++) { const isActive = i === page; items.push({ title: i, active: isActive, where: i }); } - if (total - page > 4) items.push({ title: '...', separator: true }); - - if (page === total) { - items.push({ title: 'Next', where: page + 1, disabled: true }); - } else if (total - page >= 1) { - items.push({ title: 'Next', where: page + 1 }); + if (total - page > PAGINATION_UI_BUTTON_LIMIT) { + items.push({ title: SPREAD, separator: true }); } - if (total - page >= 1) items.push({ title: 'Last >>', where: total }); + if (page === total) { + items.push({ title: NEXT, where: page + 1, disabled: true }); + } else if (total - page >= 1) { + items.push({ title: NEXT, where: page + 1 }); + } + + if (total - page >= 1) items.push({ title: LAST, where: total }); return items; }, diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 57afce34c2b..1584345098c 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -29,7 +29,7 @@ store: new gl.PipelineStore(), }, components: { - 'vue-pipelines': gl.VuePipeLines, + 'vue-pipelines': gl.VuePipelines, }, template: `
    diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index b86a4c6f81b..09c480457c4 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -2,7 +2,13 @@ /* eslint-disable no-param-reassign, no-bitwise*/ ((gl) => { - gl.VuePipeLines = Vue.extend({ + const SPREAD = '...'; + const PREV = 'Prev'; + const NEXT = 'Next'; + const FIRST = '<< First'; + const LAST = 'Last >>'; + + gl.VuePipelines = Vue.extend({ components: { runningPipeline: gl.VueRunningPipeline, pipelineActions: gl.VuePipelineActions, @@ -41,12 +47,12 @@ methods: { changepage(e, last) { const text = e.target.innerText; - if (text === '...') return; + if (text === SPREAD) return; if (/^-?[\d.]+(?:e-?\d+)?$/.test(text)) this.pagenum = +text; - if (text === 'Last >>') this.pagenum = last; - if (text === 'Next') this.pagenum = +this.pagenum + 1; - if (text === 'Prev') this.pagenum = +this.pagenum - 1; - if (text === '<< First') this.pagenum = 1; + if (text === LAST) this.pagenum = last; + if (text === NEXT) this.pagenum = +this.pagenum + 1; + if (text === PREV) this.pagenum = +this.pagenum - 1; + if (text === FIRST) this.pagenum = 1; window.history.pushState({}, null, `?p=${this.pagenum}`); clearInterval(this.intervalId); diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index ffe6292782a..ada99b67f26 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -2,6 +2,9 @@ /* eslint-disable no-param-reassign */ ((gl) => { + const PAGINATION_LIMIT = 31; + const SLICE_LIMIT = 29; + class PipelineUpdater { constructor(pipelines) { this.pipelines = pipelines; @@ -11,8 +14,8 @@ }); }; this.currentPageSlicer = (update) => { - if (update.length <= 30) return update; - return update.slice(0, 29); + if (update.length < PAGINATION_LIMIT) return update; + return update.slice(0, SLICE_LIMIT); }; } From ff4edf37f3d8e7742292db6d5e50ba6f599950ff Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 18 Nov 2016 16:33:41 -0600 Subject: [PATCH 114/277] this.count as only param for updatePipelineNums - [ci skip] --- .../javascripts/vue_pipelines_index/store.js.es6 | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index ada99b67f26..0bf59823c11 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -41,8 +41,11 @@ resetVueResources(); // set Vue.resources to 0 - const updatePipelineNums = (total, running) => { - document.querySelector('.js-totalbuilds-count').innerHTML = total; + const updatePipelineNums = (count) => { + const { all } = count; + // cannot define non camel case, so not using destructuring for running + const running = count.running_or_pending; + document.querySelector('.js-totalbuilds-count').innerHTML = all; document.querySelector('.js-running-count').innerHTML = running; }; @@ -61,7 +64,7 @@ Vue.set(this, 'updatedAt', res.updated_at); Vue.set(this, 'pipelines', res.pipelines); Vue.set(this, 'count', res.count); - updatePipelineNums(this.count.all, this.count.running_or_pending); + updatePipelineNums(this.count); this.pageRequest = false; subtractFromVueResources(); }, () => new Flash( @@ -76,7 +79,7 @@ Vue.set(this, 'updatedAt', res.updated_at); Vue.set(this, 'pipelines', p.updatePipelines(res)); Vue.set(this, 'count', res.count); - updatePipelineNums(this.count.all, this.count.running_or_pending); + updatePipelineNums(this.count); subtractFromVueResources(); }, () => new Flash( 'Something went wrong on our end.' From cb35c5e1c9172a566e88ecc3be13fd35b6c3b8d0 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 22 Nov 2016 13:36:45 -0600 Subject: [PATCH 115/277] stashing for now --- .../vue_pipelines_index/index.js.es6 | 2 ++ .../vue_pipelines_index/pipelines.js.es6 | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 1584345098c..2bd8d5d327f 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -1,6 +1,8 @@ /* global Vue, VueResource, gl */ /* eslint-disable no-bitwise, no-plusplus*/ +/*= require vue_common_component/commit */ + //= require vue-resource //= require ./object_assign.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 09c480457c4..fd843844c7c 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -13,7 +13,7 @@ runningPipeline: gl.VueRunningPipeline, pipelineActions: gl.VuePipelineActions, stages: gl.VueStages, - branchCommit: gl.VueBranchCommit, + commit: gl.CommitComponent, pipelineUrl: gl.VuePipelineUrl, pipelineHead: gl.VuePipelineHead, glPagination: gl.VueGlPagination, @@ -59,6 +59,11 @@ this.pageRequest = true; this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope); }, + author(pipeline) { + const author = pipeline.commit.author; + if (author) return author; + return ({}); + }, }, template: `
    @@ -72,7 +77,15 @@ - + + From 76ef4f02c36a3ca8f118e57d47f795000fa481f5 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 22 Nov 2016 15:36:21 -0600 Subject: [PATCH 116/277] diff updates from API to DOM --- .../vue_pipelines_index/store.js.es6 | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 0bf59823c11..16c4197dd66 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -8,27 +8,20 @@ class PipelineUpdater { constructor(pipelines) { this.pipelines = pipelines; - this.updateClone = (update, newPipe) => { - update.forEach((pipe) => { - if (pipe.id === newPipe.id) pipe = Object.assign({}, pipe, newPipe); - }); - }; - this.currentPageSlicer = (update) => { - if (update.length < PAGINATION_LIMIT) return update; - return update.slice(0, SLICE_LIMIT); - }; } updatePipelines(apiResponse) { const update = this.pipelines.slice(0); - apiResponse.pipelines.forEach((newPipe) => { + apiResponse.pipelines.forEach((newPipe, i) => { if (newPipe.commit) { update.unshift(newPipe); } else { - this.updateClone(update, newPipe); + const newMerge = Object.assign({}, update[i], newPipe); + update[i] = newMerge; } }); - return this.currentPageSlicer(update); + if (update.length < PAGINATION_LIMIT) return update; + return update.slice(0, SLICE_LIMIT); } } From d15a0b68643f09be4f8e61b45c479a200575c245 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 22 Nov 2016 15:37:51 -0600 Subject: [PATCH 117/277] slight cleanup [ci skip] --- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 16c4197dd66..836a6be9a40 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -36,7 +36,6 @@ const updatePipelineNums = (count) => { const { all } = count; - // cannot define non camel case, so not using destructuring for running const running = count.running_or_pending; document.querySelector('.js-totalbuilds-count').innerHTML = all; document.querySelector('.js-running-count').innerHTML = running; From ce2750e095ededa37087e2ba10f75ae7eb465584 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 23 Nov 2016 10:52:21 -0600 Subject: [PATCH 118/277] refactor - rename - simple behavior for time_ago --- .../vue_pipelines_index/pipelines.js.es6 | 2 +- .../vue_pipelines_index/store.js.es6 | 25 ++++++++++--------- .../vue_pipelines_index/time_ago.js.es6 | 17 ++++++++++--- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index fd843844c7c..1c8e98f3865 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -78,7 +78,7 @@ { if (newPipe.commit) { - update.unshift(newPipe); + diffData.unshift(newPipe); } else { - const newMerge = Object.assign({}, update[i], newPipe); - update[i] = newMerge; + const newMerge = Object.assign({}, diffData[i], newPipe); + diffData[i] = newMerge; } }); - if (update.length < PAGINATION_LIMIT) return update; - return update.slice(0, SLICE_LIMIT); + if (diffData.length < PAGINATION_LIMIT) return diffData; + return diffData.slice(0, SLICE_LIMIT); } } @@ -36,6 +36,7 @@ const updatePipelineNums = (count) => { const { all } = count; + // cannot define non camel case, so not using destructuring for running const running = count.running_or_pending; document.querySelector('.js-totalbuilds-count').innerHTML = all; document.querySelector('.js-running-count').innerHTML = running; @@ -67,9 +68,9 @@ this.$http.get(`${url}?page=${pageNum}&updated_at=${this.updatedAt}`) .then((response) => { const res = JSON.parse(response.body); - const p = new PipelineUpdater(this.pipelines); + const p = new RealtimePaginationUpdater(this.pipelines); Vue.set(this, 'updatedAt', res.updated_at); - Vue.set(this, 'pipelines', p.updatePipelines(res)); + Vue.set(this, 'pipelines', p.updatePageDiff(res)); Vue.set(this, 'count', res.count); updatePipelineNums(this.count); subtractFromVueResources(); diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index 8eb25efe344..1d0da41ebbd 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -5,6 +5,11 @@ props: [ 'pipeline', ], + computed: { + localTimeFinished() { + return gl.utils.formatDate(this.pipeline.details.finished_at); + }, + }, methods: { formatSection(section) { if (`${section}`.split('').length <= 1) return `0${section}`; @@ -58,13 +63,17 @@ }; }, duration() { - if (this.timeStopped()) return this.finishdate(); - return this.runningdate(); + // if (this.timeStopped()) return this.finishdate(); + // return this.runningdate(); + const { duration } = this.pipeline.details; + if (duration === 0) return '00:00:00'; + if (duration !== null) return duration; + return false; }, }, template: ` -

    +

    {{timeStopped().words}} From 8a2be66b04c1bd6368425d75239ebf0611a07f0f Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 23 Nov 2016 10:54:52 -0600 Subject: [PATCH 119/277] clean up on time_ago [ci skip] --- .../vue_pipelines_index/time_ago.js.es6 | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index 1d0da41ebbd..496065dbedc 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -11,39 +11,6 @@ }, }, methods: { - formatSection(section) { - if (`${section}`.split('').length <= 1) return `0${section}`; - return `${section}`; - }, - hours(date) { - return this.formatSection(date.getHours()); - }, - minutes(date) { - return this.formatSection(date.getMinutes()); - }, - seconds(date) { - return this.formatSection(date.getSeconds()); - }, - finishdate() { - const date = new Date( - new Date( - this.pipeline.details.finished_at - ).getTime() - new Date( - this.pipeline.created_at - ).getTime() - ); - return ( - `${this.hours(date)}:${this.minutes(date)}:${this.seconds(date)}` - ); - }, - runningdate() { - const date = new Date( - new Date().getTime() - new Date(this.pipeline.created_at).getTime() - ); - return ( - `${this.hours(date)}:${this.minutes(date)}:${this.seconds(date)}` - ); - }, timeStopped() { const options = { weekday: 'long', @@ -63,8 +30,6 @@ }; }, duration() { - // if (this.timeStopped()) return this.finishdate(); - // return this.runningdate(); const { duration } = this.pipeline.details; if (duration === 0) return '00:00:00'; if (duration !== null) return duration; From 8856d5a869939f8c5fdba827eb1c02d68bd6b162 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 29 Nov 2016 09:43:08 -0600 Subject: [PATCH 120/277] turn off diff updates - wrap commit comp in td --- .../vue_pipelines_index/pipelines.js.es6 | 22 ++++++++++--------- .../vue_pipelines_index/store.js.es6 | 20 ++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 1c8e98f3865..006e38505e2 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -55,7 +55,7 @@ if (text === FIRST) this.pagenum = 1; window.history.pushState({}, null, `?p=${this.pagenum}`); - clearInterval(this.intervalId); + // clearInterval(this.intervalId); this.pageRequest = true; this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope); }, @@ -77,15 +77,17 @@
    - - + + + + diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index c5eca3c1289..a46bdc47491 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -81,17 +81,17 @@ resourceChecker(); goFetch(); - this.intervalId = setInterval(() => { - if (this.updatedAt) { - resourceChecker(); - if (Vue.activeResources > 1) return; - goUpdate(); - } - }, 3000); + // this.intervalId = setInterval(() => { + // if (this.updatedAt) { + // resourceChecker(); + // if (Vue.activeResources > 1) return; + // goUpdate(); + // } + // }, 3000); - window.onbeforeunload = function removePipelineInterval() { - clearInterval(this.intervalId); - }; + // window.onbeforeunload = function removePipelineInterval() { + // clearInterval(this.intervalId); + // }; } }; })(window.gl || (window.gl = {})); From 1f50b3fed927bca38335bcd77b9ea69e82a0d2c1 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 29 Nov 2016 19:19:03 -0600 Subject: [PATCH 121/277] render valid user props for pipeline user --- .../javascripts/vue_pipelines_index/pipeline_url.js.es6 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 index 94c51ad77fa..e11f68bbd36 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 @@ -11,7 +11,8 @@ ], computed: { user() { - if (!this.pipeline.user === null) return true; + // debugger + if (this.pipeline.user) return true; return false; }, }, @@ -23,14 +24,14 @@
    by Date: Tue, 29 Nov 2016 19:19:22 -0600 Subject: [PATCH 122/277] rid off debugger comment [ci skip] --- app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 index e11f68bbd36..d9602b4209d 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 @@ -11,7 +11,6 @@ ], computed: { user() { - // debugger if (this.pipeline.user) return true; return false; }, From 649d1eb23073543ceed995124c1cb67aabb8bfc2 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 29 Nov 2016 20:44:00 -0600 Subject: [PATCH 123/277] new line for skipped - use logic gate for REALTIME true or false --- .../vue_pipelines_index/pipelines.js.es6 | 3 ++- .../vue_pipelines_index/store.js.es6 | 23 +++++++++++-------- .../vue_pipelines_status/skipped.js.es6 | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 006e38505e2..477d59ad2c3 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -2,6 +2,7 @@ /* eslint-disable no-param-reassign, no-bitwise*/ ((gl) => { + const REALTIME = false; const SPREAD = '...'; const PREV = 'Prev'; const NEXT = 'Next'; @@ -55,7 +56,7 @@ if (text === FIRST) this.pagenum = 1; window.history.pushState({}, null, `?p=${this.pagenum}`); - // clearInterval(this.intervalId); + if (REALTIME) clearInterval(this.intervalId); this.pageRequest = true; this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope); }, diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index a46bdc47491..3ec627d24d0 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -2,6 +2,7 @@ /* eslint-disable no-param-reassign */ ((gl) => { + const REALTIME = false; const PAGINATION_LIMIT = 31; const SLICE_LIMIT = 29; @@ -81,17 +82,19 @@ resourceChecker(); goFetch(); - // this.intervalId = setInterval(() => { - // if (this.updatedAt) { - // resourceChecker(); - // if (Vue.activeResources > 1) return; - // goUpdate(); - // } - // }, 3000); + if (REALTIME) { + this.intervalId = setInterval(() => { + if (this.updatedAt) { + resourceChecker(); + if (Vue.activeResources > 1) return; + goUpdate(); + } + }, 3000); - // window.onbeforeunload = function removePipelineInterval() { - // clearInterval(this.intervalId); - // }; + window.onbeforeunload = function removePipelineInterval() { + clearInterval(this.intervalId); + }; + } } }; })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/skipped.js.es6 b/app/assets/javascripts/vue_pipelines_status/skipped.js.es6 index 92d53c4a87e..da904010607 100644 --- a/app/assets/javascripts/vue_pipelines_status/skipped.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/skipped.js.es6 @@ -20,4 +20,4 @@ `, }); -})(window.gl || (window.gl = {})); \ No newline at end of file +})(window.gl || (window.gl = {})); From d11a298cd5ff9888f15e14efe2313f1445051618 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 30 Nov 2016 02:23:51 -0600 Subject: [PATCH 124/277] on focus - blur - and page closed -- intervals are taken care of for time ago and realtime --- .../vue_pipelines_index/pipelines.js.es6 | 10 ++++- .../vue_pipelines_index/store.js.es6 | 35 ++++++++++++---- .../vue_pipelines_index/time_ago.js.es6 | 40 ++++++++++++++----- 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 477d59ad2c3..b4cdc5080a6 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -24,6 +24,7 @@ data() { return { pipelines: [], + allTimeIntervals: [], intervalId: '', updatedAt: '', pagenum: 1, @@ -65,6 +66,9 @@ if (author) return author; return ({}); }, + addTimeInterval(id, that) { + this.allTimeIntervals.push({ id: id, component: that }); + }, }, template: `

    @@ -90,7 +94,11 @@ - + + diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 3ec627d24d0..359eab3f6c8 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -2,7 +2,7 @@ /* eslint-disable no-param-reassign */ ((gl) => { - const REALTIME = false; + const REALTIME = true; const PAGINATION_LIMIT = 31; const SLICE_LIMIT = 29; @@ -33,11 +33,10 @@ const addToVueResources = () => { Vue.activeResources += 1; }; const subtractFromVueResources = () => { Vue.activeResources -= 1; }; - resetVueResources(); // set Vue.resources to 0 + resetVueResources(); const updatePipelineNums = (count) => { const { all } = count; - // cannot define non camel case, so not using destructuring for running const running = count.running_or_pending; document.querySelector('.js-totalbuilds-count').innerHTML = all; document.querySelector('.js-running-count').innerHTML = running; @@ -82,7 +81,7 @@ resourceChecker(); goFetch(); - if (REALTIME) { + const poller = () => { this.intervalId = setInterval(() => { if (this.updatedAt) { resourceChecker(); @@ -90,11 +89,31 @@ goUpdate(); } }, 3000); + }; - window.onbeforeunload = function removePipelineInterval() { - clearInterval(this.intervalId); - }; - } + if (REALTIME) poller(); + + const removePipelineInterval = () => { + this.allTimeIntervals.forEach(e => clearInterval(e.id)); + if (REALTIME) clearInterval(this.intervalId); + }; + + const startIntervalLoops = () => { + this.allTimeIntervals.forEach(e => e.component.startInterval()); + if (REALTIME) poller(); + }; + + window.onbeforeunload = function onClose() { + removePipelineInterval(); + }; + + window.onblur = function remove() { + removePipelineInterval(); + }; + + window.onfocus = function start() { + startIntervalLoops(); + }; } }; })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index 496065dbedc..505b1ee3490 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -1,17 +1,35 @@ /* global Vue, gl */ /* eslint-disable no-param-reassign */ ((gl) => { + const REALTIME = false; + gl.VueTimeAgo = Vue.extend({ + data() { + return { + timeInterval: '', + currentTime: new Date(), + }; + }, props: [ 'pipeline', + 'addTimeInterval', ], + created() { + if (!REALTIME) { + this.timeInterval = setInterval(() => { + this.currentTime = new Date(); + }, 1000); + + this.addTimeInterval(this.timeInterval, this); + } + }, computed: { localTimeFinished() { return gl.utils.formatDate(this.pipeline.details.finished_at); }, - }, - methods: { timeStopped() { + const changeTime = this.currentTime; + const options = { weekday: 'long', year: 'numeric', @@ -23,18 +41,22 @@ const finished = this.pipeline.details.finished_at; - if (!finished) return false; - - return { - words: gl.utils.getTimeago().format(finished), - }; + if (!finished && changeTime) return false; + return ({ words: gl.utils.getTimeago().format(finished) }); }, + }, + methods: { duration() { const { duration } = this.pipeline.details; if (duration === 0) return '00:00:00'; if (duration !== null) return duration; return false; }, + startInterval() { + this.timeInterval = setInterval(() => { + this.currentTime = new Date(); + }, 1000); + }, }, template: ` @@ -51,7 +73,7 @@ {{duration()}}

    -

    +

    From 874a7e8d20dfead42ae957ea8746b20a1446dcea Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 30 Nov 2016 02:27:05 -0600 Subject: [PATCH 125/277] turned off REALTIME - time ago blur and focus still good --- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 359eab3f6c8..6d023a45b21 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -2,7 +2,7 @@ /* eslint-disable no-param-reassign */ ((gl) => { - const REALTIME = true; + const REALTIME = false; const PAGINATION_LIMIT = 31; const SLICE_LIMIT = 29; From fd9adc78824500928465d82c691fdd76e290672d Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 30 Nov 2016 02:30:03 -0600 Subject: [PATCH 126/277] eslint calm [ci skip] --- app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index b4cdc5080a6..6ef335ab398 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -67,7 +67,7 @@ return ({}); }, addTimeInterval(id, that) { - this.allTimeIntervals.push({ id: id, component: that }); + this.allTimeIntervals.push({ id, component: that }); }, }, template: ` From 81ae6c7d0a38bb66289c5d83239255ce5cdfcea5 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 30 Nov 2016 09:29:31 -0700 Subject: [PATCH 127/277] fire timeago interval no matter what --- .../javascripts/vue_pipelines_index/time_ago.js.es6 | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index 505b1ee3490..c078e9459f0 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -1,8 +1,6 @@ /* global Vue, gl */ /* eslint-disable no-param-reassign */ ((gl) => { - const REALTIME = false; - gl.VueTimeAgo = Vue.extend({ data() { return { @@ -15,13 +13,11 @@ 'addTimeInterval', ], created() { - if (!REALTIME) { - this.timeInterval = setInterval(() => { - this.currentTime = new Date(); - }, 1000); + this.timeInterval = setInterval(() => { + this.currentTime = new Date(); + }, 1000); - this.addTimeInterval(this.timeInterval, this); - } + this.addTimeInterval(this.timeInterval, this); }, computed: { localTimeFinished() { From 6eb3728490cbf33ea3e401653a5da0ab290c6f52 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 30 Nov 2016 09:30:06 -0700 Subject: [PATCH 128/277] format - [ci skip] --- app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index c078e9459f0..de6eec9bc5b 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -25,18 +25,14 @@ }, timeStopped() { const changeTime = this.currentTime; - const options = { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric', }; - options.timeZoneName = 'short'; - const finished = this.pipeline.details.finished_at; - if (!finished && changeTime) return false; return ({ words: gl.utils.getTimeago().format(finished) }); }, From 2791226c6b59db62b76bf4f5c4f4f945e9760eaa Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 1 Dec 2016 11:59:00 +0100 Subject: [PATCH 129/277] Remove support from incremental pipeline updates --- .../projects/pipelines_controller.rb | 3 +- app/serializers/pipeline_entity.rb | 38 ++++--------------- app/serializers/pipeline_serializer.rb | 5 --- spec/serializers/pipeline_serializer_spec.rb | 23 +---------- 4 files changed, 9 insertions(+), 60 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 3d095e2b690..b1d82e952bd 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -11,7 +11,6 @@ class Projects::PipelinesController < Projects::ApplicationController @running_or_pending_count = PipelinesFinder.new(project).execute(scope: 'running').count @pipelines_count = PipelinesFinder.new(project).execute.count - @last_updated = params[:updated_at] respond_to do |format| format.html @@ -19,7 +18,7 @@ class Projects::PipelinesController < Projects::ApplicationController render json: { pipelines: PipelineSerializer .new(project: @project, user: @current_user) - .incremental(@pipelines, @last_updated), + .represent(@pipelines), updated_at: Time.now.utc, count: { all: @pipelines_count, diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 08346b4ed77..cf99e628d5f 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -2,7 +2,7 @@ class PipelineEntity < Grape::Entity include RequestAwareEntity expose :id - expose :user, if: proc { created_exposure? }, using: UserEntity + expose :user, using: UserEntity expose :url do |pipeline| namespace_project_pipeline_path( @@ -11,7 +11,7 @@ class PipelineEntity < Grape::Entity pipeline) end - expose :details, if: proc { updated_exposure? } do + expose :details do expose :status expose :duration expose :finished_at @@ -20,7 +20,7 @@ class PipelineEntity < Grape::Entity expose :manual_actions, using: PipelineActionEntity end - expose :flags, if: proc { created_exposure? } do + expose :flags do expose :latest?, as: :latest expose :triggered?, as: :triggered @@ -33,7 +33,7 @@ class PipelineEntity < Grape::Entity end end - expose :ref, if: proc { updated_exposure? } do + expose :ref do expose :name do |pipeline| pipeline.ref end @@ -48,16 +48,16 @@ class PipelineEntity < Grape::Entity expose :tag? end - expose :commit, if: proc { created_exposure? }, using: CommitEntity + expose :commit, using: CommitEntity - expose :retry_url, if: proc { updated_exposure? } do |pipeline| + expose :retry_url do |pipeline| can?(request.user, :update_pipeline, pipeline.project) && pipeline.retryable? && retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) end - expose :cancel_url, if: proc { updated_exposure? } do |pipeline| + expose :cancel_url do |pipeline| can?(request.user, :update_pipeline, pipeline.project) && pipeline.cancelable? && cancel_namespace_project_pipeline_path(pipeline.project.namespace, @@ -65,28 +65,4 @@ class PipelineEntity < Grape::Entity end expose :created_at, :updated_at - - def created_exposure? - !incremental? || created? - end - - def updated_exposure? - !incremental? || updated? - end - - def incremental? - options[:incremental] && last_updated - end - - def last_updated - options.fetch(:last_updated) - end - - def updated? - @object.updated_at > last_updated - end - - def created? - @object.created_at > last_updated - end end diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index 8d3182d9766..f7abbec7d45 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -1,8 +1,3 @@ class PipelineSerializer < BaseSerializer entity PipelineEntity - - def incremental(resource, last_updated) - represent(resource, incremental: true, - last_updated: last_updated) - end end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index c4eba2f2537..f6b7d1eb1dd 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -11,26 +11,5 @@ describe PipelineSerializer do let(:user) { create(:user) } - context 'when using incremental serializer' do - let(:json) do - serializer.incremental(pipelines, time).as_json - end - - context 'when pipeline has been already updated' do - let(:time) { Time.now } - - it 'exposes only minimal information' do - expect(json.first.keys).to contain_exactly(:id, :url) - expect(json.second.keys).to contain_exactly(:id, :url) - end - end - - context 'when pipeline updated in the meantime' do - let(:time) { Time.now - 10.minutes } - - it 'exposes new data incrementally' do - expect(json.first.keys.count).to eq 9 - end - end - end + # TODO add some tests here. end From 3acee982037b1328df29dc867c83b83746f33e44 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 1 Dec 2016 15:02:26 -0700 Subject: [PATCH 130/277] update styling for loading icon --- app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 | 4 ++-- app/assets/stylesheets/pages/pipelines.scss | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 6ef335ab398..61af9799ca4 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -72,7 +72,7 @@ }, template: `
    -
    +
    @@ -104,7 +104,7 @@
    -
    +
    Date: Thu, 1 Dec 2016 16:07:30 -0700 Subject: [PATCH 131/277] get commit component to render needed info - work on SVG loading next --- .../vue_pipelines_index/pipelines.js.es6 | 16 ++++-- .../vue_pipelines_index/store.js.es6 | 54 +------------------ 2 files changed, 13 insertions(+), 57 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 61af9799ca4..e02c7dc528e 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -62,9 +62,17 @@ this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope); }, author(pipeline) { - const author = pipeline.commit.author; + const { commit } = pipeline; + const author = commit.author; if (author) return author; - return ({}); + + const nonUser = { + avatar_url: commit.author_gravatar_url, + web_url: `mailto:${commit.author_email}`, + username: commit.author_name, + }; + + return nonUser; }, addTimeInterval(id, that) { this.allTimeIntervals.push({ id, component: that }); @@ -85,7 +93,7 @@
    { - const REALTIME = false; - const PAGINATION_LIMIT = 31; - const SLICE_LIMIT = 29; - - class RealtimePaginationUpdater { - constructor(pageData) { - this.pageData = pageData; - } - - updatePageDiff(apiResponse) { - const diffData = this.pageData.slice(0); - apiResponse.pipelines.forEach((newPipe, i) => { - if (newPipe.commit) { - diffData.unshift(newPipe); - } else { - const newMerge = Object.assign({}, diffData[i], newPipe); - diffData[i] = newMerge; - } - }); - if (diffData.length < PAGINATION_LIMIT) return diffData; - return diffData.slice(0, SLICE_LIMIT); - } - } - gl.PipelineStore = class { fetchDataLoop(Vue, pageNum, url) { const setVueResources = () => { Vue.activeResources = 1; }; @@ -61,46 +37,18 @@ this.pageRequest = false; subtractFromVueResources(); }, () => new Flash( - 'Something went wrong on our end.' - )); - - const goUpdate = () => - this.$http.get(`${url}?page=${pageNum}&updated_at=${this.updatedAt}`) - .then((response) => { - const res = JSON.parse(response.body); - const p = new RealtimePaginationUpdater(this.pipelines); - Vue.set(this, 'updatedAt', res.updated_at); - Vue.set(this, 'pipelines', p.updatePageDiff(res)); - Vue.set(this, 'count', res.count); - updatePipelineNums(this.count); - subtractFromVueResources(); - }, () => new Flash( - 'Something went wrong on our end.' + 'Something went wrong on our end.', )); resourceChecker(); goFetch(); - const poller = () => { - this.intervalId = setInterval(() => { - if (this.updatedAt) { - resourceChecker(); - if (Vue.activeResources > 1) return; - goUpdate(); - } - }, 3000); - }; - - if (REALTIME) poller(); - const removePipelineInterval = () => { this.allTimeIntervals.forEach(e => clearInterval(e.id)); - if (REALTIME) clearInterval(this.intervalId); }; const startIntervalLoops = () => { this.allTimeIntervals.forEach(e => e.component.startInterval()); - if (REALTIME) poller(); }; window.onbeforeunload = function onClose() { From 1e7f91f2d611b80e7537a56aefc3e31452eaf273 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 1 Dec 2016 16:23:47 -0700 Subject: [PATCH 132/277] remove 30% width for commit column --- app/assets/stylesheets/pages/pipelines.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 3b8d2268437..0002c57cb13 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -33,8 +33,6 @@ } .branch-commit { - width: 30%; - .branch-name { max-width: 195px; } From c55ba05228c719bf1e05818fb91200b3d60da990 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 1 Dec 2016 18:08:34 -0700 Subject: [PATCH 133/277] add svg to pipeline index haml - prep for commit comp change - [ci skip] --- .../vue_pipelines_index/pipelines.js.es6 | 13 +++++++++++-- app/views/projects/pipelines/index.html.haml | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index e02c7dc528e..2e126434ea5 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -74,6 +74,15 @@ return nonUser; }, + ref(pipeline) { + const { ref } = pipeline; + const commitRef = { + name: ref.name, + tag: ref['tag?'], + ref_url: ref.url, + }; + return commitRef; + }, addTimeInterval(id, that) { this.allTimeIntervals.push({ id, component: that }); }, @@ -92,10 +101,10 @@ diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 3185f1573c6..4025a6859f2 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -53,6 +53,8 @@ = render @pipelines, commit_sha: true, stage: true, allow_retry: true = paginate @pipelines, theme: 'gitlab' - else + .commit-icon-svg.hidden + = custom_icon("icon_commit") .vue-pipelines-index = page_specific_javascript_tag('vue_icons/index.js') From a96499cf3cc8189e9f4e7f450b175b5a0ab001af Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 2 Dec 2016 10:18:23 -0700 Subject: [PATCH 134/277] reduce heavy references --- app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 | 4 ++-- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 2 +- app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 2e126434ea5..9c27cb60615 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -83,8 +83,8 @@ }; return commitRef; }, - addTimeInterval(id, that) { - this.allTimeIntervals.push({ id, component: that }); + addTimeInterval(id, start) { + this.allTimeIntervals.push({ id, start }); }, }, template: ` diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 2dc1fd571f1..5f07617a602 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -48,7 +48,7 @@ }; const startIntervalLoops = () => { - this.allTimeIntervals.forEach(e => e.component.startInterval()); + this.allTimeIntervals.forEach(e => e.start()); }; window.onbeforeunload = function onClose() { diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index de6eec9bc5b..8aae777e9f1 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -17,7 +17,7 @@ this.currentTime = new Date(); }, 1000); - this.addTimeInterval(this.timeInterval, this); + this.addTimeInterval(this.timeInterval, this.startInterval); }, computed: { localTimeFinished() { From 18b559c9be5cf903569f01ae878ed71415b8a1a8 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 2 Dec 2016 11:42:26 -0700 Subject: [PATCH 135/277] deal with preventing DDOS --- .../vue_pipelines_index/store.js.es6 | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 5f07617a602..ca537a9c20d 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -43,7 +43,7 @@ resourceChecker(); goFetch(); - const removePipelineInterval = () => { + const removePipelineIntervals = () => { this.allTimeIntervals.forEach(e => clearInterval(e.id)); }; @@ -51,17 +51,18 @@ this.allTimeIntervals.forEach(e => e.start()); }; - window.onbeforeunload = function onClose() { - removePipelineInterval(); + const removeAll = () => { + removePipelineIntervals(); + window.removeEventListener('beforeunload', () => {}); + window.removeEventListener('focus', () => {}); + window.removeEventListener('blur', () => {}); + document.removeEventListener('page:fetch', () => {}); }; - window.onblur = function remove() { - removePipelineInterval(); - }; - - window.onfocus = function start() { - startIntervalLoops(); - }; + window.addEventListener('beforeunload', removePipelineIntervals); + window.addEventListener('focus', startIntervalLoops); + window.addEventListener('blur', removePipelineIntervals); + document.addEventListener('page:fetch', removeAll); } }; })(window.gl || (window.gl = {})); From 004a9c6bccb840b97616d25c0ff24a8de62ff496 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 2 Dec 2016 11:52:20 -0700 Subject: [PATCH 136/277] change to new prop for commit component - [ci skip] --- app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 9c27cb60615..656ac0a25f1 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -101,10 +101,10 @@ From d6061b739b7f27d9b7ee9f6ff1e7a635c7bac0b3 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 2 Dec 2016 15:05:01 -0700 Subject: [PATCH 137/277] much simpler time logic - clean up - [ci skip] --- .../vue_pipelines_index/pipelines.js.es6 | 6 ++-- .../vue_pipelines_index/store.js.es6 | 33 ++++++++++++------- .../vue_pipelines_index/time_ago.js.es6 | 15 ++------- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 656ac0a25f1..bba9a82e6ec 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -2,7 +2,6 @@ /* eslint-disable no-param-reassign, no-bitwise*/ ((gl) => { - const REALTIME = false; const SPREAD = '...'; const PREV = 'Prev'; const NEXT = 'Next'; @@ -24,7 +23,7 @@ data() { return { pipelines: [], - allTimeIntervals: [], + timeLoopInterval: '', intervalId: '', updatedAt: '', pagenum: 1, @@ -57,7 +56,7 @@ if (text === FIRST) this.pagenum = 1; window.history.pushState({}, null, `?p=${this.pagenum}`); - if (REALTIME) clearInterval(this.intervalId); + clearInterval(this.timeLoopInterval); this.pageRequest = true; this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope); }, @@ -113,7 +112,6 @@ diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index ca537a9c20d..1a473c51521 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -1,5 +1,5 @@ /* global gl, Flash */ -/* eslint-disable no-param-reassign */ +/* eslint-disable no-param-reassign, no-underscore-dangle */ ((gl) => { gl.PipelineStore = class { @@ -43,25 +43,36 @@ resourceChecker(); goFetch(); - const removePipelineIntervals = () => { - this.allTimeIntervals.forEach(e => clearInterval(e.id)); + const startTimeLoops = () => { + this.timeLoopInterval = setInterval(() => { + console.log('TIME LOOP'); + this.$children + .filter(e => e.$options._componentTag === 'time-ago') + .forEach(e => e.changeTime()); + }, 1000); + }; + + startTimeLoops(); + + const removeTimeIntervals = () => { + clearInterval(this.timeLoopInterval); }; const startIntervalLoops = () => { - this.allTimeIntervals.forEach(e => e.start()); + startTimeLoops(); }; const removeAll = () => { - removePipelineIntervals(); - window.removeEventListener('beforeunload', () => {}); - window.removeEventListener('focus', () => {}); - window.removeEventListener('blur', () => {}); - document.removeEventListener('page:fetch', () => {}); + removeTimeIntervals(); + window.removeEventListener('beforeunload', removeTimeIntervals); + window.removeEventListener('focus', startIntervalLoops); + window.removeEventListener('blur', removeTimeIntervals); + document.removeEventListener('page:fetch', removeTimeIntervals); }; - window.addEventListener('beforeunload', removePipelineIntervals); + window.addEventListener('beforeunload', removeTimeIntervals); window.addEventListener('focus', startIntervalLoops); - window.addEventListener('blur', removePipelineIntervals); + window.addEventListener('blur', removeTimeIntervals); document.addEventListener('page:fetch', removeAll); } }; diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index 8aae777e9f1..33a1744fa82 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -4,21 +4,12 @@ gl.VueTimeAgo = Vue.extend({ data() { return { - timeInterval: '', currentTime: new Date(), }; }, props: [ 'pipeline', - 'addTimeInterval', ], - created() { - this.timeInterval = setInterval(() => { - this.currentTime = new Date(); - }, 1000); - - this.addTimeInterval(this.timeInterval, this.startInterval); - }, computed: { localTimeFinished() { return gl.utils.formatDate(this.pipeline.details.finished_at); @@ -44,10 +35,8 @@ if (duration !== null) return duration; return false; }, - startInterval() { - this.timeInterval = setInterval(() => { - this.currentTime = new Date(); - }, 1000); + changeTime() { + this.currentTime = new Date(); }, }, template: ` From cd111ffabd36c44ec2fd9c52d5c35e7cdc936c67 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 2 Dec 2016 15:10:31 -0700 Subject: [PATCH 138/277] anon callbacks since intervals removed in function prior - [ci skip] --- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 1a473c51521..79bf56b5f69 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -45,7 +45,6 @@ const startTimeLoops = () => { this.timeLoopInterval = setInterval(() => { - console.log('TIME LOOP'); this.$children .filter(e => e.$options._componentTag === 'time-ago') .forEach(e => e.changeTime()); @@ -64,10 +63,10 @@ const removeAll = () => { removeTimeIntervals(); - window.removeEventListener('beforeunload', removeTimeIntervals); - window.removeEventListener('focus', startIntervalLoops); - window.removeEventListener('blur', removeTimeIntervals); - document.removeEventListener('page:fetch', removeTimeIntervals); + window.removeEventListener('beforeunload', () => {}); + window.removeEventListener('focus', () => {}); + window.removeEventListener('blur', () => {}); + document.removeEventListener('page:fetch', () => {}); }; window.addEventListener('beforeunload', removeTimeIntervals); From 312c504f618513a87d817cad74c059506fac7a49 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 5 Dec 2016 13:17:23 -0700 Subject: [PATCH 139/277] can render all tabs for pipelines - remove uneeded properties - [ci skip] --- .../vue_pipelines_index/index.js.es6 | 46 ++++++++++--------- .../vue_pipelines_index/pipelines.js.es6 | 30 +++++++++--- .../vue_pipelines_index/store.js.es6 | 5 +- app/views/projects/pipelines/index.html.haml | 13 ------ 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 2bd8d5d327f..1e4ee8875fb 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -17,30 +17,32 @@ //= require ./pipelines.js.es6 (() => { - const url = window.location.href; - if (~url.indexOf('scope') && !~url.indexOf('scope=pipelines')) return null; - const project = document.querySelector('.pipelines'); + const entry = document.querySelector('.vue-pipelines-index'); Vue.use(VueResource); - return new Vue({ - el: '.vue-pipelines-index', - data: { - scope: project.dataset.url, - store: new gl.PipelineStore(), - }, - components: { - 'vue-pipelines': gl.VuePipelines, - }, - template: ` -
    - - -
    - `, - }); + if (entry) { + return new Vue({ + el: entry, + data: { + scope: project.dataset.url, + store: new gl.PipelineStore(), + }, + components: { + 'vue-pipelines': gl.VuePipelines, + }, + template: ` +
    + + +
    + `, + }); + } + + return null; })(); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index bba9a82e6ec..1fd3a2a4d99 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -8,6 +8,16 @@ const FIRST = '<< First'; const LAST = 'Last >>'; + const getParameterByName = (name) => { + const url = window.location.href; + name = name.replace(/[[\]]/g, '\\$&'); + const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); + const results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, ' ')); + }; + gl.VuePipelines = Vue.extend({ components: { runningPipeline: gl.VueRunningPipeline, @@ -25,8 +35,8 @@ pipelines: [], timeLoopInterval: '', intervalId: '', - updatedAt: '', pagenum: 1, + apiScope: 'all', count: { all: 0, running_or_pending: 0, @@ -39,11 +49,19 @@ 'store', ], created() { - const url = window.location.toString(); - if (~url.indexOf('?') && !~url.indexOf('scope=pipelines')) { - this.pagenum = url.split('?')[1].split('=')[1]; - } - this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope); + const pagenum = getParameterByName('p'); + const scope = getParameterByName('scope'); + + if (pagenum) this.pagenum = pagenum; + if (scope) this.apiScope = scope; + + this.store.fetchDataLoop.call( + this, + Vue, + this.pagenum, + this.scope, + this.apiScope, + ); }, methods: { changepage(e, last) { diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 79bf56b5f69..93e4a640012 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -3,7 +3,7 @@ ((gl) => { gl.PipelineStore = class { - fetchDataLoop(Vue, pageNum, url) { + fetchDataLoop(Vue, pageNum, url, apiScope) { const setVueResources = () => { Vue.activeResources = 1; }; const resetVueResources = () => { Vue.activeResources = 0; }; const addToVueResources = () => { Vue.activeResources += 1; }; @@ -27,10 +27,9 @@ }; const goFetch = () => - this.$http.get(`${url}?page=${pageNum}`) + this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`) .then((response) => { const res = JSON.parse(response.body); - Vue.set(this, 'updatedAt', res.updated_at); Vue.set(this, 'pipelines', res.pipelines); Vue.set(this, 'count', res.count); updatePipelineNums(this.count); diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 4025a6859f2..10f1a88b65d 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -39,19 +39,6 @@ - if @pipelines.blank? %div .nothing-here-block No pipelines to show - - elsif @scope == 'branches' || @scope == 'tags' || @scope == 'running' - .table-holder - %table.table.ci-table - %thead - %th Status - %th Pipeline - %th Commit - %th Stages - %th - %th.hidden-xs - - = render @pipelines, commit_sha: true, stage: true, allow_retry: true - = paginate @pipelines, theme: 'gitlab' - else .commit-icon-svg.hidden = custom_icon("icon_commit") From a0055435438e956b09bb92b6bbf618fbf005e46c Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 5 Dec 2016 15:52:02 -0700 Subject: [PATCH 140/277] tried css only approach didn't work for now - adding all icons to stages --- app/assets/javascripts/vue_icons/index.js.es6 | 55 +++---------------- .../vue_pipelines_index/stage.js.es6 | 4 ++ 2 files changed, 13 insertions(+), 46 deletions(-) diff --git a/app/assets/javascripts/vue_icons/index.js.es6 b/app/assets/javascripts/vue_icons/index.js.es6 index 84a19348862..6ddf8592cbe 100644 --- a/app/assets/javascripts/vue_icons/index.js.es6 +++ b/app/assets/javascripts/vue_icons/index.js.es6 @@ -5,86 +5,49 @@ ((gl) => { gl.VueRunningIcon = Vue.extend({ template: ` - - - - - - + `, }); gl.VuePendingIcon = Vue.extend({ template: ` - - - - - - + `, }); gl.VueSuccessIcon = Vue.extend({ template: ` - - - - - - + `, }); gl.VueFailedIcon = Vue.extend({ template: ` - - - - - - + `, }); gl.VueCreatedIcon = Vue.extend({ template: ` - - - - + `, }); gl.VueCanceledIcon = Vue.extend({ template: ` - - - - - - + `, }); gl.VueSkippedIcon = Vue.extend({ template: ` - - - - - + `, }); - gl.VueUnstableIcon = Vue.extend({ + gl.VueWarningIcon = Vue.extend({ template: ` - - - - - - `, }); })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index e5f9178a796..38b993d4b51 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -10,6 +10,8 @@ 'success-icon': gl.VueSuccessIcon, 'created-icon': gl.VueCreatedIcon, 'canceled-icon': gl.VueCanceledIcon, + 'warning-icon': gl.VueWarningIcon, + 'skipped-icon': gl.VueSkippedIcon, }, props: ['stage'], computed: { @@ -32,6 +34,8 @@ + + `, }); From cc198c1f94520e756ac08ddb4c8bef6a75962cda Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 5 Dec 2016 16:01:49 -0700 Subject: [PATCH 141/277] change unstable to warning [ci skip] --- .../{unstable.js.es6 => warning.js.es6} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename app/assets/javascripts/vue_pipelines_status/{unstable.js.es6 => warning.js.es6} (59%) diff --git a/app/assets/javascripts/vue_pipelines_status/unstable.js.es6 b/app/assets/javascripts/vue_pipelines_status/warning.js.es6 similarity index 59% rename from app/assets/javascripts/vue_pipelines_status/unstable.js.es6 rename to app/assets/javascripts/vue_pipelines_status/warning.js.es6 index 27fd29b9d80..1a8b70d82f1 100644 --- a/app/assets/javascripts/vue_pipelines_status/unstable.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/warning.js.es6 @@ -2,9 +2,9 @@ /* eslint-disable no-param-reassign */ ((gl) => { - gl.VueUnstableScope = Vue.extend({ + gl.VueWarningScope = Vue.extend({ components: { - 'vue-unstable-icon': gl.VueUnstableIcon, + 'vue-warning-icon': gl.VueWarningIcon, }, props: [ 'pipeline', @@ -12,9 +12,9 @@ template: ` - - -  unstable + + +  warning From bdd6b841b0369fcadbcd375905c37476415fa100 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 5 Dec 2016 16:20:34 -0700 Subject: [PATCH 142/277] require correct file [ci skip] --- app/assets/javascripts/vue_pipelines_status/index.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_status/index.js.es6 b/app/assets/javascripts/vue_pipelines_status/index.js.es6 index 712ac2644e5..c48b4adb661 100644 --- a/app/assets/javascripts/vue_pipelines_status/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_status/index.js.es6 @@ -3,5 +3,5 @@ //= require ./running.js.es6 //= require ./canceled.js.es6 //= require ./status.js.es6 -//= require ./unstable.js.es6 +//= require ./warning.js.es6 //= require ./skipped.js.es6 From a62e5219a56e24cefef09b4466cbe42882098220 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 6 Dec 2016 13:20:46 +0100 Subject: [PATCH 143/277] Fix exception related to pipeline stages in view --- app/views/projects/pipelines/_with_tabs.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml index 3464e155a1b..ac139797a89 100644 --- a/app/views/projects/pipelines/_with_tabs.html.haml +++ b/app/views/projects/pipelines/_with_tabs.html.haml @@ -50,5 +50,5 @@ - if pipeline.project.build_coverage_enabled? %th Coverage %th - - pipeline.statuses.relevant.stages.each do |stage| + - pipeline.stages.each do |stage| = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage) From 62b3b28b0b1522ff57e76b1a68fb2c77e0930abe Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 6 Dec 2016 09:03:45 -0700 Subject: [PATCH 144/277] remove all files related to Vue SVG rendering - use dynamic svg loading --- app/assets/javascripts/vue_icons/index.js.es6 | 53 ------------------- .../vue_pipelines_index/index.js.es6 | 1 + .../vue_pipelines_index/stage.js.es6 | 25 +++------ .../vue_pipelines_index/status.js.es6 | 34 ++++++++++++ .../vue_pipelines_status/canceled.js.es6 | 23 -------- .../vue_pipelines_status/created.js.es6 | 23 -------- .../vue_pipelines_status/failed.js.es6 | 23 -------- .../vue_pipelines_status/index.js.es6 | 7 --- .../vue_pipelines_status/pending.js.es6 | 23 -------- .../vue_pipelines_status/running.js.es6 | 23 -------- .../vue_pipelines_status/skipped.js.es6 | 23 -------- .../vue_pipelines_status/status.js.es6 | 52 ------------------ .../vue_pipelines_status/warning.js.es6 | 23 -------- app/views/projects/pipelines/index.html.haml | 18 ++++++- config/application.rb | 2 - 15 files changed, 58 insertions(+), 295 deletions(-) delete mode 100644 app/assets/javascripts/vue_icons/index.js.es6 create mode 100644 app/assets/javascripts/vue_pipelines_index/status.js.es6 delete mode 100644 app/assets/javascripts/vue_pipelines_status/canceled.js.es6 delete mode 100644 app/assets/javascripts/vue_pipelines_status/created.js.es6 delete mode 100644 app/assets/javascripts/vue_pipelines_status/failed.js.es6 delete mode 100644 app/assets/javascripts/vue_pipelines_status/index.js.es6 delete mode 100644 app/assets/javascripts/vue_pipelines_status/pending.js.es6 delete mode 100644 app/assets/javascripts/vue_pipelines_status/running.js.es6 delete mode 100644 app/assets/javascripts/vue_pipelines_status/skipped.js.es6 delete mode 100644 app/assets/javascripts/vue_pipelines_status/status.js.es6 delete mode 100644 app/assets/javascripts/vue_pipelines_status/warning.js.es6 diff --git a/app/assets/javascripts/vue_icons/index.js.es6 b/app/assets/javascripts/vue_icons/index.js.es6 deleted file mode 100644 index 6ddf8592cbe..00000000000 --- a/app/assets/javascripts/vue_icons/index.js.es6 +++ /dev/null @@ -1,53 +0,0 @@ -//= require vue -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueRunningIcon = Vue.extend({ - template: ` - - `, - }); - - gl.VuePendingIcon = Vue.extend({ - template: ` - - `, - }); - - gl.VueSuccessIcon = Vue.extend({ - template: ` - - `, - }); - - gl.VueFailedIcon = Vue.extend({ - template: ` - - `, - }); - - gl.VueCreatedIcon = Vue.extend({ - template: ` - - `, - }); - - gl.VueCanceledIcon = Vue.extend({ - template: ` - - `, - }); - - gl.VueSkippedIcon = Vue.extend({ - template: ` - - `, - }); - - gl.VueWarningIcon = Vue.extend({ - template: ` - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 1e4ee8875fb..c454d8672b3 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -6,6 +6,7 @@ //= require vue-resource //= require ./object_assign.js.es6 +//= require ./status.js.es6 //= require ./store.js.es6 //= require ./pipeline_url.js.es6 //= require ./pipeline_head.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 38b993d4b51..d8e3f1c3dcd 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -3,16 +3,6 @@ ((gl) => { gl.VueStage = Vue.extend({ - components: { - 'running-icon': gl.VueRunningIcon, - 'pending-icon': gl.VuePendingIcon, - 'failed-icon': gl.VueFailedIcon, - 'success-icon': gl.VueSuccessIcon, - 'created-icon': gl.VueCreatedIcon, - 'canceled-icon': gl.VueCanceledIcon, - 'warning-icon': gl.VueWarningIcon, - 'skipped-icon': gl.VueSkippedIcon, - }, props: ['stage'], computed: { buildStatus() { @@ -21,21 +11,20 @@ tooltip() { return `has-tooltip ci-status-icon-${this.stage.status}`; }, + svg() { + return document + .querySelector( + `.${this.stage.status}-icon-svg.hidden`, + ).innerHTML; + }, }, template: ` - - - - - - - - `, }); diff --git a/app/assets/javascripts/vue_pipelines_index/status.js.es6 b/app/assets/javascripts/vue_pipelines_index/status.js.es6 new file mode 100644 index 00000000000..bbdc357035f --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/status.js.es6 @@ -0,0 +1,34 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign */ + +((gl) => { + gl.VueStatusScope = Vue.extend({ + props: [ + 'pipeline', + ], + computed: { + cssClasses() { + const cssObject = {}; + cssObject['ci-status'] = true; + cssObject[`ci-${this.pipeline.details.status}`] = true; + return cssObject; + }, + svg() { + return document + .querySelector( + `.${this.pipeline.details.status}-icon-svg.hidden`, + ).innerHTML; + }, + }, + template: ` + + + + +  {{pipeline.details.status}} + + + + `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/canceled.js.es6 b/app/assets/javascripts/vue_pipelines_status/canceled.js.es6 deleted file mode 100644 index 20ca8ad5ebb..00000000000 --- a/app/assets/javascripts/vue_pipelines_status/canceled.js.es6 +++ /dev/null @@ -1,23 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueCanceledScope = Vue.extend({ - components: { - 'vue-canceled-icon': gl.VueCanceledIcon, - }, - props: [ - 'pipeline', - ], - template: ` - - - - -  canceled - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/created.js.es6 b/app/assets/javascripts/vue_pipelines_status/created.js.es6 deleted file mode 100644 index b74566b7341..00000000000 --- a/app/assets/javascripts/vue_pipelines_status/created.js.es6 +++ /dev/null @@ -1,23 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueCreatedScope = Vue.extend({ - components: { - 'vue-created-icon': gl.VueCreatedIcon, - }, - props: [ - 'pipeline', - ], - template: ` - - - - -  failed - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/failed.js.es6 b/app/assets/javascripts/vue_pipelines_status/failed.js.es6 deleted file mode 100644 index 0988e2c04ab..00000000000 --- a/app/assets/javascripts/vue_pipelines_status/failed.js.es6 +++ /dev/null @@ -1,23 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueFailedScope = Vue.extend({ - components: { - 'vue-failed-icon': gl.VueFailedIcon, - }, - props: [ - 'pipeline', - ], - template: ` - - - - -  failed - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/index.js.es6 b/app/assets/javascripts/vue_pipelines_status/index.js.es6 deleted file mode 100644 index c48b4adb661..00000000000 --- a/app/assets/javascripts/vue_pipelines_status/index.js.es6 +++ /dev/null @@ -1,7 +0,0 @@ -//= require ./pending.js.es6 -//= require ./failed.js.es6 -//= require ./running.js.es6 -//= require ./canceled.js.es6 -//= require ./status.js.es6 -//= require ./warning.js.es6 -//= require ./skipped.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_status/pending.js.es6 b/app/assets/javascripts/vue_pipelines_status/pending.js.es6 deleted file mode 100644 index 579c6f71a2e..00000000000 --- a/app/assets/javascripts/vue_pipelines_status/pending.js.es6 +++ /dev/null @@ -1,23 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VuePendingScope = Vue.extend({ - components: { - 'vue-pending-icon': gl.VuePendingIcon, - }, - props: [ - 'pipeline', - ], - template: ` - - - - -  pending - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/running.js.es6 b/app/assets/javascripts/vue_pipelines_status/running.js.es6 deleted file mode 100644 index 57274422b15..00000000000 --- a/app/assets/javascripts/vue_pipelines_status/running.js.es6 +++ /dev/null @@ -1,23 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueRunningScope = Vue.extend({ - components: { - 'vue-running-icon': gl.VueRunningIcon, - }, - props: [ - 'pipeline', - ], - template: ` - - - - -  running - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/skipped.js.es6 b/app/assets/javascripts/vue_pipelines_status/skipped.js.es6 deleted file mode 100644 index da904010607..00000000000 --- a/app/assets/javascripts/vue_pipelines_status/skipped.js.es6 +++ /dev/null @@ -1,23 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueSkippedScope = Vue.extend({ - components: { - 'vue-skipped-icon': gl.VueSkippedIcon, - }, - props: [ - 'pipeline', - ], - template: ` - - - - -  skipped - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/status.js.es6 b/app/assets/javascripts/vue_pipelines_status/status.js.es6 deleted file mode 100644 index dfef19e21da..00000000000 --- a/app/assets/javascripts/vue_pipelines_status/status.js.es6 +++ /dev/null @@ -1,52 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueStatusScope = Vue.extend({ - components: { - 'vue-running-scope': gl.VueRunningScope, - 'vue-pending-scope': gl.VuePendingScope, - 'vue-failed-scope': gl.VueFailedScope, - 'vue-created-scope': gl.VueCreatedScope, - 'vue-canceled-scope': gl.VueCanceledScope, - 'vue-unstable-scope': gl.VueUnstableScope, - }, - props: [ - 'pipeline', - ], - template: ` - - - - - - - - - - - - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_status/warning.js.es6 b/app/assets/javascripts/vue_pipelines_status/warning.js.es6 deleted file mode 100644 index 1a8b70d82f1..00000000000 --- a/app/assets/javascripts/vue_pipelines_status/warning.js.es6 +++ /dev/null @@ -1,23 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueWarningScope = Vue.extend({ - components: { - 'vue-warning-icon': gl.VueWarningIcon, - }, - props: [ - 'pipeline', - ], - template: ` - - - - -  warning - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 10f1a88b65d..132c21fea00 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -42,9 +42,23 @@ - else .commit-icon-svg.hidden = custom_icon("icon_commit") + .canceled-icon-svg.hidden + = custom_icon("icon_status_canceled") + .running-icon-svg.hidden + = custom_icon("icon_status_running") + .skipped-icon-svg.hidden + = custom_icon("icon_status_skipped") + .created-icon-svg.hidden + = custom_icon("icon_status_created") + .pending-icon-svg.hidden + = custom_icon("icon_status_pending") + .success-icon-svg.hidden + = custom_icon("icon_status_success") + .failed-icon-svg.hidden + = custom_icon("icon_status_failed") + .warning-icon-svg.hidden + = custom_icon("icon_status_warning") .vue-pipelines-index -= page_specific_javascript_tag('vue_icons/index.js') = page_specific_javascript_tag('vue_pagination/index.js') -= page_specific_javascript_tag('vue_pipelines_status/index.js') = page_specific_javascript_tag('vue_pipelines_index/index.js') diff --git a/config/application.rb b/config/application.rb index 64a542a9774..e2babbe6707 100644 --- a/config/application.rb +++ b/config/application.rb @@ -101,9 +101,7 @@ module Gitlab config.assets.precompile << "lib/*.js" config.assets.precompile << "u2f.js" config.assets.precompile << "vue_pipelines_index/index.js" - config.assets.precompile << "vue_pipelines_status/index.js" config.assets.precompile << "vue_pagination/index.js" - config.assets.precompile << "vue_icons/index.js" # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' From e9e498fdc75e257bf97ba6e02c9196fe662d49e1 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 6 Dec 2016 13:08:37 -0700 Subject: [PATCH 145/277] no need for running icon comp --- .../javascripts/vue_pipelines_index/pipeline_url.js.es6 | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 index d9602b4209d..99ecdf3fed0 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 @@ -3,9 +3,6 @@ ((gl) => { gl.VuePipelineUrl = Vue.extend({ - components: { - 'vue-running-icon': gl.VueRunningIcon, - }, props: [ 'pipeline', ], @@ -56,7 +53,7 @@ yaml invalid stuck From 840ee74ef65eba0d9704b6a8ed4535b38c8d2675 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 6 Dec 2016 22:33:27 -0700 Subject: [PATCH 146/277] remove unused components and polyfill - use interceptor for vue resource --- .../vue_pipelines_index/branch_commit.js.es6 | 64 ------------------- .../vue_pipelines_index/index.js.es6 | 3 +- .../vue_pipelines_index/interceptor.js.es6 | 8 +++ .../vue_pipelines_index/object_assign.js.es6 | 30 --------- .../vue_pipelines_index/store.js.es6 | 17 ----- 5 files changed, 9 insertions(+), 113 deletions(-) delete mode 100644 app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 create mode 100644 app/assets/javascripts/vue_pipelines_index/interceptor.js.es6 delete mode 100644 app/assets/javascripts/vue_pipelines_index/object_assign.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 b/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 deleted file mode 100644 index 6bebbd221fe..00000000000 --- a/app/assets/javascripts/vue_pipelines_index/branch_commit.js.es6 +++ /dev/null @@ -1,64 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueBranchCommit = Vue.extend({ - props: ['pipeline'], - computed: { - mailto() { - return `mailto:${this.pipeline.commit.author_email}`; - }, - alt() { - return `${this.pipeline.commit.author_name}'s avatar`; - }, - avatarUrl() { - const author = this.pipeline.commit.author; - if (author) return author.avatar_url; - return this.pipeline.commit.author_gravatar_url; - }, - }, - template: ` - -
    - -
    - - {{pipeline.ref.name}} - -
    - - - -
    - - {{pipeline.commit.short_id}} - -

    - - - - - - {{pipeline.commit.title}} - -

    - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index c454d8672b3..25f5c796a60 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -5,7 +5,7 @@ //= require vue-resource -//= require ./object_assign.js.es6 +//= require ./interceptor.js.es6 //= require ./status.js.es6 //= require ./store.js.es6 //= require ./pipeline_url.js.es6 @@ -13,7 +13,6 @@ //= require ./stage.js.es6 //= require ./stages.js.es6 //= require ./pipeline_actions.js.es6 -//= require ./branch_commit.js.es6 //= require ./time_ago.js.es6 //= require ./pipelines.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/interceptor.js.es6 b/app/assets/javascripts/vue_pipelines_index/interceptor.js.es6 new file mode 100644 index 00000000000..80f137ca12e --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/interceptor.js.es6 @@ -0,0 +1,8 @@ +/* eslint-disable */ +Vue.http.interceptors.push((request, next) => { + Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; + + next(function (response) { + Vue.activeResources--; + }); +}); diff --git a/app/assets/javascripts/vue_pipelines_index/object_assign.js.es6 b/app/assets/javascripts/vue_pipelines_index/object_assign.js.es6 deleted file mode 100644 index 05a5a7da1c3..00000000000 --- a/app/assets/javascripts/vue_pipelines_index/object_assign.js.es6 +++ /dev/null @@ -1,30 +0,0 @@ -/* eslint-disable */ - -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ -(() => { - if (typeof Object.assign != 'function') { - (function () { - Object.assign = function (target) { - 'use strict'; - // We must check against these specific cases. - if (target === undefined || target === null) { - throw new TypeError('Cannot convert undefined or null to object'); - } - - var output = Object(target); - for (var index = 1; index < arguments.length; index++) { - var source = arguments[index]; - if (source !== undefined && source !== null) { - for (var nextKey in source) { - if (source.hasOwnProperty(nextKey)) { - output[nextKey] = source[nextKey]; - } - } - } - } - return output; - }; - })(); - } -})(); diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 93e4a640012..49b77454314 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -4,13 +4,6 @@ ((gl) => { gl.PipelineStore = class { fetchDataLoop(Vue, pageNum, url, apiScope) { - const setVueResources = () => { Vue.activeResources = 1; }; - const resetVueResources = () => { Vue.activeResources = 0; }; - const addToVueResources = () => { Vue.activeResources += 1; }; - const subtractFromVueResources = () => { Vue.activeResources -= 1; }; - - resetVueResources(); - const updatePipelineNums = (count) => { const { all } = count; const running = count.running_or_pending; @@ -18,14 +11,6 @@ document.querySelector('.js-running-count').innerHTML = running; }; - const resourceChecker = () => { - if (Vue.activeResources === 0) { - setVueResources(); - } else { - addToVueResources(); - } - }; - const goFetch = () => this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`) .then((response) => { @@ -34,12 +19,10 @@ Vue.set(this, 'count', res.count); updatePipelineNums(this.count); this.pageRequest = false; - subtractFromVueResources(); }, () => new Flash( 'Something went wrong on our end.', )); - resourceChecker(); goFetch(); const startTimeLoops = () => { From 7870614fb94ce3eb1531e77dc824f4832a808571 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 7 Dec 2016 13:15:01 +0100 Subject: [PATCH 147/277] Extract API pagination code to a separete module --- lib/api/helpers.rb | 39 +----------------------------- lib/api/helpers/pagination.rb | 45 +++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 38 deletions(-) create mode 100644 lib/api/helpers/pagination.rb diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 7f94ede7940..164aea613e4 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -1,6 +1,7 @@ module API module Helpers include Gitlab::Utils + include Helpers::Pagination PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN" PRIVATE_TOKEN_PARAM = :private_token @@ -131,12 +132,6 @@ module API IssuesFinder.new(current_user, project_id: user_project.id).find(id) end - def paginate(relation) - relation.page(params[:page]).per(params[:per_page].to_i).tap do |data| - add_pagination_headers(data) - end - end - def authenticate! unauthorized! unless current_user end @@ -367,38 +362,6 @@ module API private - def add_pagination_headers(paginated_data) - header 'X-Total', paginated_data.total_count.to_s - header 'X-Total-Pages', paginated_data.total_pages.to_s - header 'X-Per-Page', paginated_data.limit_value.to_s - header 'X-Page', paginated_data.current_page.to_s - header 'X-Next-Page', paginated_data.next_page.to_s - header 'X-Prev-Page', paginated_data.prev_page.to_s - header 'Link', pagination_links(paginated_data) - end - - def pagination_links(paginated_data) - request_url = request.url.split('?').first - request_params = params.clone - request_params[:per_page] = paginated_data.limit_value - - links = [] - - request_params[:page] = paginated_data.current_page - 1 - links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") unless paginated_data.first_page? - - request_params[:page] = paginated_data.current_page + 1 - links << %(<#{request_url}?#{request_params.to_query}>; rel="next") unless paginated_data.last_page? - - request_params[:page] = 1 - links << %(<#{request_url}?#{request_params.to_query}>; rel="first") - - request_params[:page] = paginated_data.total_pages - links << %(<#{request_url}?#{request_params.to_query}>; rel="last") - - links.join(', ') - end - def secret_token Gitlab::Shell.secret_token end diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb new file mode 100644 index 00000000000..2199eea7e5f --- /dev/null +++ b/lib/api/helpers/pagination.rb @@ -0,0 +1,45 @@ +module API + module Helpers + module Pagination + def paginate(relation) + relation.page(params[:page]).per(params[:per_page].to_i).tap do |data| + add_pagination_headers(data) + end + end + + private + + def add_pagination_headers(paginated_data) + header 'X-Total', paginated_data.total_count.to_s + header 'X-Total-Pages', paginated_data.total_pages.to_s + header 'X-Per-Page', paginated_data.limit_value.to_s + header 'X-Page', paginated_data.current_page.to_s + header 'X-Next-Page', paginated_data.next_page.to_s + header 'X-Prev-Page', paginated_data.prev_page.to_s + header 'Link', pagination_links(paginated_data) + end + + def pagination_links(paginated_data) + request_url = request.url.split('?').first + request_params = params.clone + request_params[:per_page] = paginated_data.limit_value + + links = [] + + request_params[:page] = paginated_data.current_page - 1 + links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") unless paginated_data.first_page? + + request_params[:page] = paginated_data.current_page + 1 + links << %(<#{request_url}?#{request_params.to_query}>; rel="next") unless paginated_data.last_page? + + request_params[:page] = 1 + links << %(<#{request_url}?#{request_params.to_query}>; rel="first") + + request_params[:page] = paginated_data.total_pages + links << %(<#{request_url}?#{request_params.to_query}>; rel="last") + + links.join(', ') + end + end + end +end From f55fcef3dbadf5f3003e0a51633e30e05b47c51d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 7 Dec 2016 15:22:01 +0100 Subject: [PATCH 148/277] Add prototype of pipelines serializer with pagination --- .../projects/pipelines_controller.rb | 1 + app/serializers/pipeline_serializer.rb | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 4edafaa5950..7179f3b22ea 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -18,6 +18,7 @@ class Projects::PipelinesController < Projects::ApplicationController render json: { pipelines: PipelineSerializer .new(project: @project, user: @current_user) + .with_pagination(request, response) .represent(@pipelines), updated_at: Time.now.utc, count: { diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index f7abbec7d45..c4ca5c40dba 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -1,3 +1,37 @@ class PipelineSerializer < BaseSerializer entity PipelineEntity + include API::Helpers::Pagination + Struct.new('Pagination', :request, :response) + + def with_pagination(request, response) + tap { @pagination = Struct::Pagination.new(request, response) } + end + + def paginate? + defined?(@pagination) + end + + def represent(resource, opts = {}) + if paginate? + super(paginate(resource), opts) + else + super(resource, opts) + end + end + + private + + # Methods needed by `API::Helpers::Pagination` + # + def params + @pagination.request.query_parameters + end + + def request + @pagination.request + end + + def header(header, value) + @pagination.response.headers[header] = value + end end From e4efb135e810046b1f4283db4dfe1b386ef15bad Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 7 Dec 2016 16:46:37 +0100 Subject: [PATCH 149/277] Add status entity prototype and expose in pipeline --- app/serializers/pipeline_entity.rb | 2 +- app/serializers/status_entity.rb | 8 ++++++++ spec/serializers/status_entity_spec.rb | 24 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 app/serializers/status_entity.rb create mode 100644 spec/serializers/status_entity_spec.rb diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index cf99e628d5f..a07abb4bec0 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -12,7 +12,7 @@ class PipelineEntity < Grape::Entity end expose :details do - expose :status + expose :detailed_status, as: :status, using: StatusEntity expose :duration expose :finished_at expose :stages_with_statuses, as: :stages, using: PipelineStageEntity diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb new file mode 100644 index 00000000000..ae14dd57f61 --- /dev/null +++ b/app/serializers/status_entity.rb @@ -0,0 +1,8 @@ +class StatusEntity < Grape::Entity + include RequestAwareEntity + + expose :icon, :text, :label, :title + + expose :has_details?, as: :has_details + expose :details_path +end diff --git a/spec/serializers/status_entity_spec.rb b/spec/serializers/status_entity_spec.rb new file mode 100644 index 00000000000..5afa1c25a42 --- /dev/null +++ b/spec/serializers/status_entity_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe StatusEntity do + let(:entity) do + described_class.new(status) + end + + let(:status) do # TODO, add statuses factory + Gitlab::Ci::Status::Success.new(double('object')) + end + + before do + allow(status).to receive(:has_details?).and_return(true) + allow(status).to receive(:details_path).and_return('some/path') + end + + subject { entity.as_json } + + it 'contains status details' do + expect(subject).to include :text, :icon, :label, :title + expect(subject).to include :has_details + expect(subject).to include :details_path + end +end From e86c5570fbfd1d5276eb67b16303599fc747a7a0 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 7 Dec 2016 09:51:51 -0700 Subject: [PATCH 150/277] fix status for svg's on status column --- app/assets/javascripts/vue_pipelines_index/status.js.es6 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/status.js.es6 b/app/assets/javascripts/vue_pipelines_index/status.js.es6 index bbdc357035f..ed65841e6a7 100644 --- a/app/assets/javascripts/vue_pipelines_index/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/status.js.es6 @@ -10,13 +10,13 @@ cssClasses() { const cssObject = {}; cssObject['ci-status'] = true; - cssObject[`ci-${this.pipeline.details.status}`] = true; + cssObject[`ci-${this.pipeline.details.status.text}`] = true; return cssObject; }, svg() { return document .querySelector( - `.${this.pipeline.details.status}-icon-svg.hidden`, + `.${this.pipeline.details.status.text}-icon-svg.hidden`, ).innerHTML; }, }, @@ -25,7 +25,7 @@ -  {{pipeline.details.status}} +  {{pipeline.details.status.text}} From c9b6392452f586eafc08605b71f6c696a5ca12d7 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 7 Dec 2016 10:05:39 -0700 Subject: [PATCH 151/277] conform to status object for svg icon names --- .../javascripts/vue_pipelines_index/stage.js.es6 | 2 +- .../vue_pipelines_index/status.js.es6 | 6 ++---- app/views/projects/pipelines/index.html.haml | 16 ++++++++-------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index d8e3f1c3dcd..b313317b0bb 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -14,7 +14,7 @@ svg() { return document .querySelector( - `.${this.stage.status}-icon-svg.hidden`, + `.icon_status_${this.stage.status}`, ).innerHTML; }, }, diff --git a/app/assets/javascripts/vue_pipelines_index/status.js.es6 b/app/assets/javascripts/vue_pipelines_index/status.js.es6 index ed65841e6a7..2f362d0c69e 100644 --- a/app/assets/javascripts/vue_pipelines_index/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/status.js.es6 @@ -15,14 +15,12 @@ }, svg() { return document - .querySelector( - `.${this.pipeline.details.status.text}-icon-svg.hidden`, - ).innerHTML; + .querySelector(`.${this.pipeline.details.status.icon}`).innerHTML; }, }, template: ` - +  {{pipeline.details.status.text}} diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 132c21fea00..fd1baa6195b 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -42,21 +42,21 @@ - else .commit-icon-svg.hidden = custom_icon("icon_commit") - .canceled-icon-svg.hidden + .icon_status_canceled.hidden = custom_icon("icon_status_canceled") - .running-icon-svg.hidden + .icon_status_running.hidden = custom_icon("icon_status_running") - .skipped-icon-svg.hidden + .icon_status_skipped.hidden = custom_icon("icon_status_skipped") - .created-icon-svg.hidden + .icon_status_created.hidden = custom_icon("icon_status_created") - .pending-icon-svg.hidden + .icon_status_pending.hidden = custom_icon("icon_status_pending") - .success-icon-svg.hidden + .icon_status_success.hidden = custom_icon("icon_status_success") - .failed-icon-svg.hidden + .icon_status_failed.hidden = custom_icon("icon_status_failed") - .warning-icon-svg.hidden + .icon_status_warning.hidden = custom_icon("icon_status_warning") .vue-pipelines-index From 64b66c9a8327b9309f6bad6402a36a795f957fd1 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 7 Dec 2016 13:35:26 -0700 Subject: [PATCH 152/277] use API values for pagination --- .../javascripts/vue_pagination/index.js.es6 | 31 ++++++++++--------- .../vue_pipelines_index/pipelines.js.es6 | 14 +++++---- .../vue_pipelines_index/store.js.es6 | 15 +++++++++ 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 4f83671e83d..961f7101fb1 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -2,7 +2,6 @@ /* eslint-disable no-param-reassign, no-plusplus */ ((gl) => { - const PAGINATION_SIZE = 30; const PAGINATION_UI_BUTTON_LIMIT = 4; const SPREAD = '...'; const PREV = 'Prev'; @@ -13,24 +12,26 @@ gl.VueGlPagination = Vue.extend({ props: [ 'changepage', - 'count', - 'pagenum', + 'pageInfo', ], computed: { - last() { - return Math.ceil(+this.count / PAGINATION_SIZE); + prev() { + return this.pageInfo.previousPage; + }, + next() { + return this.pageInfo.nextPage; }, getItems() { - const total = +this.last; - const page = +this.pagenum; + const total = this.pageInfo.totalPages; + const page = this.pageInfo.page; const items = []; - if (page > 1) items.push({ title: FIRST, where: 1 }); + if (page > 1) items.push({ title: FIRST }); if (page > 1) { - items.push({ title: PREV, where: page - 1 }); + items.push({ title: PREV }); } else { - items.push({ title: PREV, where: page - 1, disabled: true }); + items.push({ title: PREV, disabled: true }); } if (page > 6) items.push({ title: SPREAD, separator: true }); @@ -40,7 +41,7 @@ for (let i = start; i <= end; i++) { const isActive = i === page; - items.push({ title: i, active: isActive, where: i }); + items.push({ title: i, active: isActive }); } if (total - page > PAGINATION_UI_BUTTON_LIMIT) { @@ -48,12 +49,12 @@ } if (page === total) { - items.push({ title: NEXT, where: page + 1, disabled: true }); + items.push({ title: NEXT, disabled: true }); } else if (total - page >= 1) { - items.push({ title: NEXT, where: page + 1 }); + items.push({ title: NEXT }); } - if (total - page >= 1) items.push({ title: LAST, where: total }); + if (total - page >= 1) items.push({ title: LAST }); return items; }, @@ -69,7 +70,7 @@ }' > {{item.title}} diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 1fd3a2a4d99..3f35c07b628 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -35,8 +35,8 @@ pipelines: [], timeLoopInterval: '', intervalId: '', - pagenum: 1, apiScope: 'all', + pageInfo: {}, count: { all: 0, running_or_pending: 0, @@ -64,13 +64,14 @@ ); }, methods: { - changepage(e, last) { + changepage(e) { const text = e.target.innerText; + const { totalPages, nextPage, previousPage } = this.pageInfo; if (text === SPREAD) return; if (/^-?[\d.]+(?:e-?\d+)?$/.test(text)) this.pagenum = +text; - if (text === LAST) this.pagenum = last; - if (text === NEXT) this.pagenum = +this.pagenum + 1; - if (text === PREV) this.pagenum = +this.pagenum - 1; + if (text === LAST) this.pagenum = totalPages; + if (text === NEXT) this.pagenum = nextPage; + if (text === PREV) this.pagenum = previousPage; if (text === FIRST) this.pagenum = 1; window.history.pushState({}, null, `?p=${this.pagenum}`); @@ -141,10 +142,11 @@
    diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 49b77454314..7d8316d6d20 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -2,6 +2,17 @@ /* eslint-disable no-param-reassign, no-underscore-dangle */ ((gl) => { + const pageValues = (headers) => { + const values = {}; + values.perPage = +headers['X-Per-Page']; + values.page = +headers['X-Page']; + values.total = +headers['X-Total']; + values.totalPages = +headers['X-Total-Pages']; + values.nextPage = +headers['X-Next-Page']; + values.previousPage = +headers['X-Prev-Page']; + return values; + }; + gl.PipelineStore = class { fetchDataLoop(Vue, pageNum, url, apiScope) { const updatePipelineNums = (count) => { @@ -14,9 +25,13 @@ const goFetch = () => this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`) .then((response) => { + const pageInfo = pageValues(response.headers); + Vue.set(this, 'pageInfo', pageInfo); + const res = JSON.parse(response.body); Vue.set(this, 'pipelines', res.pipelines); Vue.set(this, 'count', res.count); + updatePipelineNums(this.count); this.pageRequest = false; }, () => new Flash( From 813be933969956acd1ce401dce90a4c552a6f9d3 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 7 Dec 2016 15:20:30 -0700 Subject: [PATCH 153/277] default to page 1 if url is clean --- app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 3f35c07b628..9f7e71dbee9 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -37,6 +37,7 @@ intervalId: '', apiScope: 'all', pageInfo: {}, + pagenum: 1, count: { all: 0, running_or_pending: 0, From 20f74fe7ca6388a2dcc72231e84ed2945d6c4444 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 8 Dec 2016 10:03:53 -0700 Subject: [PATCH 154/277] make Vue render with null commit object - handle scope and page in different tabs --- .../vue_pipelines_index/pipelines.js.es6 | 38 +++++++++++++++---- .../projects/pipelines/pipelines_spec.rb | 22 +++-------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 9f7e71dbee9..090ceb67593 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -66,6 +66,8 @@ }, methods: { changepage(e) { + const scope = getParameterByName('scope'); + if (scope) this.apiScope = scope; const text = e.target.innerText; const { totalPages, nextPage, previousPage } = this.pageInfo; if (text === SPREAD) return; @@ -74,14 +76,21 @@ if (text === NEXT) this.pagenum = nextPage; if (text === PREV) this.pagenum = previousPage; if (text === FIRST) this.pagenum = 1; - - window.history.pushState({}, null, `?p=${this.pagenum}`); + window.history.pushState({}, null, `?scope=${this.apiScope}&p=${this.pagenum}`); clearInterval(this.timeLoopInterval); this.pageRequest = true; - this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope); + this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope); }, author(pipeline) { const { commit } = pipeline; + if (!commit) { + return ({ + avatar_url: '', + web_url: '', + username: '', + }); + } + const author = commit.author; if (author) return author; @@ -105,6 +114,21 @@ addTimeInterval(id, start) { this.allTimeIntervals.push({ id, start }); }, + commitTitle(pipeline) { + const { commit } = pipeline; + if (commit) return commit.title; + return ''; + }, + commitSha(pipeline) { + const { commit } = pipeline; + if (commit) return commit.short_id; + return ''; + }, + commitUrl(pipeline) { + const { commit } = pipeline; + if (commit) return commit.commit_url; + return ''; + }, }, template: `
    @@ -122,10 +146,10 @@ @@ -143,7 +167,7 @@
    Date: Thu, 8 Dec 2016 11:05:20 -0700 Subject: [PATCH 155/277] bunch of blocked tests now passing - moving on --- .../vue_pipelines_index/pipelines.js.es6 | 70 ++++--------------- .../projects/pipelines/pipelines_spec.rb | 35 ++++------ 2 files changed, 29 insertions(+), 76 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 090ceb67593..2c18b6cc7e5 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -38,31 +38,17 @@ apiScope: 'all', pageInfo: {}, pagenum: 1, - count: { - all: 0, - running_or_pending: 0, - }, + count: { all: 0, running_or_pending: 0 }, pageRequest: false, }; }, - props: [ - 'scope', - 'store', - ], + props: ['scope', 'store'], created() { const pagenum = getParameterByName('p'); const scope = getParameterByName('scope'); - if (pagenum) this.pagenum = pagenum; if (scope) this.apiScope = scope; - - this.store.fetchDataLoop.call( - this, - Vue, - this.pagenum, - this.scope, - this.apiScope, - ); + this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope); }, methods: { changepage(e) { @@ -82,51 +68,28 @@ this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope); }, author(pipeline) { - const { commit } = pipeline; - if (!commit) { - return ({ - avatar_url: '', - web_url: '', - username: '', - }); - } - - const author = commit.author; - if (author) return author; - - const nonUser = { - avatar_url: commit.author_gravatar_url, - web_url: `mailto:${commit.author_email}`, - username: commit.author_name, - }; - - return nonUser; + if (!pipeline.commit) return ({ avatar_url: '', web_url: '', username: '' }); + if (pipeline.commit.author) return pipeline.commit.author; + return ({ + avatar_url: pipeline.commit.author_gravatar_url, + web_url: `mailto:${pipeline.commit.author_email}`, + username: pipeline.commit.author_name, + }); }, ref(pipeline) { const { ref } = pipeline; - const commitRef = { - name: ref.name, - tag: ref['tag?'], - ref_url: ref.url, - }; - return commitRef; - }, - addTimeInterval(id, start) { - this.allTimeIntervals.push({ id, start }); + return ({ name: ref.name, tag: ref['tag?'], ref_url: ref.url }); }, commitTitle(pipeline) { - const { commit } = pipeline; - if (commit) return commit.title; + if (pipeline.commit) return pipeline.commit.title; return ''; }, commitSha(pipeline) { - const { commit } = pipeline; - if (commit) return commit.short_id; + if (pipeline.commit) return pipeline.commit.short_id; return ''; }, commitUrl(pipeline) { - const { commit } = pipeline; - if (commit) return commit.commit_url; + if (pipeline.commit) return pipeline.commit.commit_url; return ''; }, }, @@ -154,10 +117,7 @@ - - + diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 8c53235e461..e8faa62db96 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -16,28 +16,28 @@ describe "Pipelines", feature: true, js: true do describe 'GET /:project/pipelines', feature: true, js: true do include WaitForVueResource + let(:project) { create(:project) } let!(:pipeline) do create( :ci_empty_pipeline, project: project, ref: 'master', - status: 'running' + status: 'running', + sha: project.commit.id, ) end [:all, :running, :branches].each do |scope| context "displaying #{scope}" do - let(:project) { create(:project) } - before do visit namespace_project_pipelines_path( project.namespace, project, scope: scope ) + wait_for_vue_resource end it do - wait_for_vue_resource expect(page).to have_content(pipeline.short_sha) end end @@ -46,6 +46,7 @@ describe "Pipelines", feature: true, js: true do context 'anonymous access' do before do visit namespace_project_pipelines_path(project.namespace, project) + wait_for_vue_resource end it { expect(page).to have_http_status(:success) } @@ -59,17 +60,12 @@ describe "Pipelines", feature: true, js: true do before do build.run visit namespace_project_pipelines_path(project.namespace, project) + wait_for_vue_resource end - it do - wait_for_vue_resource - expect(page).to have_link('Cancel') - end + it { expect(page).to have_link('Cancel') } - it do - wait_for_vue_resource - expect(page).to have_selector('.ci-running') - end + it { expect(page).to have_selector('.ci-running') } context 'when canceling' do before do @@ -77,15 +73,9 @@ describe "Pipelines", feature: true, js: true do click_link('Cancel') end - it do - wait_for_vue_resource - expect(page).not_to have_link('Cancel') - end + it { expect(page).not_to have_link('Cancel') } - it do - wait_for_vue_resource - expect(page).to have_selector('.ci-canceled') - end + it { expect(page).to have_selector('.ci-canceled') } end end @@ -103,7 +93,10 @@ describe "Pipelines", feature: true, js: true do it { expect(page).to have_selector('.ci-failed') } context 'when retrying' do - before { click_link('Retry') } + before do + wait_for_vue_resource + click_link('Retry') + end it { expect(page).not_to have_link('Retry') } it { expect(page).to have_selector('.ci-running') } From aa74a704ca43334fa521ff99696513012ebee66c Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 8 Dec 2016 11:22:05 -0700 Subject: [PATCH 156/277] first manual build test passing --- .../vue_pipelines_index/pipeline_actions.js.es6 | 2 +- spec/features/projects/pipelines/pipelines_spec.rb | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index e99e65d50b8..fe1db7aae29 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -20,8 +20,8 @@ v-if='pipeline.details.manual_actions.length > 0' class="dropdown-toggle btn btn-default" data-toggle="dropdown" - type="button" title="Manual build" + alt="Manual Build" > Date: Thu, 8 Dec 2016 11:35:39 -0700 Subject: [PATCH 157/277] tests don't see Manual build link --- .../javascripts/vue_pipelines_index/pipeline_actions.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index fe1db7aae29..1c7fa994900 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -23,7 +23,7 @@ title="Manual build" alt="Manual Build" > - + - + Date: Thu, 8 Dec 2016 16:18:24 -0700 Subject: [PATCH 158/277] add passed scope to adhere to dynamic api response --- app/assets/stylesheets/pages/status.scss | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 5084b466722..c77995b8fb6 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -47,6 +47,20 @@ } } + &.ci-passed, + &.ci-passed_with_warnings { + color: $gl-success; + border-color: $gl-success; + + &:not(span):hover { + background-color: rgba( $gl-success, .07); + } + + svg { + fill: $gl-success; + } + } + &.ci-info { color: $gl-info; border-color: $gl-info; From bff8e5bad99ccf515133a82576ca38165de0624c Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 8 Dec 2016 17:37:00 -0700 Subject: [PATCH 159/277] add change page logic to pagination component - add first test for pagination --- .../javascripts/vue_pagination/index.js.es6 | 32 ++++++++++++++- .../vue_pipelines_index/pipelines.js.es6 | 24 ++---------- .../vue_pagination/pagination_spec.js.es6 | 39 +++++++++++++++++++ 3 files changed, 74 insertions(+), 21 deletions(-) create mode 100644 spec/javascripts/vue_pagination/pagination_spec.js.es6 diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 961f7101fb1..ed2357b5d9e 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -9,11 +9,41 @@ const FIRST = '<< First'; const LAST = 'Last >>'; + const getParameterByName = (name) => { + const url = window.location.href; + name = name.replace(/[[\]]/g, '\\$&'); + const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); + const results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, ' ')); + }; + gl.VueGlPagination = Vue.extend({ props: [ - 'changepage', + 'change', 'pageInfo', ], + methods: { + changepage(e) { + let pagenum = this.pageInfo.page; + let apiScope = getParameterByName('scope'); + + if (!apiScope) apiScope = 'all'; + + const text = e.target.innerText; + const { totalPages, nextPage, previousPage } = this.pageInfo; + + if (text === SPREAD) return; + if (/^-?[\d.]+(?:e-?\d+)?$/.test(text)) pagenum = +text; + if (text === LAST) pagenum = totalPages; + if (text === NEXT) pagenum = nextPage; + if (text === PREV) pagenum = previousPage; + if (text === FIRST) pagenum = 1; + + this.change(pagenum, apiScope); + }, + }, computed: { prev() { return this.pageInfo.previousPage; diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 2c18b6cc7e5..ac889bb2d9f 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -2,12 +2,6 @@ /* eslint-disable no-param-reassign, no-bitwise*/ ((gl) => { - const SPREAD = '...'; - const PREV = 'Prev'; - const NEXT = 'Next'; - const FIRST = '<< First'; - const LAST = 'Last >>'; - const getParameterByName = (name) => { const url = window.location.href; name = name.replace(/[[\]]/g, '\\$&'); @@ -51,21 +45,11 @@ this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope); }, methods: { - changepage(e) { - const scope = getParameterByName('scope'); - if (scope) this.apiScope = scope; - const text = e.target.innerText; - const { totalPages, nextPage, previousPage } = this.pageInfo; - if (text === SPREAD) return; - if (/^-?[\d.]+(?:e-?\d+)?$/.test(text)) this.pagenum = +text; - if (text === LAST) this.pagenum = totalPages; - if (text === NEXT) this.pagenum = nextPage; - if (text === PREV) this.pagenum = previousPage; - if (text === FIRST) this.pagenum = 1; - window.history.pushState({}, null, `?scope=${this.apiScope}&p=${this.pagenum}`); + change(pagenum, apiScope) { + window.history.pushState({}, null, `?scope=${apiScope}&p=${pagenum}`); clearInterval(this.timeLoopInterval); this.pageRequest = true; - this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope); + this.store.fetchDataLoop.call(this, Vue, pagenum, this.scope, apiScope); }, author(pipeline) { if (!pipeline.commit) return ({ avatar_url: '', web_url: '', username: '' }); @@ -129,7 +113,7 @@ diff --git a/spec/javascripts/vue_pagination/pagination_spec.js.es6 b/spec/javascripts/vue_pagination/pagination_spec.js.es6 new file mode 100644 index 00000000000..201295ec97e --- /dev/null +++ b/spec/javascripts/vue_pagination/pagination_spec.js.es6 @@ -0,0 +1,39 @@ +//= require vue +//= require vue_pagination/index + +describe('Pagination component', () => { + let component; + + const changeChanges = { + one: '', + two: '', + }; + + const change = (one, two) => { + changeChanges.one = one; + changeChanges.two = two; + }; + + it('should render', () => { + fixture.set('
    '); + + component = new window.gl.VueGlPagination({ + el: document.querySelector('.test-pagination-container'), + propsData: { + pageInfo: { + totalPages: 10, + nextPage: 2, + previousPage: '', + }, + change, + }, + }); + + expect(component.$el.classList).toContain('gl-pagination'); + + component.changepage({ target: { innerText: '1' } }); + + expect(changeChanges.one).toEqual(1); + expect(changeChanges.two).toEqual('all'); + }); +}); From 2ecb65b8c4a3fc277925938e691621e34c449664 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 8 Dec 2016 17:41:59 -0700 Subject: [PATCH 160/277] extract param helper to pagination dir --- app/assets/javascripts/vue_pagination/index.js.es6 | 14 +++----------- .../javascripts/vue_pagination/param_helper.js.es6 | 13 +++++++++++++ .../vue_pipelines_index/pipelines.js.es6 | 14 ++------------ 3 files changed, 18 insertions(+), 23 deletions(-) create mode 100644 app/assets/javascripts/vue_pagination/param_helper.js.es6 diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index ed2357b5d9e..f0f0b54dd2b 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -1,6 +1,8 @@ /* global Vue, gl */ /* eslint-disable no-param-reassign, no-plusplus */ +//= require ./param_helper.js.es6 + ((gl) => { const PAGINATION_UI_BUTTON_LIMIT = 4; const SPREAD = '...'; @@ -9,16 +11,6 @@ const FIRST = '<< First'; const LAST = 'Last >>'; - const getParameterByName = (name) => { - const url = window.location.href; - name = name.replace(/[[\]]/g, '\\$&'); - const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); - const results = regex.exec(url); - if (!results) return null; - if (!results[2]) return ''; - return decodeURIComponent(results[2].replace(/\+/g, ' ')); - }; - gl.VueGlPagination = Vue.extend({ props: [ 'change', @@ -27,7 +19,7 @@ methods: { changepage(e) { let pagenum = this.pageInfo.page; - let apiScope = getParameterByName('scope'); + let apiScope = gl.getParameterByName('scope'); if (!apiScope) apiScope = 'all'; diff --git a/app/assets/javascripts/vue_pagination/param_helper.js.es6 b/app/assets/javascripts/vue_pagination/param_helper.js.es6 new file mode 100644 index 00000000000..4c684f96e9e --- /dev/null +++ b/app/assets/javascripts/vue_pagination/param_helper.js.es6 @@ -0,0 +1,13 @@ +/* eslint-disable no-param-reassign */ + +((w) => { + w.getParameterByName = (name) => { + const url = window.location.href; + name = name.replace(/[[\]]/g, '\\$&'); + const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); + const results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, ' ')); + }; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index ac889bb2d9f..2fdcaf3e16c 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -2,16 +2,6 @@ /* eslint-disable no-param-reassign, no-bitwise*/ ((gl) => { - const getParameterByName = (name) => { - const url = window.location.href; - name = name.replace(/[[\]]/g, '\\$&'); - const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); - const results = regex.exec(url); - if (!results) return null; - if (!results[2]) return ''; - return decodeURIComponent(results[2].replace(/\+/g, ' ')); - }; - gl.VuePipelines = Vue.extend({ components: { runningPipeline: gl.VueRunningPipeline, @@ -38,8 +28,8 @@ }, props: ['scope', 'store'], created() { - const pagenum = getParameterByName('p'); - const scope = getParameterByName('scope'); + const pagenum = gl.getParameterByName('p'); + const scope = gl.getParameterByName('scope'); if (pagenum) this.pagenum = pagenum; if (scope) this.apiScope = scope; this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope); From 67e46e6f9f73b25b7fc2428cfac3110ada849f35 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 9 Dec 2016 21:56:12 +0100 Subject: [PATCH 161/277] Integrate new stages into ci pipeline serializer --- app/serializers/pipeline_entity.rb | 2 +- app/serializers/pipeline_stage_entity.rb | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index a07abb4bec0..e071c03e20b 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -15,7 +15,7 @@ class PipelineEntity < Grape::Entity expose :detailed_status, as: :status, using: StatusEntity expose :duration expose :finished_at - expose :stages_with_statuses, as: :stages, using: PipelineStageEntity + expose :stages, using: PipelineStageEntity expose :artifacts, using: PipelineArtifactEntity expose :manual_actions, using: PipelineActionEntity end diff --git a/app/serializers/pipeline_stage_entity.rb b/app/serializers/pipeline_stage_entity.rb index 230ef8a22da..e4681a0a633 100644 --- a/app/serializers/pipeline_stage_entity.rb +++ b/app/serializers/pipeline_stage_entity.rb @@ -1,15 +1,10 @@ class PipelineStageEntity < Grape::Entity include RequestAwareEntity - expose :name do |stage| - stage.name - end + expose :name + expose :detailed_status, as: :status, using: StatusEntity - expose :status do |stage| - stage.status || 'not found' - end - - expose :url do |stage| + expose :path do |stage| namespace_project_pipeline_path( stage.pipeline.project.namespace, stage.pipeline.project, From 07c6c8e0a1438f745f9f420b24a66a05a76c4272 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 12 Dec 2016 11:59:22 -0700 Subject: [PATCH 162/277] stages render correctly and use new status object --- app/assets/javascripts/vue_pipelines_index/stage.js.es6 | 9 +++------ app/assets/stylesheets/pages/icons.scss | 8 ++++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index b313317b0bb..3761977d5db 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -6,16 +6,13 @@ props: ['stage'], computed: { buildStatus() { - return `Build: ${this.stage.status}`; + return `Build: ${this.stage.status.label}`; }, tooltip() { - return `has-tooltip ci-status-icon-${this.stage.status}`; + return `has-tooltip ci-status-icon-${this.stage.status.label}`; }, svg() { - return document - .querySelector( - `.icon_status_${this.stage.status}`, - ).innerHTML; + return document.querySelector(`.${this.stage.status.icon}`).innerHTML; }, }, template: ` diff --git a/app/assets/stylesheets/pages/icons.scss b/app/assets/stylesheets/pages/icons.scss index 226bd2ead31..8210208cbf6 100644 --- a/app/assets/stylesheets/pages/icons.scss +++ b/app/assets/stylesheets/pages/icons.scss @@ -6,6 +6,14 @@ } } +.ci-status-icon-passed { + color: $gl-success; + + svg { + fill: $gl-success; + } +} + .ci-status-icon-failed { color: $gl-danger; From 562df3a3fb6d41c83180b3b3c519920c9e156de5 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 12 Dec 2016 12:11:28 -0700 Subject: [PATCH 163/277] pass svg as prop to commit component --- app/assets/javascripts/vue_pipelines_index/index.js.es6 | 3 +++ .../javascripts/vue_pipelines_index/pipelines.js.es6 | 9 +++++---- app/views/projects/pipelines/index.html.haml | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 25f5c796a60..ced3e8f2602 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -19,6 +19,7 @@ (() => { const project = document.querySelector('.pipelines'); const entry = document.querySelector('.vue-pipelines-index'); + const svgs = document.querySelector('.pipeline-svgs'); Vue.use(VueResource); @@ -28,6 +29,7 @@ data: { scope: project.dataset.url, store: new gl.PipelineStore(), + svgs: svgs.dataset, }, components: { 'vue-pipelines': gl.VuePipelines, @@ -37,6 +39,7 @@
    diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 2fdcaf3e16c..37fe6c29c5a 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -26,7 +26,7 @@ pageRequest: false, }; }, - props: ['scope', 'store'], + props: ['scope', 'store', 'svgs'], created() { const pagenum = gl.getParameterByName('p'); const scope = gl.getParameterByName('scope'); @@ -81,12 +81,13 @@ diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index fd1baa6195b..565dfa36e8e 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -40,8 +40,8 @@ %div .nothing-here-block No pipelines to show - else - .commit-icon-svg.hidden - = custom_icon("icon_commit") + .pipeline-svgs{"data" => {"commit-icon-svg" => custom_icon("icon_commit")} } + .icon_status_canceled.hidden = custom_icon("icon_status_canceled") .icon_status_running.hidden From 13798c003eb7c2f2f6ccc1ec5a8728b1ed1a130e Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 13 Dec 2016 09:54:18 -0700 Subject: [PATCH 164/277] dynamic API consumption for SVGs --- .../vue_pipelines_index/pipelines.js.es6 | 17 ++++++++++-- .../vue_pipelines_index/stage.js.es6 | 4 +-- .../vue_pipelines_index/stages.js.es6 | 4 +-- app/views/projects/pipelines/index.html.haml | 27 +++++++------------ 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 37fe6c29c5a..a2b12554494 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -66,6 +66,9 @@ if (pipeline.commit) return pipeline.commit.commit_url; return ''; }, + match(string) { + return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase()); + }, }, template: `
    @@ -77,7 +80,12 @@ - + + - + + diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 3761977d5db..7821ed62bd5 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -3,7 +3,7 @@ ((gl) => { gl.VueStage = Vue.extend({ - props: ['stage'], + props: ['stage', 'svgs', 'match'], computed: { buildStatus() { return `Build: ${this.stage.status.label}`; @@ -12,7 +12,7 @@ return `has-tooltip ci-status-icon-${this.stage.status.label}`; }, svg() { - return document.querySelector(`.${this.stage.status.icon}`).innerHTML; + return this.svgs[this.match(this.stage.status.icon)]; }, }, template: ` diff --git a/app/assets/javascripts/vue_pipelines_index/stages.js.es6 b/app/assets/javascripts/vue_pipelines_index/stages.js.es6 index f91c44174de..d7dda9294cd 100644 --- a/app/assets/javascripts/vue_pipelines_index/stages.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stages.js.es6 @@ -6,14 +6,14 @@ components: { 'vue-stage': gl.VueStage, }, - props: ['pipeline'], + props: ['pipeline', 'svgs', 'match'], template: `
    - +
    `, diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 565dfa36e8e..0822480ae14 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -40,24 +40,17 @@ %div .nothing-here-block No pipelines to show - else - .pipeline-svgs{"data" => {"commit-icon-svg" => custom_icon("icon_commit")} } + .pipeline-svgs{"data" => {"commit_icon_svg" => custom_icon("icon_commit"), + "icon_status_canceled" => custom_icon("icon_status_canceled"), + "icon_status_running" => custom_icon("icon_status_running"), + "icon_status_skipped" => custom_icon("icon_status_skipped"), + "icon_status_created" => custom_icon("icon_status_created"), + "icon_status_pending" => custom_icon("icon_status_pending"), + "icon_status_success" => custom_icon("icon_status_success"), + "icon_status_failed" => custom_icon("icon_status_failed"), + "icon_status_warning" => custom_icon("icon_status_warning"), + } } - .icon_status_canceled.hidden - = custom_icon("icon_status_canceled") - .icon_status_running.hidden - = custom_icon("icon_status_running") - .icon_status_skipped.hidden - = custom_icon("icon_status_skipped") - .icon_status_created.hidden - = custom_icon("icon_status_created") - .icon_status_pending.hidden - = custom_icon("icon_status_pending") - .icon_status_success.hidden - = custom_icon("icon_status_success") - .icon_status_failed.hidden - = custom_icon("icon_status_failed") - .icon_status_warning.hidden - = custom_icon("icon_status_warning") .vue-pipelines-index = page_specific_javascript_tag('vue_pagination/index.js') From 1f99283519bd9a009e38464e5029d460fe1e290d Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 13 Dec 2016 10:27:34 -0700 Subject: [PATCH 165/277] status dynamic --- app/assets/javascripts/vue_pipelines_index/status.js.es6 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/status.js.es6 b/app/assets/javascripts/vue_pipelines_index/status.js.es6 index 2f362d0c69e..fd145f5e066 100644 --- a/app/assets/javascripts/vue_pipelines_index/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/status.js.es6 @@ -4,7 +4,7 @@ ((gl) => { gl.VueStatusScope = Vue.extend({ props: [ - 'pipeline', + 'pipeline', 'svgs', 'match', ], computed: { cssClasses() { @@ -14,8 +14,7 @@ return cssObject; }, svg() { - return document - .querySelector(`.${this.pipeline.details.status.icon}`).innerHTML; + return this.svgs[this.match(this.pipeline.details.status.icon)]; }, }, template: ` From 7d2ca647df50f66eba40654b0d52fd8a9aa0d30d Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 13 Dec 2016 10:31:09 -0700 Subject: [PATCH 166/277] stage icon link --- app/assets/javascripts/vue_pipelines_index/stage.js.es6 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 7821ed62bd5..9a2aa5af2b4 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -12,6 +12,7 @@ return `has-tooltip ci-status-icon-${this.stage.status.label}`; }, svg() { + // debugger return this.svgs[this.match(this.stage.status.icon)]; }, }, @@ -19,7 +20,7 @@
    From f9db61fa7c596816367345de948fc6c92a1eabbe Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 13 Dec 2016 10:38:51 -0700 Subject: [PATCH 167/277] add more tests for pagination --- .../vue_pipelines_index/stage.js.es6 | 1 - .../vue_pagination/pagination_spec.js.es6 | 30 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 9a2aa5af2b4..7fcf575b17f 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -12,7 +12,6 @@ return `has-tooltip ci-status-icon-${this.stage.status.label}`; }, svg() { - // debugger return this.svgs[this.match(this.stage.status.icon)]; }, }, diff --git a/spec/javascripts/vue_pagination/pagination_spec.js.es6 b/spec/javascripts/vue_pagination/pagination_spec.js.es6 index 201295ec97e..4a0b60b2e75 100644 --- a/spec/javascripts/vue_pagination/pagination_spec.js.es6 +++ b/spec/javascripts/vue_pagination/pagination_spec.js.es6 @@ -14,7 +14,7 @@ describe('Pagination component', () => { changeChanges.two = two; }; - it('should render', () => { + it('should render and start at page 1', () => { fixture.set('
    '); component = new window.gl.VueGlPagination({ @@ -36,4 +36,32 @@ describe('Pagination component', () => { expect(changeChanges.one).toEqual(1); expect(changeChanges.two).toEqual('all'); }); + + it('should change page to 2 and previous should go cak to 1', () => { + fixture.set('
    '); + + component = new window.gl.VueGlPagination({ + el: document.querySelector('.test-pagination-container'), + propsData: { + pageInfo: { + totalPages: 10, + nextPage: 2, + previousPage: '', + }, + change, + }, + }); + + expect(component.$el.classList).toContain('gl-pagination'); + + component.changepage({ target: { innerText: '2' } }); + + expect(changeChanges.one).toEqual(2); + expect(changeChanges.two).toEqual('all'); + + component.changepage({ target: { innerText: 'Prev' } }); + + expect(changeChanges.one).toEqual(1); + expect(changeChanges.two).toEqual('all'); + }); }); From d4a2c9e6fbfae18b305fb8dee9face7de88c2e6d Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 13 Dec 2016 10:52:07 -0700 Subject: [PATCH 168/277] prev next tests for pagination --- .../vue_pagination/pagination_spec.js.es6 | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/spec/javascripts/vue_pagination/pagination_spec.js.es6 b/spec/javascripts/vue_pagination/pagination_spec.js.es6 index 4a0b60b2e75..9c1fdd339c9 100644 --- a/spec/javascripts/vue_pagination/pagination_spec.js.es6 +++ b/spec/javascripts/vue_pagination/pagination_spec.js.es6 @@ -37,7 +37,7 @@ describe('Pagination component', () => { expect(changeChanges.two).toEqual('all'); }); - it('should change page to 2 and previous should go cak to 1', () => { + it('should go to the previous page', () => { fixture.set('
    '); component = new window.gl.VueGlPagination({ @@ -45,23 +45,37 @@ describe('Pagination component', () => { propsData: { pageInfo: { totalPages: 10, - nextPage: 2, - previousPage: '', + nextPage: 3, + previousPage: 1, }, change, }, }); - expect(component.$el.classList).toContain('gl-pagination'); - - component.changepage({ target: { innerText: '2' } }); - - expect(changeChanges.one).toEqual(2); - expect(changeChanges.two).toEqual('all'); - component.changepage({ target: { innerText: 'Prev' } }); expect(changeChanges.one).toEqual(1); expect(changeChanges.two).toEqual('all'); }); + + it('should go to the next page', () => { + fixture.set('
    '); + + component = new window.gl.VueGlPagination({ + el: document.querySelector('.test-pagination-container'), + propsData: { + pageInfo: { + totalPages: 10, + nextPage: 5, + previousPage: 3, + }, + change, + }, + }); + + component.changepage({ target: { innerText: 'Next' } }); + + expect(changeChanges.one).toEqual(5); + expect(changeChanges.two).toEqual('all'); + }); }); From d46af1d9b263916063200f5fd2cafa18df11c37b Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 13 Dec 2016 10:58:59 -0700 Subject: [PATCH 169/277] main scope of pagination covered --- .../vue_pagination/pagination_spec.js.es6 | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/spec/javascripts/vue_pagination/pagination_spec.js.es6 b/spec/javascripts/vue_pagination/pagination_spec.js.es6 index 9c1fdd339c9..b7f41a875d7 100644 --- a/spec/javascripts/vue_pagination/pagination_spec.js.es6 +++ b/spec/javascripts/vue_pagination/pagination_spec.js.es6 @@ -78,4 +78,67 @@ describe('Pagination component', () => { expect(changeChanges.one).toEqual(5); expect(changeChanges.two).toEqual('all'); }); + + it('should go to the last page', () => { + fixture.set('
    '); + + component = new window.gl.VueGlPagination({ + el: document.querySelector('.test-pagination-container'), + propsData: { + pageInfo: { + totalPages: 10, + nextPage: 5, + previousPage: 3, + }, + change, + }, + }); + + component.changepage({ target: { innerText: 'Last >>' } }); + + expect(changeChanges.one).toEqual(10); + expect(changeChanges.two).toEqual('all'); + }); + + it('should go to the first page', () => { + fixture.set('
    '); + + component = new window.gl.VueGlPagination({ + el: document.querySelector('.test-pagination-container'), + propsData: { + pageInfo: { + totalPages: 10, + nextPage: 5, + previousPage: 3, + }, + change, + }, + }); + + component.changepage({ target: { innerText: '<< First' } }); + + expect(changeChanges.one).toEqual(1); + expect(changeChanges.two).toEqual('all'); + }); + + it('should do nothing', () => { + fixture.set('
    '); + + component = new window.gl.VueGlPagination({ + el: document.querySelector('.test-pagination-container'), + propsData: { + pageInfo: { + totalPages: 10, + nextPage: 2, + previousPage: '', + }, + change, + }, + }); + + component.changepage({ target: { innerText: '...' } }); + + expect(changeChanges.one).toEqual(1); + expect(changeChanges.two).toEqual('all'); + }); }); From 01c8499a791a9179a0ae875ad26a1fa125e7fe4c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 13 Dec 2016 22:37:16 +0000 Subject: [PATCH 170/277] Fix broken rspec tests --- .../pipeline_actions.js.es6 | 4 +- .../projects/pipelines/pipelines_spec.rb | 103 +----------------- 2 files changed, 4 insertions(+), 103 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index 1c7fa994900..24640ba5fb8 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -18,7 +18,7 @@
    diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 16bf33a1e93..f9854bb3fad 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -125,6 +125,7 @@ describe "Pipelines", feature: true, js: true do context 'when playing' do before do wait_for_vue_resource + find('.js-pipeline-dropdown-manual-actions').click click_link('Manual build') end @@ -213,6 +214,7 @@ describe "Pipelines", feature: true, js: true do it do wait_for_vue_resource + find('.js-pipeline-dropdown-download').click expect(page).to have_link(with_artifacts.name) end end @@ -256,107 +258,6 @@ describe "Pipelines", feature: true, js: true do end end - describe 'GET /:project/pipelines/:id' do - let(:project) { create(:project) } - let(:pipeline) do - create( - :ci_pipeline, - project: project, - ref: 'master', - sha: project.commit.id - ) - end - - before do - @success = create( - :ci_build, - :success, - pipeline: pipeline, - stage: 'build', - name: 'build' - ) - @failed = create( - :ci_build, - :failed, - pipeline: pipeline, - stage: 'test', - name: 'test', - commands: 'test' - ) - @running = create( - :ci_build, - :running, - pipeline: pipeline, - stage: 'deploy', - name: 'deploy' - ) - @manual = create( - :ci_build, - :manual, - pipeline: pipeline, - stage: 'deploy', - name: 'manual build' - ) - @external = create( - :generic_commit_status, - status: 'success', - pipeline: pipeline, - name: 'jenkins', - stage: 'external' - ) - end - - before do - visit namespace_project_pipeline_path( - project.namespace, project, pipeline - ) - end - - it 'shows a list of builds' do - expect(page).to have_content('Test') - expect(page).to have_content(@success.id) - expect(page).to have_content('Deploy') - expect(page).to have_content(@failed.id) - expect(page).to have_content(@running.id) - expect(page).to have_content(@external.id) - expect(page).to have_content('Retry failed') - expect(page).to have_content('Cancel running') - expect(page).to have_link('Play') - end - - context 'retrying builds' do - it { expect(page).not_to have_content('retried') } - - context 'when retrying' do - before { click_on 'Retry failed' } - - it { expect(page).not_to have_content('Retry failed') } - it { expect(page).to have_selector('.retried') } - end - end - - context 'canceling builds' do - it { expect(page).not_to have_selector('.ci-canceled') } - - context 'when canceling' do - before { click_on 'Cancel running' } - - it { expect(page).not_to have_content('Cancel running') } - it { expect(page).to have_selector('.ci-canceled') } - end - end - - context 'playing manual build' do - before do - within '.pipeline-holder' do - click_link('Play') - end - end - - it { expect(@manual.reload).to be_pending } - end - end - describe 'POST /:project/pipelines', feature: true, js: true do let(:project) { create(:project) } From 70c0a76cad4c7ce5656c97ac81b4caa21318e961 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 15 Dec 2016 13:41:46 +0100 Subject: [PATCH 171/277] Fix rubocop offenses in code related to pipelines --- .../projects/pipelines_controller.rb | 35 +++++++++++-------- .../projects/pipelines/pipelines_spec.rb | 9 ++--- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 7179f3b22ea..642651101f0 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -7,25 +7,32 @@ class Projects::PipelinesController < Projects::ApplicationController def index @scope = params[:scope] - @pipelines = PipelinesFinder.new(project).execute(scope: @scope).page(params[:page]).per(30) + @pipelines = PipelinesFinder + .new(project) + .execute(scope: @scope) + .page(params[:page]) + .per(30) - @running_or_pending_count = PipelinesFinder.new(project).execute(scope: 'running').count - @pipelines_count = PipelinesFinder.new(project).execute.count + @running_or_pending_count = PipelinesFinder + .new(project).execute(scope: 'running').count + + @pipelines_count = PipelinesFinder + .new(project).execute.count respond_to do |format| format.html format.json do - render json: { - pipelines: PipelineSerializer - .new(project: @project, user: @current_user) - .with_pagination(request, response) - .represent(@pipelines), - updated_at: Time.now.utc, - count: { - all: @pipelines_count, - running_or_pending: @running_or_pending_count - } - } + render json: { + pipelines: PipelineSerializer + .new(project: @project, user: @current_user) + .with_pagination(request, response) + .represent(@pipelines), + updated_at: Time.now.utc, + count: { + all: @pipelines_count, + running_or_pending: @running_or_pending_count + } + } end end end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index f9854bb3fad..c8554c1a975 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -271,12 +271,9 @@ describe "Pipelines", feature: true, js: true do context 'with gitlab-ci.yml' do before { stub_ci_pipeline_to_return_yaml_file } - it do - expect{ - click_on 'Create pipeline' - }.to change{ - Ci::Pipeline.count - }.by(1) + it 'creates a new pipeline' do + expect { click_on 'Create pipeline' } + .to change { Ci::Pipeline.count }.by(1) end end From ccea4727a6999d6abaea9e7a43bd1e015e43f1d2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 15 Dec 2016 15:57:35 +0100 Subject: [PATCH 172/277] Fix pipeline detailed status serializer and entities --- app/serializers/pipeline_entity.rb | 9 +++++++-- app/serializers/status_entity.rb | 2 +- spec/serializers/pipeline_serializer_spec.rb | 15 +++++++++------ spec/serializers/status_entity_spec.rb | 10 ++++------ 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index e071c03e20b..613a18e6357 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -4,7 +4,7 @@ class PipelineEntity < Grape::Entity expose :id expose :user, using: UserEntity - expose :url do |pipeline| + expose :path do |pipeline| namespace_project_pipeline_path( pipeline.project.namespace, pipeline.project, @@ -12,7 +12,12 @@ class PipelineEntity < Grape::Entity end expose :details do - expose :detailed_status, as: :status, using: StatusEntity + expose :status do |pipeline, options| + StatusEntity.represent( + pipeline.detailed_status(request.user), + options) + end + expose :duration expose :finished_at expose :stages, using: PipelineStageEntity diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb index ae14dd57f61..261faa67527 100644 --- a/app/serializers/status_entity.rb +++ b/app/serializers/status_entity.rb @@ -1,7 +1,7 @@ class StatusEntity < Grape::Entity include RequestAwareEntity - expose :icon, :text, :label, :title + expose :icon, :text, :label expose :has_details?, as: :has_details expose :details_path diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index f6b7d1eb1dd..d85c6ae5ad6 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -1,15 +1,18 @@ require 'spec_helper' describe PipelineSerializer do + let(:user) { create(:user) } + let(:pipeline) { create(:ci_empty_pipeline) } + let(:serializer) do described_class.new(user: user) end - let(:pipelines) do - create_list(:ci_pipeline, 2) + describe '#represent' do + subject { serializer.represent(pipeline) } + + it 'serializers the pipeline object' do + expect(subject.as_json).to include :id + end end - - let(:user) { create(:user) } - - # TODO add some tests here. end diff --git a/spec/serializers/status_entity_spec.rb b/spec/serializers/status_entity_spec.rb index 5afa1c25a42..c3da61321de 100644 --- a/spec/serializers/status_entity_spec.rb +++ b/spec/serializers/status_entity_spec.rb @@ -1,12 +1,10 @@ require 'spec_helper' describe StatusEntity do - let(:entity) do - described_class.new(status) - end + let(:entity) { described_class.new(status) } - let(:status) do # TODO, add statuses factory - Gitlab::Ci::Status::Success.new(double('object')) + let(:status) do + Gitlab::Ci::Status::Success.new(double('object'), double('user')) end before do @@ -17,7 +15,7 @@ describe StatusEntity do subject { entity.as_json } it 'contains status details' do - expect(subject).to include :text, :icon, :label, :title + expect(subject).to include :text, :icon, :label expect(subject).to include :has_details expect(subject).to include :details_path end From b33fd0ab3d2b40c91effadc92663a9a618422393 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 15 Dec 2016 15:59:18 +0100 Subject: [PATCH 173/277] Fix detailed status for pipeline stage entity --- app/serializers/pipeline_stage_entity.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/serializers/pipeline_stage_entity.rb b/app/serializers/pipeline_stage_entity.rb index e4681a0a633..5ef39bafdc1 100644 --- a/app/serializers/pipeline_stage_entity.rb +++ b/app/serializers/pipeline_stage_entity.rb @@ -2,7 +2,11 @@ class PipelineStageEntity < Grape::Entity include RequestAwareEntity expose :name - expose :detailed_status, as: :status, using: StatusEntity + expose :status do |stage, options| + StatusEntity.represent( + stage.detailed_status(request.user), + options) + end expose :path do |stage| namespace_project_pipeline_path( From 438199a50db495fedfd857d844f65947cd8ec6d2 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 15 Dec 2016 09:27:57 -0700 Subject: [PATCH 174/277] camel case changepage and make span an a tag --- app/assets/javascripts/vue_pagination/index.js.es6 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index f0f0b54dd2b..b1de0faade8 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -17,7 +17,7 @@ 'pageInfo', ], methods: { - changepage(e) { + changePage(e) { let pagenum = this.pageInfo.page; let apiScope = gl.getParameterByName('scope'); @@ -91,11 +91,11 @@ disabled: item.disabled }' > - {{item.title}} - +
    From 192d003ad1e33c09eab24fdf145c733a23ba94aa Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 15 Dec 2016 09:30:44 -0700 Subject: [PATCH 175/277] add comment about complex regex in pagination --- app/assets/javascripts/vue_pagination/index.js.es6 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index b1de0faade8..db9b81065cc 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -27,6 +27,8 @@ const { totalPages, nextPage, previousPage } = this.pageInfo; if (text === SPREAD) return; + // the regex here is to read if the string coming in will become a valid string + // had issues with parsing using `+` because `typeof NaN === 'number'` if (/^-?[\d.]+(?:e-?\d+)?$/.test(text)) pagenum = +text; if (text === LAST) pagenum = totalPages; if (text === NEXT) pagenum = nextPage; From 0dcd30b4a8ea9a10f2db6506c91bcf005a697186 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 15 Dec 2016 09:33:46 -0700 Subject: [PATCH 176/277] fix comment formatting --- app/assets/javascripts/vue_pagination/index.js.es6 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index db9b81065cc..872c2b11a11 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -27,8 +27,10 @@ const { totalPages, nextPage, previousPage } = this.pageInfo; if (text === SPREAD) return; - // the regex here is to read if the string coming in will become a valid string - // had issues with parsing using `+` because `typeof NaN === 'number'` + /** + the regex here is to read if the string coming in will become a valid number + had issues with parsing using `+` because `typeof NaN === 'number'` + */ if (/^-?[\d.]+(?:e-?\d+)?$/.test(text)) pagenum = +text; if (text === LAST) pagenum = totalPages; if (text === NEXT) pagenum = nextPage; From 0380fdb4206fb0c589a40adda1e1c72520e35394 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 15 Dec 2016 09:40:40 -0700 Subject: [PATCH 177/277] cache timeAgo object --- app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index 33a1744fa82..be3cd41594c 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -11,6 +11,9 @@ 'pipeline', ], computed: { + timeAgo() { + return gl.utils.getTimeago(); + }, localTimeFinished() { return gl.utils.formatDate(this.pipeline.details.finished_at); }, @@ -25,7 +28,7 @@ options.timeZoneName = 'short'; const finished = this.pipeline.details.finished_at; if (!finished && changeTime) return false; - return ({ words: gl.utils.getTimeago().format(finished) }); + return ({ words: this.timeAgo.format(finished) }); }, }, methods: { From be3e32f5f484bf720653e04601035507a0a72fcd Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 15 Dec 2016 09:52:59 -0700 Subject: [PATCH 178/277] change test to reflect camelCase method --- .../vue_pagination/pagination_spec.js.es6 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/javascripts/vue_pagination/pagination_spec.js.es6 b/spec/javascripts/vue_pagination/pagination_spec.js.es6 index b7f41a875d7..eeed95a42c0 100644 --- a/spec/javascripts/vue_pagination/pagination_spec.js.es6 +++ b/spec/javascripts/vue_pagination/pagination_spec.js.es6 @@ -31,7 +31,7 @@ describe('Pagination component', () => { expect(component.$el.classList).toContain('gl-pagination'); - component.changepage({ target: { innerText: '1' } }); + component.changePage({ target: { innerText: '1' } }); expect(changeChanges.one).toEqual(1); expect(changeChanges.two).toEqual('all'); @@ -52,7 +52,7 @@ describe('Pagination component', () => { }, }); - component.changepage({ target: { innerText: 'Prev' } }); + component.changePage({ target: { innerText: 'Prev' } }); expect(changeChanges.one).toEqual(1); expect(changeChanges.two).toEqual('all'); @@ -73,7 +73,7 @@ describe('Pagination component', () => { }, }); - component.changepage({ target: { innerText: 'Next' } }); + component.changePage({ target: { innerText: 'Next' } }); expect(changeChanges.one).toEqual(5); expect(changeChanges.two).toEqual('all'); @@ -94,7 +94,7 @@ describe('Pagination component', () => { }, }); - component.changepage({ target: { innerText: 'Last >>' } }); + component.changePage({ target: { innerText: 'Last >>' } }); expect(changeChanges.one).toEqual(10); expect(changeChanges.two).toEqual('all'); @@ -115,7 +115,7 @@ describe('Pagination component', () => { }, }); - component.changepage({ target: { innerText: '<< First' } }); + component.changePage({ target: { innerText: '<< First' } }); expect(changeChanges.one).toEqual(1); expect(changeChanges.two).toEqual('all'); @@ -136,7 +136,7 @@ describe('Pagination component', () => { }, }); - component.changepage({ target: { innerText: '...' } }); + component.changePage({ target: { innerText: '...' } }); expect(changeChanges.one).toEqual(1); expect(changeChanges.two).toEqual('all'); From 696864eacd88f771839b24a0ceb34cbd346048d1 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 15 Dec 2016 10:00:30 -0700 Subject: [PATCH 179/277] remove need for complex regex in pagination - add else if --- .../javascripts/vue_pagination/index.js.es6 | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 872c2b11a11..54c7ae9b510 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -26,16 +26,23 @@ const text = e.target.innerText; const { totalPages, nextPage, previousPage } = this.pageInfo; - if (text === SPREAD) return; /** the regex here is to read if the string coming in will become a valid number had issues with parsing using `+` because `typeof NaN === 'number'` */ - if (/^-?[\d.]+(?:e-?\d+)?$/.test(text)) pagenum = +text; - if (text === LAST) pagenum = totalPages; - if (text === NEXT) pagenum = nextPage; - if (text === PREV) pagenum = previousPage; - if (text === FIRST) pagenum = 1; + if (text === SPREAD) { + return; + } else if (text === LAST) { + pagenum = totalPages; + } else if (text === NEXT) { + pagenum = nextPage; + } else if (text === PREV) { + pagenum = previousPage; + } else if (text === FIRST) { + pagenum = 1; + } else { + pagenum = +text; + } this.change(pagenum, apiScope); }, From 5151b15c71d9d9805b060f4f47c00c4f72516296 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 15 Dec 2016 10:04:26 -0700 Subject: [PATCH 180/277] comments on turbolinks event handling --- app/assets/javascripts/vue_pagination/index.js.es6 | 4 ---- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 54c7ae9b510..25d1af664b3 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -26,10 +26,6 @@ const text = e.target.innerText; const { totalPages, nextPage, previousPage } = this.pageInfo; - /** - the regex here is to read if the string coming in will become a valid number - had issues with parsing using `+` because `typeof NaN === 'number'` - */ if (text === SPREAD) { return; } else if (text === LAST) { diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 7d8316d6d20..619fd094996 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -63,12 +63,16 @@ window.removeEventListener('beforeunload', () => {}); window.removeEventListener('focus', () => {}); window.removeEventListener('blur', () => {}); + + // turbolinks event handler document.removeEventListener('page:fetch', () => {}); }; window.addEventListener('beforeunload', removeTimeIntervals); window.addEventListener('focus', startIntervalLoops); window.addEventListener('blur', removeTimeIntervals); + + // turbolinks event handler document.addEventListener('page:fetch', removeAll); } }; From 3f21b9573313445b18df999b554e002bc05635c2 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 15 Dec 2016 10:12:39 -0700 Subject: [PATCH 181/277] change to 'Latest pipeline for this branch' --- app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 index 99ecdf3fed0..bf24392b82f 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 @@ -40,7 +40,7 @@ v-if='pipeline.flags.latest === true' class="label label-success has-tooltip" title="" - data-original-title="Latest build for this branch" + data-original-title="Latest pipeline for this branch" > latest From 2dfb7e882626f41d3a1b5b4c2e30038079c7a1bc Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 15 Dec 2016 10:21:45 -0700 Subject: [PATCH 182/277] pageNum is now camelCased --- app/assets/javascripts/vue_pagination/index.js.es6 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 25d1af664b3..712fa8442bc 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -18,7 +18,7 @@ ], methods: { changePage(e) { - let pagenum = this.pageInfo.page; + let pageNum = this.pageInfo.page; let apiScope = gl.getParameterByName('scope'); if (!apiScope) apiScope = 'all'; @@ -29,18 +29,18 @@ if (text === SPREAD) { return; } else if (text === LAST) { - pagenum = totalPages; + pageNum = totalPages; } else if (text === NEXT) { - pagenum = nextPage; + pageNum = nextPage; } else if (text === PREV) { - pagenum = previousPage; + pageNum = previousPage; } else if (text === FIRST) { - pagenum = 1; + pageNum = 1; } else { - pagenum = +text; + pageNum = +text; } - this.change(pagenum, apiScope); + this.change(pageNum, apiScope); }, }, computed: { From 40f433125e5747deb434e17669127cde250a6585 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 15 Dec 2016 10:28:17 -0700 Subject: [PATCH 183/277] strict props for easier re-use --- app/assets/javascripts/vue_pagination/index.js.es6 | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 712fa8442bc..54407c5ceef 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -12,10 +12,16 @@ const LAST = 'Last >>'; gl.VueGlPagination = Vue.extend({ - props: [ - 'change', - 'pageInfo', - ], + props: { + change: { + type: Function, + required: true, + }, + pageInfo: { + type: Object, + required: true, + }, + }, methods: { changePage(e) { let pageNum = this.pageInfo.page; From 9eed507dafbfdd09fcd51b970d633ba0fd764d78 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 15 Dec 2016 10:32:44 -0700 Subject: [PATCH 184/277] add examples of what the props need to be --- .../javascripts/vue_pagination/index.js.es6 | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 54407c5ceef..3889e6d6d28 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -13,10 +13,45 @@ gl.VueGlPagination = Vue.extend({ props: { + + /** + This function will take the information given by the pagination component + And make a new API call from the parent + + Here is an example `change` method: + + change(pagenum, apiScope) { + window.history.pushState({}, null, `?scope=${apiScope}&p=${pagenum}`); + clearInterval(this.timeLoopInterval); + this.pageRequest = true; + this.store.fetchDataLoop.call(this, Vue, pagenum, this.scope, apiScope); + }, + */ + change: { type: Function, required: true, }, + + /** + pageInfo will come from the headers of the API call + in the `.then` clause of the VueResource API call + there should be a function that contructs the pageInfo for this component + + This is an example: + + const pageInfo = (headers) => { + const values = {}; + values.perPage = +headers['X-Per-Page']; + values.page = +headers['X-Page']; + values.total = +headers['X-Total']; + values.totalPages = +headers['X-Total-Pages']; + values.nextPage = +headers['X-Next-Page']; + values.previousPage = +headers['X-Prev-Page']; + return values; + }; + */ + pageInfo: { type: Object, required: true, From b2508cbdaf600bbb8fb2fd56c9891304502429b3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Dec 2016 22:06:39 +0100 Subject: [PATCH 185/277] Improve performance of Pipelines API --- app/finders/pipelines_finder.rb | 1 + app/models/ci/pipeline.rb | 8 ++++++-- app/serializers/pipeline_entity.rb | 4 +--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb index 32aea75486d..af4bb54e52d 100644 --- a/app/finders/pipelines_finder.rb +++ b/app/finders/pipelines_finder.rb @@ -20,6 +20,7 @@ class PipelinesFinder end scoped_pipelines.order(id: :desc) + .includes(project: [:namespace]) end private diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 54f73171fd4..9efda36fc0c 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -124,7 +124,7 @@ module Ci end def artifacts - builds.latest.with_artifacts_not_expired + builds.latest.with_artifacts_not_expired.includes(project: [:namespace]) end def project_id @@ -173,7 +173,11 @@ module Ci end def manual_actions - builds.latest.manual_actions + builds.latest.manual_actions.includes(project: [:namespace]) + end + + def stuck? + builds.pending.any?(&:stuck?) end def retryable? diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 613a18e6357..3ede0fb0f53 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -33,9 +33,7 @@ class PipelineEntity < Grape::Entity pipeline.yaml_errors.present? end - expose :stuck?, as: :stuck do |pipeline| - pipeline.builds.any?(&:stuck?) - end + expose :stuck?, as: :stuck end expose :ref do From a85235b824cd4cf791a10dc69f64732d971bf323 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Dec 2016 22:06:59 +0100 Subject: [PATCH 186/277] Remove unused updated_at --- app/controllers/projects/pipelines_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 642651101f0..9f3702f9f19 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -27,7 +27,6 @@ class Projects::PipelinesController < Projects::ApplicationController .new(project: @project, user: @current_user) .with_pagination(request, response) .represent(@pipelines), - updated_at: Time.now.utc, count: { all: @pipelines_count, running_or_pending: @running_or_pending_count From 27a4aef5848d36b38c861c952a098f85562a31df Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 15 Dec 2016 14:46:35 -0700 Subject: [PATCH 187/277] use boards interceptor --- app/assets/javascripts/vue_pipelines_index/index.js.es6 | 3 ++- .../javascripts/vue_pipelines_index/interceptor.js.es6 | 8 -------- 2 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 app/assets/javascripts/vue_pipelines_index/interceptor.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index ced3e8f2602..8f3e211aac8 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -5,7 +5,8 @@ //= require vue-resource -//= require ./interceptor.js.es6 +/*= require boards/vue_resource_interceptor */ + //= require ./status.js.es6 //= require ./store.js.es6 //= require ./pipeline_url.js.es6 diff --git a/app/assets/javascripts/vue_pipelines_index/interceptor.js.es6 b/app/assets/javascripts/vue_pipelines_index/interceptor.js.es6 deleted file mode 100644 index 80f137ca12e..00000000000 --- a/app/assets/javascripts/vue_pipelines_index/interceptor.js.es6 +++ /dev/null @@ -1,8 +0,0 @@ -/* eslint-disable */ -Vue.http.interceptors.push((request, next) => { - Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; - - next(function (response) { - Vue.activeResources--; - }); -}); From db43813aa0118852d7529b9ec856d6cca19540ad Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 16 Dec 2016 15:37:18 -0700 Subject: [PATCH 188/277] add CONST and one line a tag in pagination - add request to false in store handler --- app/assets/javascripts/vue_pagination/index.js.es6 | 9 +++------ .../javascripts/vue_pipelines_index/index.js.es6 | 14 ++++++-------- .../javascripts/vue_pipelines_index/store.js.es6 | 7 ++++--- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 3889e6d6d28..6aeb6d3b37b 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -5,6 +5,7 @@ ((gl) => { const PAGINATION_UI_BUTTON_LIMIT = 4; + const UI_LIMIT = 6; const SPREAD = '...'; const PREV = 'Prev'; const NEXT = 'Next'; @@ -104,7 +105,7 @@ items.push({ title: PREV, disabled: true }); } - if (page > 6) items.push({ title: SPREAD, separator: true }); + if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true }); const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1); const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total); @@ -139,11 +140,7 @@ disabled: item.disabled }' > - - {{item.title}} - + {{item.title}}
    diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 8f3e211aac8..bad24ce41d0 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -36,14 +36,12 @@ 'vue-pipelines': gl.VuePipelines, }, template: ` -
    - - -
    + + `, }); } diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 619fd094996..46c27b1c38a 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -34,9 +34,10 @@ updatePipelineNums(this.count); this.pageRequest = false; - }, () => new Flash( - 'Something went wrong on our end.', - )); + }, () => { + this.pageRequest = false; + return new Flash('Something went wrong on our end.'); + }); goFetch(); From b08f92928058b395d2d6666b13ccb113bd78efbf Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 16 Dec 2016 15:42:05 -0700 Subject: [PATCH 189/277] interval event handlers - keep page:fetch listener null --- app/assets/javascripts/vue_pipelines_index/store.js.es6 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 46c27b1c38a..3cd31dbffe7 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -60,10 +60,9 @@ }; const removeAll = () => { - removeTimeIntervals(); - window.removeEventListener('beforeunload', () => {}); - window.removeEventListener('focus', () => {}); - window.removeEventListener('blur', () => {}); + window.removeEventListener('beforeunload', removeTimeIntervals); + window.removeEventListener('focus', startIntervalLoops); + window.removeEventListener('blur', removeTimeIntervals); // turbolinks event handler document.removeEventListener('page:fetch', () => {}); From 1e237c9f906460ee13155ee63f91f42bb8f93d3a Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 16 Dec 2016 15:46:15 -0700 Subject: [PATCH 190/277] computed for actions and artifacts --- .../vue_pipelines_index/pipeline_actions.js.es6 | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index 24640ba5fb8..491403c8b59 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -6,6 +6,14 @@ props: [ 'pipeline', ], + computed: { + actions() { + return this.pipeline.details.manual_actions.length > 0; + }, + artifacts() { + return this.pipeline.details.artifacts.length > 0; + }, + }, methods: { download(name) { return `Download ${name} artifacts`; @@ -17,7 +25,7 @@
    Date: Fri, 16 Dec 2016 15:48:24 -0700 Subject: [PATCH 191/277] fix v-ifs in pipeline_url --- .../javascripts/vue_pipelines_index/pipeline_url.js.es6 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 index bf24392b82f..87e516d2f14 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 @@ -37,7 +37,7 @@ API stuck From 0b207930e0196d025e893ab2228682c38f364903 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 16 Dec 2016 15:50:28 -0700 Subject: [PATCH 192/277] refactor pageValues --- .../vue_pipelines_index/store.js.es6 | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 3cd31dbffe7..04fde27af58 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -2,16 +2,14 @@ /* eslint-disable no-param-reassign, no-underscore-dangle */ ((gl) => { - const pageValues = (headers) => { - const values = {}; - values.perPage = +headers['X-Per-Page']; - values.page = +headers['X-Page']; - values.total = +headers['X-Total']; - values.totalPages = +headers['X-Total-Pages']; - values.nextPage = +headers['X-Next-Page']; - values.previousPage = +headers['X-Prev-Page']; - return values; - }; + const pageValues = headers => ({ + perPage: +headers['X-Per-Page'], + page: +headers['X-Page'], + total: +headers['X-Total'], + totalPages: +headers['X-Total-Pages'], + nextPage: +headers['X-Next-Page'], + previousPage: +headers['X-Prev-Page'], + }); gl.PipelineStore = class { fetchDataLoop(Vue, pageNum, url, apiScope) { From 4c620d6b136d3b294e0935a8d13ba102761393e1 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 16 Dec 2016 15:50:56 -0700 Subject: [PATCH 193/277] change example function in pagination component --- .../javascripts/vue_pagination/index.js.es6 | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 index 6aeb6d3b37b..7df15f9d4af 100644 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ b/app/assets/javascripts/vue_pagination/index.js.es6 @@ -41,16 +41,14 @@ This is an example: - const pageInfo = (headers) => { - const values = {}; - values.perPage = +headers['X-Per-Page']; - values.page = +headers['X-Page']; - values.total = +headers['X-Total']; - values.totalPages = +headers['X-Total-Pages']; - values.nextPage = +headers['X-Next-Page']; - values.previousPage = +headers['X-Prev-Page']; - return values; - }; + const pageInfo = headers => ({ + perPage: +headers['X-Per-Page'], + page: +headers['X-Page'], + total: +headers['X-Total'], + totalPages: +headers['X-Total-Pages'], + nextPage: +headers['X-Next-Page'], + previousPage: +headers['X-Prev-Page'], + }); */ pageInfo: { From 715461a9bc74ad732872577e506ff00703fd108c Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 16 Dec 2016 15:59:13 -0700 Subject: [PATCH 194/277] pipline user for pipeline_url refactor --- app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 index 87e516d2f14..96baef32a9f 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 @@ -8,8 +8,7 @@ ], computed: { user() { - if (this.pipeline.user) return true; - return false; + return !!this.pipeline.user; }, }, template: ` From e8b55c51d87e06825f2ea27e2ce32f810ff2fb20 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 16 Dec 2016 16:03:01 -0700 Subject: [PATCH 195/277] refactor commitSha - commitUrl - commitTitle in pipelines.js.es6 --- .../javascripts/vue_pipelines_index/pipelines.js.es6 | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index a2b12554494..4a907eec681 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -55,16 +55,13 @@ return ({ name: ref.name, tag: ref['tag?'], ref_url: ref.url }); }, commitTitle(pipeline) { - if (pipeline.commit) return pipeline.commit.title; - return ''; + return pipeline.commit ? pipeline.commit.title : ''; }, commitSha(pipeline) { - if (pipeline.commit) return pipeline.commit.short_id; - return ''; + return pipeline.commit ? pipeline.commit.short_id : ''; }, commitUrl(pipeline) { - if (pipeline.commit) return pipeline.commit.commit_url; - return ''; + return pipeline.commit ? pipeline.commit.commit_url : ''; }, match(string) { return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase()); From e98cdf4b2070c27da76cfa4ba3d5e2022ff08b19 Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 16 Dec 2016 16:12:52 -0700 Subject: [PATCH 196/277] all svgs from index haml --- .../pipeline_actions.js.es6 | 20 +++---------------- .../vue_pipelines_index/pipelines.js.es6 | 4 ++-- .../vue_pipelines_index/time_ago.js.es6 | 15 ++------------ app/views/projects/pipelines/index.html.haml | 2 ++ 4 files changed, 9 insertions(+), 32 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index 491403c8b59..b568d3a75a2 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -3,9 +3,7 @@ ((gl) => { gl.VuePipelineActions = Vue.extend({ - props: [ - 'pipeline', - ], + props: ['pipeline', 'svgs'], computed: { actions() { return this.pipeline.details.manual_actions.length > 0; @@ -31,13 +29,7 @@ title="Manual build" alt="Manual Build" > - - - - +
    From 6a7af99a42a13d87cd4c8c2ea757464d2b0f9ce4 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 21 Dec 2016 17:30:27 -0700 Subject: [PATCH 208/277] changed label to group for css --- app/assets/javascripts/vue_pipelines_index/stage.js.es6 | 2 +- app/assets/javascripts/vue_pipelines_index/status.js.es6 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 7fcf575b17f..d214092f412 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -9,7 +9,7 @@ return `Build: ${this.stage.status.label}`; }, tooltip() { - return `has-tooltip ci-status-icon-${this.stage.status.label}`; + return `has-tooltip ci-status-icon-${this.stage.status.group}`; }, svg() { return this.svgs[this.match(this.stage.status.icon)]; diff --git a/app/assets/javascripts/vue_pipelines_index/status.js.es6 b/app/assets/javascripts/vue_pipelines_index/status.js.es6 index fd145f5e066..55efb8ebae4 100644 --- a/app/assets/javascripts/vue_pipelines_index/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/status.js.es6 @@ -10,7 +10,7 @@ cssClasses() { const cssObject = {}; cssObject['ci-status'] = true; - cssObject[`ci-${this.pipeline.details.status.text}`] = true; + cssObject[`ci-${this.pipeline.details.status.group}`] = true; return cssObject; }, svg() { From 2d1218991c1cd5b7b56e15ccc8d7c47d4b8e6cd0 Mon Sep 17 00:00:00 2001 From: Regis Date: Wed, 21 Dec 2016 18:06:23 -0700 Subject: [PATCH 209/277] use path instead of url for commit ref and pipeline url --- .../javascripts/vue_pipelines_index/pipeline_url.js.es6 | 2 +- app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 index 96baef32a9f..92c343e33da 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 @@ -13,7 +13,7 @@ }, template: ` - + #{{pipeline.id}} by diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index b82d8f1a0db..a16c1e5bef9 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -52,7 +52,7 @@ }, ref(pipeline) { const { ref } = pipeline; - return ({ name: ref.name, tag: ref['tag?'], ref_url: ref.url }); + return ({ name: ref.name, tag: ref.tag, ref_url: ref.path }); }, commitTitle(pipeline) { return pipeline.commit ? pipeline.commit.title : ''; @@ -61,7 +61,7 @@ return pipeline.commit ? pipeline.commit.short_id : ''; }, commitUrl(pipeline) { - return pipeline.commit ? pipeline.commit.commit_url : ''; + return pipeline.commit ? pipeline.commit.commit_path : ''; }, match(string) { return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase()); From c0e5e69f9dc778bfe60e8362b72866413672c06f Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 23 Dec 2016 16:23:19 -0700 Subject: [PATCH 210/277] fix manual build tests --- .../javascripts/vue_pipelines_index/pipeline_actions.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index 65aa3ff07d2..f658cfd077c 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -37,7 +37,7 @@ @@ -60,7 +60,7 @@
  • {{download(artifact.name)}} From 115536fbfa9904cef459bb735d29f20756887ebe Mon Sep 17 00:00:00 2001 From: Regis Date: Fri, 23 Dec 2016 16:28:37 -0700 Subject: [PATCH 211/277] use flags for retry and cancel logic --- .../javascripts/vue_pipelines_index/pipeline_actions.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index f658cfd077c..ad5cb30cc42 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -71,7 +71,7 @@
  • Date: Fri, 23 Dec 2016 16:54:03 -0700 Subject: [PATCH 212/277] get rid of nested spans in status component --- app/assets/javascripts/vue_pipelines_index/status.js.es6 | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/status.js.es6 b/app/assets/javascripts/vue_pipelines_index/status.js.es6 index 55efb8ebae4..f8decb17ca8 100644 --- a/app/assets/javascripts/vue_pipelines_index/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/status.js.es6 @@ -19,11 +19,8 @@ }, template: ` - - - -  {{pipeline.details.status.text}} - + + `, From e56254d38ab4a3c3deeacadbc2388b76d932805d Mon Sep 17 00:00:00 2001 From: Regis Date: Sat, 24 Dec 2016 14:04:17 -0700 Subject: [PATCH 213/277] remove use of span for svg and status text --- app/assets/javascripts/vue_pipelines_index/status.js.es6 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/status.js.es6 b/app/assets/javascripts/vue_pipelines_index/status.js.es6 index f8decb17ca8..edb24ced080 100644 --- a/app/assets/javascripts/vue_pipelines_index/status.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/status.js.es6 @@ -18,9 +18,12 @@ }, }, template: ` - - - + + `, From 725ba04e0376c8909e75448f4407eca0fba6c807 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 26 Dec 2016 14:24:15 -0700 Subject: [PATCH 214/277] wip --- .../vue_pipelines_index/stage.js.es6 | 56 ++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index d214092f412..42d237cb2cc 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -3,6 +3,12 @@ ((gl) => { gl.VueStage = Vue.extend({ + data() { + return { + request: false, + builds: '
      ', + }; + }, props: ['stage', 'svgs', 'match'], computed: { buildStatus() { @@ -14,15 +20,51 @@ svg() { return this.svgs[this.match(this.stage.status.icon)]; }, + spanClass() { + return `ci-status-icon ci-status-icon-${this.stage.status.group}`; + }, }, template: ` - - +
      + +
      `, }); })(window.gl || (window.gl = {})); From 9b5bbf0a343d7730f08b61112d73579025f2208b Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 26 Dec 2016 14:34:03 -0700 Subject: [PATCH 215/277] add api call - get mock url next --- .../javascripts/vue_pipelines_index/stage.js.es6 | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 42d237cb2cc..d5f7194979b 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -1,4 +1,4 @@ -/* global Vue, gl */ +/* global Vue, Flash, gl */ /* eslint-disable no-param-reassign */ ((gl) => { @@ -23,6 +23,17 @@ spanClass() { return `ci-status-icon ci-status-icon-${this.stage.status.group}`; }, + methods: { + fetchBuilds() { + this.$http.get(this.stage.status.endpoint) + .then((response) => { + Vue.set(this, 'builds', response.html); + Vue.set(this, 'response', true); + }, () => new Flash( + 'Something went wrong on our end.', + )); + }, + }, }, template: `
      From 8a7f58d21337b8858ae8e3e57f052eed1026687f Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 26 Dec 2016 14:59:48 -0700 Subject: [PATCH 216/277] mini-graph shows up from API call - need to fix state bugs --- .../vue_pipelines_index/stage.js.es6 | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index d5f7194979b..944f84f7880 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -10,7 +10,28 @@ }; }, props: ['stage', 'svgs', 'match'], + methods: { + fetchBuilds() { + this.$http.get(this.endpoint) + .then((response) => { + this.builds = JSON.parse(response.body).html; + this.request = true; + }, () => new Flash( + 'Something went wrong on our end.', + )); + }, + clearState() { + this.response = false; + this.builds = '
        '; + }, + }, computed: { + endpoint() { + return '/gitlab-org/gitlab-shell/pipelines/121/stage?stage=deploy'; + }, + stageTitle() { + return 'deploy: running'; + }, buildStatus() { return `Build: ${this.stage.status.label}`; }, @@ -23,26 +44,17 @@ spanClass() { return `ci-status-icon ci-status-icon-${this.stage.status.group}`; }, - methods: { - fetchBuilds() { - this.$http.get(this.stage.status.endpoint) - .then((response) => { - Vue.set(this, 'builds', response.html); - Vue.set(this, 'response', true); - }, () => new Flash( - 'Something went wrong on our end.', - )); - }, - }, }, template: `
        From c9fad91d042453530bdb886f6df5ed3b7a2b7f8f Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 26 Dec 2016 15:29:03 -0700 Subject: [PATCH 218/277] remove v-if to let spinner show --- app/assets/javascripts/vue_pipelines_index/stage.js.es6 | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 5fcfae2375c..fe8e94ac7a7 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -84,7 +84,6 @@
        From e077cebe5003ff93671a147aad8266e5b0dbd04d Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 26 Dec 2016 16:09:27 -0700 Subject: [PATCH 219/277] add borderless svgs --- .../javascripts/vue_pipelines_index/stage.js.es6 | 15 ++++++++------- app/views/projects/pipelines/index.html.haml | 8 ++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index fe8e94ac7a7..b7da52ad01b 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -16,9 +16,7 @@ this.$http.get(this.endpoint) .then((response) => { this.request = true; - setTimeout(() => { - this.builds = JSON.parse(response.body).html; - }, 100); + this.builds = JSON.parse(response.body).html; }, () => new Flash( 'Something went wrong on our end.', )); @@ -30,8 +28,7 @@ }, computed: { buildsOrSpinner() { - if (this.request) return this.builds; - return this.spinner; + return this.request ? this.builds : this.spinner; }, dropdownClass() { if (this.request) return 'js-builds-dropdown-container'; @@ -50,7 +47,9 @@ return `has-tooltip ci-status-icon-${this.stage.status.group}`; }, svg() { - return this.svgs[this.match(this.stage.status.icon)]; + const icon = this.stage.status.icon; + icon.replace('icon', 'stageIcon'); + return this.svgs[this.match(icon)]; }, spanClass() { return `ci-status-icon ci-status-icon-${this.stage.status.group}`; @@ -58,10 +57,10 @@ }, template: `
        + +
        `, }); diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 5d7ffce78df..64d3665594e 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -49,6 +49,14 @@ "icon_status_success" => custom_icon("icon_status_success"), "icon_status_failed" => custom_icon("icon_status_failed"), "icon_status_warning" => custom_icon("icon_status_warning"), + "stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"), + "stage_icon_status_running" => custom_icon("icon_status_running_borderless"), + "stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"), + "stage_icon_status_created" => custom_icon("icon_status_created_borderless"), + "stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"), + "stage_icon_status_success" => custom_icon("icon_status_success_borderless"), + "stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"), + "stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"), "icon_play" => custom_icon("icon_play"), "icon_timer" => custom_icon("icon_timer"), } } From 71d50042f45e9f04e80d1b54d310f0f8fb03bda4 Mon Sep 17 00:00:00 2001 From: Regis Date: Mon, 26 Dec 2016 16:50:57 -0700 Subject: [PATCH 220/277] add manual icon to svgs --- app/views/projects/pipelines/index.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 64d3665594e..e423fc8c471 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -59,6 +59,7 @@ "stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"), "icon_play" => custom_icon("icon_play"), "icon_timer" => custom_icon("icon_timer"), + "icon_status_manual" => custom_icon("icon_status_manual"), } } .vue-pipelines-index From f9331db136f19dfdc6b2affa0922bdcda7c681fc Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 22 Dec 2016 13:50:45 +0100 Subject: [PATCH 221/277] Add tests for pagination module extracted from API --- spec/lib/api/helpers/pagination_spec.rb | 94 +++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 spec/lib/api/helpers/pagination_spec.rb diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb new file mode 100644 index 00000000000..267318faed4 --- /dev/null +++ b/spec/lib/api/helpers/pagination_spec.rb @@ -0,0 +1,94 @@ +require 'spec_helper' + +describe API::Helpers::Pagination do + let(:resource) { Project.all } + + subject do + Class.new.include(described_class).new + end + + describe '#paginate' do + let(:value) { spy('return value') } + + before do + allow(value).to receive(:to_query).and_return(value) + + allow(subject).to receive(:header).and_return(value) + allow(subject).to receive(:params).and_return(value) + allow(subject).to receive(:request).and_return(value) + end + + describe 'required instance methods' do + let(:return_spy) { spy } + + it 'requires some instance methods' do + expect_message(:header) + expect_message(:params) + expect_message(:request) + + subject.paginate(resource) + end + end + + context 'when resource can be paginated' do + before do + create_list(:empty_project, 3) + end + + describe 'first page' do + before do + allow(subject).to receive(:params) + .and_return({ page: 1, per_page: 2 }) + end + + it 'returns appropriate amount of resources' do + expect(subject.paginate(resource).count).to eq 2 + end + + it 'adds appropriate headers' do + expect_header('X-Total', '3') + expect_header('X-Total-Pages', '2') + expect_header('X-Per-Page', '2') + expect_header('X-Page', '1') + expect_header('X-Next-Page', '2') + expect_header('X-Prev-Page', '') + expect_header('Link', any_args) + + subject.paginate(resource) + end + end + + describe 'second page' do + before do + allow(subject).to receive(:params) + .and_return({ page: 2, per_page: 2 }) + end + + it 'returns appropriate amount of resources' do + expect(subject.paginate(resource).count).to eq 1 + end + + it 'adds appropriate headers' do + expect_header('X-Total', '3') + expect_header('X-Total-Pages', '2') + expect_header('X-Per-Page', '2') + expect_header('X-Page', '2') + expect_header('X-Next-Page', '') + expect_header('X-Prev-Page', '1') + expect_header('Link', any_args) + + subject.paginate(resource) + end + end + end + + def expect_header(name, value) + expect(subject).to receive(:header).with(name, value) + end + + def expect_message(method) + expect(subject).to receive(method) + .at_least(:once).and_return(value) + end + end +end From 1b14182f21a60384234e79b3a0f02c81d016192d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 27 Dec 2016 12:48:01 +0100 Subject: [PATCH 222/277] Expose stage dropdown path and title in stage entity --- app/serializers/stage_entity.rb | 28 ++++++++++++++++++++++----- spec/serializers/stage_entity_spec.rb | 11 +++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb index b57202a6267..1aebba3a2d1 100644 --- a/app/serializers/stage_entity.rb +++ b/app/serializers/stage_entity.rb @@ -3,17 +3,35 @@ class StageEntity < Grape::Entity expose :name - expose :status do |stage, options| - StatusEntity.represent( - stage.detailed_status(request.user), - options) + expose :title do |stage| + "#{stage.name}: #{detailed_status.label}" end + expose :detailed_status, as: :status, + with: StatusEntity + expose :path do |stage| namespace_project_pipeline_path( stage.pipeline.project.namespace, stage.pipeline.project, - stage.pipeline.id, + stage.pipeline, anchor: stage.name) end + + expose :dropdown_path do |stage| + stage_namespace_project_pipeline_path( + stage.pipeline.project.namespace, + stage.pipeline.project, + stage.pipeline, + stage: stage.name, + format: :json) + end + + private + + alias_method :stage, :object + + def detailed_status + stage.detailed_status(request.user) + end end diff --git a/spec/serializers/stage_entity_spec.rb b/spec/serializers/stage_entity_spec.rb index 807e09f860a..4ab40d08432 100644 --- a/spec/serializers/stage_entity_spec.rb +++ b/spec/serializers/stage_entity_spec.rb @@ -15,6 +15,7 @@ describe StageEntity do before do allow(request).to receive(:user).and_return(user) + create(:ci_build, :success, pipeline: pipeline) end describe '#as_json' do @@ -26,6 +27,7 @@ describe StageEntity do it 'contains detailed status' do expect(subject[:status]).to include :text, :label, :group, :icon + expect(subject[:status][:label]).to eq 'passed' end it 'contains valid name' do @@ -36,5 +38,14 @@ describe StageEntity do expect(subject[:path]) .to include "pipelines/#{pipeline.id}##{stage.name}" end + + it 'contains path to the stage dropdown' do + expect(subject[:dropdown_path]) + .to include "pipelines/#{pipeline.id}/stage.json?stage=test" + end + + it 'contains stage title' do + expect(subject[:title]).to eq 'test: passed' + end end end From 8e603b62c9305edfa4f5e677ed86a99346e4303f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 27 Dec 2016 13:15:14 +0100 Subject: [PATCH 223/277] Add missing specs for new methods in pipeline class --- spec/factories/ci/runners.rb | 4 ++++ spec/models/ci/pipeline_spec.rb | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb index e3b73e29987..ed4acca23f1 100644 --- a/spec/factories/ci/runners.rb +++ b/spec/factories/ci/runners.rb @@ -8,6 +8,10 @@ FactoryGirl.define do is_shared false active true + trait :online do + contacted_at Time.now + end + trait :shared do is_shared true end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index dc377d15f15..79341d43c08 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -875,6 +875,48 @@ describe Ci::Pipeline, models: true do end end + describe '#stuck?' do + before do + create(:ci_build, :pending, pipeline: pipeline) + end + + context 'when pipeline is stuck' do + it 'is stuck' do + expect(pipeline).to be_stuck + end + end + + context 'when pipeline is not stuck' do + before { create(:ci_runner, :shared, :online) } + + it 'is not stuck' do + expect(pipeline).not_to be_stuck + end + end + end + + describe '#has_yaml_errors?' do + context 'when pipeline has errors' do + let(:pipeline) do + create(:ci_pipeline, config: { rspec: nil }) + end + + it 'contains yaml errors' do + expect(pipeline).to have_yaml_errors + end + end + + context 'when pipeline does not have errors' do + let(:pipeline) do + create(:ci_pipeline, config: { rspec: { script: 'rake test' } }) + end + + it 'does not containyaml errors' do + expect(pipeline).not_to have_yaml_errors + end + end + end + describe 'notifications when pipeline success or failed' do let(:project) { create(:project) } From 589fd93d489cfa9e40ee265be69ab1ad95deb6e1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 27 Dec 2016 14:37:17 +0100 Subject: [PATCH 224/277] Fix Rubocop offenses in serializer entities --- app/serializers/stage_entity.rb | 3 ++- spec/serializers/request_aware_entity_spec.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb index 1aebba3a2d1..7a047bdc712 100644 --- a/app/serializers/stage_entity.rb +++ b/app/serializers/stage_entity.rb @@ -7,7 +7,8 @@ class StageEntity < Grape::Entity "#{stage.name}: #{detailed_status.label}" end - expose :detailed_status, as: :status, + expose :detailed_status, + as: :status, with: StatusEntity expose :path do |stage| diff --git a/spec/serializers/request_aware_entity_spec.rb b/spec/serializers/request_aware_entity_spec.rb index ac23497fad9..aa666b961dc 100644 --- a/spec/serializers/request_aware_entity_spec.rb +++ b/spec/serializers/request_aware_entity_spec.rb @@ -15,7 +15,7 @@ describe RequestAwareEntity do it 'fetches request from options' do expect(subject).to receive(:options) - .and_return({request: 'some value'}) + .and_return({ request: 'some value' }) expect(subject.request).to eq 'some value' end From 6a110c00b52b0e312f06249c5fd597f39ababe87 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 27 Dec 2016 11:15:19 -0700 Subject: [PATCH 225/277] hook up new API key values to stage dropdown mini graph in vue --- .../javascripts/vue_pipelines_index/stage.js.es6 | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index b7da52ad01b..026fba80ca2 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -13,7 +13,7 @@ props: ['stage', 'svgs', 'match'], methods: { fetchBuilds() { - this.$http.get(this.endpoint) + this.$http.get(this.stage.dropdown_path) .then((response) => { this.request = true; this.builds = JSON.parse(response.body).html; @@ -34,12 +34,6 @@ if (this.request) return 'js-builds-dropdown-container'; return 'js-builds-dropdown-loading builds-dropdown-loading'; }, - endpoint() { - return '/gitlab-org/gitlab-shell/pipelines/121/stage?stage=deploy'; - }, - stageTitle() { - return 'deploy: running'; - }, buildStatus() { return `Build: ${this.stage.status.label}`; }, @@ -63,7 +57,7 @@ @click='fetchBuilds' class="has-tooltip builds-dropdown js-builds-dropdown-button" data-placement="top" - :title='stageTitle' + :title='stage.title' data-toggle="dropdown" type="button" > From 5e558af7a0cb77f58271b02039966ecf6c8df84c Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 27 Dec 2016 11:42:55 -0700 Subject: [PATCH 226/277] remove mini-graph js from dispatcher for pipelines:index --- app/assets/javascripts/dispatcher.js.es6 | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index a118003617d..6a84f52d8df 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -173,11 +173,6 @@ new TreeView(); } break; - case 'projects:pipelines:index': - // new gl.MiniPipelineGraph({ - // container: '.js-pipeline-table', - // }); - break; case 'projects:pipelines:builds': case 'projects:pipelines:show': const { controllerAction } = document.querySelector('.js-pipeline-container').dataset; From abed2c1a9443180dddb3c26d29d447d2e7562337 Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 27 Dec 2016 11:49:14 -0700 Subject: [PATCH 227/277] remove data attribute test since endpoint is in Vue state for stage component --- spec/features/projects/pipelines/pipelines_spec.rb | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 9e3d68ed99d..e783576cb2e 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -276,29 +276,19 @@ describe 'Pipelines', :feature, :js do before do visit namespace_project_pipelines_path(project.namespace, project) - end - - it 'should render a mini pipeline graph' do - endpoint = stage_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline, stage: build.name) - - expect(page).to have_selector('.mini-pipeline-graph') - expect(page).to have_selector(".js-builds-dropdown-button[data-stage-endpoint='#{endpoint}']") + wait_for_vue_resource end context 'when clicking a graph stage' do it 'should open a dropdown' do find('.js-builds-dropdown-button').trigger('click') - wait_for_ajax - expect(page).to have_link build.name end it 'should be possible to retry the failed build' do find('.js-builds-dropdown-button').trigger('click') - wait_for_ajax - find('a.ci-action-icon-container').trigger('click') expect(page).not_to have_content('Cancel running') end From 75bddd01d0b611fcd45e8a9789e7f8e650e5850b Mon Sep 17 00:00:00 2001 From: Regis Date: Tue, 27 Dec 2016 12:24:11 -0700 Subject: [PATCH 228/277] avoid making multiple calls on blur on collapse click events --- app/assets/javascripts/vue_pipelines_index/stage.js.es6 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 026fba80ca2..920ae5dac9a 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -13,7 +13,13 @@ props: ['stage', 'svgs', 'match'], methods: { fetchBuilds() { - this.$http.get(this.stage.dropdown_path) + if (this.request) { + this.request = false; + this.builds = ''; + return null; + } + + return this.$http.get(this.stage.dropdown_path) .then((response) => { this.request = true; this.builds = JSON.parse(response.body).html; @@ -55,6 +61,7 @@