From c13c0d9937c05176031ad0163817afd2fa8f20c9 Mon Sep 17 00:00:00 2001 From: Ming Hay Luk Date: Wed, 10 Jul 2024 15:44:12 -0700 Subject: [PATCH 1/4] New pipeline header component --- app/components/pipeline/header/component.js | 99 ++++++++++++++ app/components/pipeline/header/styles.scss | 84 ++++++++++++ app/components/pipeline/header/template.hbs | 90 +++++++++++++ app/components/pipeline/styles.scss | 2 + .../pipeline/header/component-test.js | 127 ++++++++++++++++++ 5 files changed, 402 insertions(+) create mode 100644 app/components/pipeline/header/component.js create mode 100644 app/components/pipeline/header/styles.scss create mode 100644 app/components/pipeline/header/template.hbs create mode 100644 tests/integration/components/pipeline/header/component-test.js diff --git a/app/components/pipeline/header/component.js b/app/components/pipeline/header/component.js new file mode 100644 index 000000000..342b5ebc4 --- /dev/null +++ b/app/components/pipeline/header/component.js @@ -0,0 +1,99 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { service } from '@ember/service'; +import { action } from '@ember/object'; + +export default class PipelineHeaderComponent extends Component { + @service scm; + + @service shuttle; + + @tracked addToCollectionModalOpen = false; + + @tracked errorMessage; + + @tracked collections = null; + + sameRepoPipeline = []; + + get sonarBadgeUri() { + return ( + this.args.pipeline.badges.sonar.uri || + this.args.pipeline.badges.sonar.defaultUri + ); + } + + get sonarBadgeDescription() { + const sonarBadgeDescription = 'SonarQube project'; + const sonarBadgeName = + this.args.pipeline.badges.sonar.name || + this.args.pipeline.badges.sonar.defaultName; + + return sonarBadgeName + ? `SonarQube project: ${sonarBadgeName}` + : sonarBadgeDescription; + } + + get scmContext() { + const scm = this.scm.getScm(this.args.pipeline.scmContext); + + return { + scm: scm.displayName, + scmIcon: scm.iconType + }; + } + + @action + async getPipelinesWithSameRepo() { + const pipelineId = this.args.pipeline.id; + + if (this.args.pipeline.scmRepo && this.args.pipeline.scmUri) { + const [scm, repositoryId] = this.args.pipeline.scmUri.split(':'); + + this.sameRepoPipeline = await this.shuttle + .fetchFromApi( + 'get', + `/pipelines?search=${this.args.pipeline.scmRepo.name}&sortBy=name&sort=ascending` + ) + .then(pipelines => + pipelines + .filter(pipeline => { + const [s, r] = pipeline.scmUri.split(':'); + + return ( + pipeline.id !== pipelineId && scm === s && repositoryId === r + ); + }) + .map((pipeline, i) => ({ + index: i, + url: `/pipelines/${pipeline.id}`, + branchAndRootDir: pipeline.scmRepo.rootDir + ? `${pipeline.scmRepo.branch}:${pipeline.scmRepo.rootDir}` + : pipeline.scmRepo.branch + })) + .sort((l, r) => + l.branchAndRootDir.localeCompare(r.branchAndRootDir) + ) + ); + } + } + + @action + async openAddToCollectionModal() { + if (!this.collections) { + this.collections = await this.shuttle + .fetchFromApi('get', '/collections') + .catch(err => { + this.errorMessage = `Could not get collections. ${err.message}`; + }); + } + + this.addToCollectionModalOpen = true; + } + + @action + closeAddToCollectionModal(collections) { + this.addToCollectionModalOpen = false; + this.collections = collections; + } +} diff --git a/app/components/pipeline/header/styles.scss b/app/components/pipeline/header/styles.scss new file mode 100644 index 000000000..0b741d252 --- /dev/null +++ b/app/components/pipeline/header/styles.scss @@ -0,0 +1,84 @@ +@use 'screwdriver-colors' as colors; +@use 'screwdriver-button' as button; +@use 'variables'; + +@mixin styles { + #pipeline-header { + display: flex; + background-color: colors.$sd-flyout-bg; + padding: 1rem; + + a { + color: colors.$sd-black; + } + + .header-item { + margin-top: auto; + margin-bottom: auto; + padding-right: 0.5rem; + + &.pipeline-name { + font-size: 24px; + font-weight: variables.$weight-bold; + } + + .sonarqube { + height: 1rem; + width: 1rem; + } + + &.dropdown { + max-width: 20rem; + + .dropdown-toggle { + display: flex; + justify-content: space-between; + + svg { + margin: auto 0.25rem; + } + + &::after { + margin-top: auto; + margin-bottom: 0.75rem; + } + + .branch { + overflow: auto; + } + } + + .dropdown-menu { + display: flex; + flex-direction: column; + overflow-x: scroll; + max-height: 10rem; + width: 20rem; + + svg { + width: 10px; + height: 10px; + } + + a { + padding: 0.25rem 0.75rem; + + &:hover { + background-color: colors.$sd-highlight; + } + } + + span { + margin: auto; + } + } + } + } + + @include button.styles; + + #add-to-collection { + margin-left: auto; + } + } +} diff --git a/app/components/pipeline/header/template.hbs b/app/components/pipeline/header/template.hbs new file mode 100644 index 000000000..085f6a75e --- /dev/null +++ b/app/components/pipeline/header/template.hbs @@ -0,0 +1,90 @@ +
+ + {{@pipeline.scmRepo.name}} + + + {{#if @pipeline.configPipelineId}} + + + Parent Pipeline + + {{/if}} + + {{#if @pipeline.badges.sonar}} + + {{svg-jar "sonarqube" class="img sonarqube"}} + + {{/if}} + + + + {{this.scmContext.scm}} + + + + + + + {{@pipeline.scmRepo.branch}} + + + Switch to another Pipeline with the same repository + + + + {{#each (await this.sameRepoPipeline) as |pipe|}} + + + {{svg-jar "link" class="img"}} {{pipe.branchAndRootDir}} + + + {{else}} + + No other Pipelines with the same repository + + {{/each}} + + + + + + {{#if this.addToCollectionModalOpen}} + + {{/if}} +
\ No newline at end of file diff --git a/app/components/pipeline/styles.scss b/app/components/pipeline/styles.scss index 0976d4cbf..2cb4520c9 100644 --- a/app/components/pipeline/styles.scss +++ b/app/components/pipeline/styles.scss @@ -1,10 +1,12 @@ @use 'nav/styles' as nav; +@use 'header/styles' as header; @use 'modal/confirm-action/styles' as confirm-action-modal; @use 'modal/toggle-job/styles' as toggle-job; @use 'parameters/styles' as parameters; @mixin styles { @include nav.styles; + @include header.styles; @include confirm-action-modal.styles; @include toggle-job.styles; @include parameters.styles; diff --git a/tests/integration/components/pipeline/header/component-test.js b/tests/integration/components/pipeline/header/component-test.js new file mode 100644 index 000000000..1c5411fc1 --- /dev/null +++ b/tests/integration/components/pipeline/header/component-test.js @@ -0,0 +1,127 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'screwdriver-ui/tests/helpers'; +import { click, render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import sinon from 'sinon'; + +module('Integration | Component | pipeline/header', function (hooks) { + setupRenderingTest(hooks); + + test('it renders core items', async function (assert) { + this.setProperties({ + pipeline: { + id: 123, + scmContext: 'github:github.com', + scmRepo: { url: 'https://gihub.com/test' } + } + }); + + await render(hbs``); + + assert + .dom('#pipeline-link') + .hasAttribute('href', `/v2/pipelines/${this.pipeline.id}`); + assert.dom('#parent-pipeline-link').doesNotExist(); + assert.dom('#sonarqube-link').doesNotExist(); + assert + .dom('#scm-link') + .hasAttribute('href', `${this.pipeline.scmRepo.url}`); + assert.dom('#repo-pipelines').exists({ count: 1 }); + assert.dom('#add-to-collection').exists({ count: 1 }); + }); + + test('it renders link to parent pipeline', async function (assert) { + this.setProperties({ + pipeline: { configPipelineId: 999 } + }); + + await render(hbs``); + + assert + .dom('#parent-pipeline-link') + .hasAttribute('href', `/v2/pipelines/${this.pipeline.configPipelineId}`); + }); + + test('it renders link to sonarqube project', async function (assert) { + this.setProperties({ + pipeline: { + badges: { sonar: { uri: 'https://sonarqube.com/test' } } + } + }); + + await render(hbs``); + + assert + .dom('#sonarqube-link') + .hasAttribute('href', `${this.pipeline.badges.sonar.uri}`); + }); + + test('it renders link to sonarqube', async function (assert) { + this.setProperties({ + pipeline: { + badges: { + sonar: { defaultUri: 'https://sonarqube.com' } + } + } + }); + + await render(hbs``); + + assert + .dom('#sonarqube-link') + .hasAttribute('href', `${this.pipeline.badges.sonar.defaultUri}`); + }); + + test('it renders dropdown to other pipelines', async function (assert) { + const shuttle = this.owner.lookup('service:shuttle'); + + sinon.stub(shuttle, 'fetchFromApi').resolves([ + { + id: 123, + scmUri: 'git.github.com:9876', + scmRepo: { branch: 'abc' } + }, + { + id: 124, + scmUri: 'git.github.com:9876', + scmRepo: { branch: 'def' } + }, + { + id: 125, + scmUri: 'git.github.com:9876', + scmRepo: { branch: 'ghi' } + } + ]); + + this.setProperties({ + pipeline: { + id: 123, + scmUri: 'git.github.com:9876', + scmRepo: { url: 'https://gihub.com/test' } + } + }); + + await render(hbs``); + + assert.dom('#repo-pipelines .dropdown-menu').doesNotExist(); + await click('#repo-pipelines > a.dropdown-toggle'); + + assert.dom('#repo-pipelines .dropdown-menu').exists({ count: 1 }); + assert.dom('#repo-pipelines .dropdown-menu > a').exists({ count: 2 }); + }); +}); From 58dd451236e58b016e1ee7640f836c466b8de479 Mon Sep 17 00:00:00 2001 From: Ming Hay Luk Date: Wed, 10 Jul 2024 18:58:56 -0700 Subject: [PATCH 2/4] Integrate new header component --- app/v2/pipeline/controller.js | 12 ------------ app/v2/pipeline/template.hbs | 7 ++++--- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/app/v2/pipeline/controller.js b/app/v2/pipeline/controller.js index a54ce51f5..1ff8ed4b2 100644 --- a/app/v2/pipeline/controller.js +++ b/app/v2/pipeline/controller.js @@ -1,6 +1,5 @@ import Controller from '@ember/controller'; import { service } from '@ember/service'; -import { action } from '@ember/object'; export default class NewPipelineController extends Controller { @service session; @@ -24,15 +23,4 @@ export default class NewPipelineController extends Controller { 'v2.pipeline.jobs.index' ].includes(currentRouteName); } - - @action - addToCollection(pipelineId, collection) { - const { pipelineIds } = collection; - - if (!pipelineIds.includes(pipelineId)) { - collection.set('pipelineIds', [...pipelineIds, pipelineId]); - } - - return collection.save(); - } } diff --git a/app/v2/pipeline/template.hbs b/app/v2/pipeline/template.hbs index 61497c890..30dff0e80 100644 --- a/app/v2/pipeline/template.hbs +++ b/app/v2/pipeline/template.hbs @@ -2,9 +2,10 @@
-
- -
+
{{outlet}} From 8097a26902a601516bf81cce6f13dfe56e89b92d Mon Sep 17 00:00:00 2001 From: Ming Hay Luk Date: Wed, 10 Jul 2024 19:06:51 -0700 Subject: [PATCH 3/4] Add newline --- app/components/pipeline/header/template.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/pipeline/header/template.hbs b/app/components/pipeline/header/template.hbs index 085f6a75e..af47a67a3 100644 --- a/app/components/pipeline/header/template.hbs +++ b/app/components/pipeline/header/template.hbs @@ -87,4 +87,4 @@ @closeModal={{this.closeAddToCollectionModal}} /> {{/if}} -
\ No newline at end of file +
From 6dd17c931050434afa9b17704ebb4cfdc004d06e Mon Sep 17 00:00:00 2001 From: Ming Hay Luk Date: Wed, 10 Jul 2024 19:22:06 -0700 Subject: [PATCH 4/4] Update grid area with right selector --- app/v2/pipeline/styles.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/v2/pipeline/styles.scss b/app/v2/pipeline/styles.scss index 5c2b2fd62..c58057f2b 100644 --- a/app/v2/pipeline/styles.scss +++ b/app/v2/pipeline/styles.scss @@ -12,7 +12,7 @@ 'pipeline-nav pipeline-header' 'pipeline-nav pipeline-main'; - .pipeline-header { + #pipeline-header { grid-area: pipeline-header; }