diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2017-05-04 16:11:48 +0300 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2017-05-04 16:11:48 +0300 |
commit | e69732e2fb4a2a22947bb6faadd0292275c03ace (patch) | |
tree | f315cea436799b8eb235136b2dc82003dfd703bb /app | |
parent | dcdced81ae249d38837068d2cc6b7e21ebf1bef2 (diff) |
Pipeline table mini graph dropdown remains open when table is refreshed
Diffstat (limited to 'app')
8 files changed, 239 insertions, 153 deletions
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js index e704be8b53e..ad9c600b499 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js @@ -46,6 +46,7 @@ export default Vue.component('pipelines-table', { isLoading: false, hasError: false, isMakingRequest: false, + updateGraphDropdown: false, }; }, @@ -130,15 +131,21 @@ export default Vue.component('pipelines-table', { const pipelines = response.pipelines || response; this.store.storePipelines(pipelines); this.isLoading = false; + this.updateGraphDropdown = true; }, errorCallback() { this.hasError = true; this.isLoading = false; + this.updateGraphDropdown = false; }, setIsMakingRequest(isMakingRequest) { this.isMakingRequest = isMakingRequest; + + if (isMakingRequest) { + this.updateGraphDropdown = false; + } }, }, @@ -163,7 +170,9 @@ export default Vue.component('pipelines-table', { v-if="shouldRenderTable"> <pipelines-table-component :pipelines="state.pipelines" - :service="service" /> + :service="service" + :update-graph-dropdown="updateGraphDropdown" + /> </div> </div> `, diff --git a/app/assets/javascripts/pipelines/components/stage.js b/app/assets/javascripts/pipelines/components/stage.js deleted file mode 100644 index 203485f2990..00000000000 --- a/app/assets/javascripts/pipelines/components/stage.js +++ /dev/null @@ -1,115 +0,0 @@ -/* global Flash */ -import StatusIconEntityMap from '../../ci_status_icons'; - -export default { - props: { - stage: { - type: Object, - required: true, - }, - }, - - data() { - return { - builds: '', - spinner: '<span class="fa fa-spinner fa-spin"></span>', - }; - }, - - updated() { - if (this.builds) { - this.stopDropdownClickPropagation(); - } - }, - - methods: { - fetchBuilds(e) { - const ariaExpanded = e.currentTarget.attributes['aria-expanded']; - - if (ariaExpanded && (ariaExpanded.textContent === 'true')) return null; - - return this.$http.get(this.stage.dropdown_path) - .then((response) => { - this.builds = JSON.parse(response.body).html; - }) - .catch(() => { - // If dropdown is opened we'll close it. - if (this.$el.classList.contains('open')) { - $(this.$refs.dropdown).dropdown('toggle'); - } - - const flash = new Flash('Something went wrong on our end.'); - return flash; - }); - }, - - /** - * When the user right clicks or cmd/ctrl + click in the job name - * the dropdown should not be closed and the link should open in another tab, - * so we stop propagation of the click event inside the dropdown. - * - * Since this component is rendered multiple times per page we need to guarantee we only - * target the click event of this component. - */ - stopDropdownClickPropagation() { - $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')) - .on('click', (e) => { - e.stopPropagation(); - }); - }, - }, - computed: { - buildsOrSpinner() { - return this.builds ? this.builds : this.spinner; - }, - dropdownClass() { - if (this.builds) return 'js-builds-dropdown-container'; - return 'js-builds-dropdown-loading builds-dropdown-loading'; - }, - buildStatus() { - return `Build: ${this.stage.status.label}`; - }, - tooltip() { - return `has-tooltip ci-status-icon ci-status-icon-${this.stage.status.group}`; - }, - triggerButtonClass() { - return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`; - }, - svgHTML() { - return StatusIconEntityMap[this.stage.status.icon]; - }, - }, - template: ` - <div> - <button - @click="fetchBuilds($event)" - :class="triggerButtonClass" - :title="stage.title" - data-placement="top" - data-toggle="dropdown" - type="button" - :aria-label="stage.title" - ref="dropdown"> - <span - v-html="svgHTML" - aria-hidden="true"> - </span> - <i - class="fa fa-caret-down" - aria-hidden="true" /> - </button> - <ul - ref="dropdown-content" - class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"> - <div - class="arrow-up" - aria-hidden="true"></div> - <div - :class="dropdownClass" - class="js-builds-dropdown-list scrollable-menu" - v-html="buildsOrSpinner"> - </div> - </ul> - </div> - `, -}; diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue new file mode 100644 index 00000000000..2e485f951a1 --- /dev/null +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -0,0 +1,173 @@ +<script> + +/** + * Renders each stage of the pipeline mini graph. + * + * Given the provided endpoint will make a request to + * fetch the dropdown data when the stage is clicked. + * + * Request is made inside this component to make it reusable between: + * 1. Pipelines main table + * 2. Pipelines table in commit and Merge request views + * 3. Merge request widget + * 4. Commit widget + */ + +/* global Flash */ +import StatusIconEntityMap from '../../ci_status_icons'; + +export default { + props: { + stage: { + type: Object, + required: true, + }, + + updateDropdown: { + type: Boolean, + required: false, + default: false, + }, + }, + + data() { + return { + isLoading: false, + dropdownContent: '', + endpoint: this.stage.dropdown_path, + }; + }, + + updated() { + if (this.dropdownContent.length > 0) { + this.stopDropdownClickPropagation(); + } + }, + + watch: { + updateDropdown() { + if (this.updateDropdown && + this.isDropdownOpen() && + !this.isLoading) { + this.fetchJobs(); + } + }, + }, + + methods: { + onClickStage() { + if (!this.isDropdownOpen()) { + this.isLoading = true; + this.fetchJobs(); + } + }, + + fetchJobs() { + this.$http.get(this.endpoint) + .then((response) => { + this.dropdownContent = response.json().html; + this.isLoading = false; + }) + .catch(() => { + this.closeDropdown(); + this.isLoading = false; + + const flash = new Flash('Something went wrong on our end.'); + return flash; + }); + }, + + /** + * When the user right clicks or cmd/ctrl + click in the job name + * the dropdown should not be closed and the link should open in another tab, + * so we stop propagation of the click event inside the dropdown. + * + * Since this component is rendered multiple times per page we need to guarantee we only + * target the click event of this component. + */ + stopDropdownClickPropagation() { + $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')) + .on('click', (e) => { + e.stopPropagation(); + }); + }, + + closeDropdown() { + if (this.isDropdownOpen()) { + $(this.$refs.dropdown).dropdown('toggle'); + } + }, + + isDropdownOpen() { + return this.$el.classList.contains('open'); + }, + }, + + computed: { + dropdownClass() { + return this.dropdownContent.length > 0 ? 'js-builds-dropdown-container' : 'js-builds-dropdown-loading'; + }, + + triggerButtonClass() { + return `ci-status-icon-${this.stage.status.group}`; + }, + + svgIcon() { + return StatusIconEntityMap[this.stage.status.icon]; + }, + }, +}; +</script> + +<template> + <div class="dropdown"> + <button + :class="triggerButtonClass" + @click="onClickStage" + class="mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button" + :title="stage.title" + data-placement="top" + data-toggle="dropdown" + type="button" + id="stageDropdown" + aria-haspopup="true" + aria-expanded="false"> + + <span + v-html="svgIcon" + aria-hidden="true" + :aria-label="stage.title"> + </span> + + <i + class="fa fa-caret-down" + aria-hidden="true"> + </i> + </button> + + <ul + class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container" + aria-labelledby="stageDropdown"> + + <li + :class="dropdownClass" + class="js-builds-dropdown-list scrollable-menu"> + + <div + class="text-center" + v-if="isLoading"> + <i + class="fa fa-spin fa-spinner" + aria-hidden="true" + aria-label="Loading"> + </i> + </div> + + <ul + v-else + v-html="dropdownContent"> + </ul> + </li> + </ul> + </div> +</script> diff --git a/app/assets/javascripts/pipelines/pipelines.js b/app/assets/javascripts/pipelines/pipelines.js index 93d4818231f..934bd7deb31 100644 --- a/app/assets/javascripts/pipelines/pipelines.js +++ b/app/assets/javascripts/pipelines/pipelines.js @@ -49,6 +49,7 @@ export default { isLoading: false, hasError: false, isMakingRequest: false, + updateGraphDropdown: false, }; }, @@ -198,15 +199,21 @@ export default { this.store.storePagination(response.headers); this.isLoading = false; + this.updateGraphDropdown = true; }, errorCallback() { this.hasError = true; this.isLoading = false; + this.updateGraphDropdown = false; }, setIsMakingRequest(isMakingRequest) { this.isMakingRequest = isMakingRequest; + + if (isMakingRequest) { + this.updateGraphDropdown = false; + } }, }, @@ -263,7 +270,9 @@ export default { <pipelines-table-component :pipelines="state.pipelines" - :service="service"/> + :service="service" + :update-graph-dropdown="updateGraphDropdown" + /> </div> <gl-pagination diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js b/app/assets/javascripts/vue_shared/components/pipelines_table.js index afd8d7acf6b..48a39f18112 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table.js +++ b/app/assets/javascripts/vue_shared/components/pipelines_table.js @@ -10,13 +10,18 @@ export default { pipelines: { type: Array, required: true, - default: () => ([]), }, service: { type: Object, required: true, }, + + updateGraphDropdown: { + type: Boolean, + required: false, + default: false, + }, }, components: { @@ -40,7 +45,9 @@ export default { v-bind:model="model"> <tr is="pipelines-table-row-component" :pipeline="model" - :service="service"></tr> + :service="service" + :update-graph-dropdown="updateGraphDropdown" + /> </template> </tbody> </table> diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js index 79806bc7204..fbae85c85f6 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js +++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js @@ -3,7 +3,7 @@ import AsyncButtonComponent from '../../pipelines/components/async_button.vue'; import PipelinesActionsComponent from '../../pipelines/components/pipelines_actions'; import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts'; import PipelinesStatusComponent from '../../pipelines/components/status'; -import PipelinesStageComponent from '../../pipelines/components/stage'; +import PipelinesStageComponent from '../../pipelines/components/stage.vue'; import PipelinesUrlComponent from '../../pipelines/components/pipeline_url'; import PipelinesTimeagoComponent from '../../pipelines/components/time_ago'; import CommitComponent from './commit'; @@ -24,6 +24,12 @@ export default { type: Object, required: true, }, + + updateGraphDropdown: { + type: Boolean, + required: false, + default: false, + }, }, components: { @@ -213,7 +219,10 @@ export default { <div class="stage-container dropdown js-mini-pipeline-graph" v-if="pipeline.details.stages.length > 0" v-for="stage in pipeline.details.stages"> - <dropdown-stage :stage="stage"/> + + <dropdown-stage + :stage="stage" + :update-dropdown="updateGraphDropdown"/> </div> </td> diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a4fe652b52f..9115d26c779 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -781,16 +781,11 @@ } .scrollable-menu { + padding: 0; max-height: 245px; overflow: auto; } - // Loading icon - .builds-dropdown-loading { - margin: 0 auto; - width: 20px; - } - // Action icon on the right a.ci-action-icon-wrapper { color: $action-icon-color; @@ -893,30 +888,29 @@ * Top arrow in the dropdown in the mini pipeline graph */ .mini-pipeline-graph-dropdown-menu { - .arrow-up { - &::before, - &::after { - content: ''; - display: inline-block; - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - top: -6px; - left: 2px; - border-width: 0 5px 6px; - } - &::before { - border-width: 0 5px 5px; - border-bottom-color: $border-color; - } + &::before, + &::after { + content: ''; + display: inline-block; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + top: -6px; + left: 2px; + border-width: 0 5px 6px; + } - &::after { - margin-top: 1px; - border-bottom-color: $white-light; - } + &::before { + border-width: 0 5px 5px; + border-bottom-color: $border-color; + } + + &::after { + margin-top: 1px; + border-bottom-color: $white-light; } } diff --git a/app/views/shared/_mini_pipeline_graph.html.haml b/app/views/shared/_mini_pipeline_graph.html.haml index b0778653d4e..07970ad9cba 100644 --- a/app/views/shared/_mini_pipeline_graph.html.haml +++ b/app/views/shared/_mini_pipeline_graph.html.haml @@ -11,8 +11,8 @@ = icon('caret-down') %ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container - .arrow-up - .js-builds-dropdown-list.scrollable-menu + %li.js-builds-dropdown-list.scrollable-menu - .js-builds-dropdown-loading.builds-dropdown-loading.hidden - %span.fa.fa-spinner.fa-spin + %li.js-builds-dropdown-loading.hidden + .text-center + %i.fa.fa-spinner.fa-spin{ 'aria-hidden': 'true', 'aria-label': 'Loading' } |