aboutsummaryrefslogtreecommitdiffstats
path: root/catalog-ui/src/app/ng2/pages/workspace/disribution
diff options
context:
space:
mode:
Diffstat (limited to 'catalog-ui/src/app/ng2/pages/workspace/disribution')
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.html62
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.less78
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.spec.ts90
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.ts68
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.html47
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.less66
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.spec.ts47
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.ts104
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.html80
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.less92
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.spec.ts92
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.ts117
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.module.ts34
-rw-r--r--catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.service.ts233
14 files changed, 1210 insertions, 0 deletions
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.html b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.html
new file mode 100644
index 0000000000..574f2d1bb4
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.html
@@ -0,0 +1,62 @@
+<div class="status-page">
+ <ngx-datatable
+ class="material"
+ [columnMode]="'standard'"
+ [rowHeight]="'auto'"
+ [reorderable]="false"
+ [swapColumns]="false"
+ [rows]="artifacts"
+ [scrollbarH]="true"
+ #statusTable>
+ <ngx-datatable-row-detail [rowHeight]="'auto'">
+ <ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template>
+ <div *ngFor="let status of row.statuses">
+ <span class = "status" [attr.data-tests-id]="generateDataTestID('statusTimeStamp_',componentName, row.name, status.status)">{{ status.timeStamp | date:'short':'UTC'}}</span>
+ <span class = "status" [attr.data-tests-id]="generateDataTestID('statusValue_',componentName, row.name, status.status)">{{ status.status }}</span>
+ </div>
+ </ng-template>
+ </ngx-datatable-row-detail>
+ <ngx-datatable-column name="Component ID" [resizeable]="false" [width]="250">
+ <ng-template ngx-datatable-cell-template let-row="row" let-expanded="expanded" >
+ <div>
+ <span class="urlValue">
+ <svg-icon [clickable]="true" class="expand-collapse-icon"
+ [name]="expanded ? 'caret1-up-o': 'caret1-down-o'" [mode]="'primary'"
+ [size]="'medium'" (click)="expandRow(row)" [attr.data-tests-id]="generateDataTestID('expandIcon_compID_', componentName, row.name)"></svg-icon>
+ </span>
+ <span class="urlValue ellipsisCell" [attr.data-tests-id]="generateDataTestID('compID_',componentName, row.name)" sdc-tooltip [tooltip-placement]="3" [tooltip-text]="componentName">
+ {{ componentName }}
+ </span>
+ </div>
+ </ng-template>
+ </ngx-datatable-column>
+ <ngx-datatable-column [resizeable]="false" [width]="280" name="Artifact Name">
+ <ng-template ngx-datatable-cell-template let-row="row">
+ <div class = "distributionRowValue ellipsisCell" [attr.data-tests-id]="generateDataTestID('artName_',componentName, row.name)" sdc-tooltip [tooltip-placement]="3" [tooltip-text]="row.name">{{ row.name }}</div>
+ </ng-template>
+ </ngx-datatable-column>
+ <ngx-datatable-column [resizeable]="false" [width]="380" name="URL">
+ <ng-template ngx-datatable-cell-template let-row="row">
+ <div>
+ <span class="urlValue ellipsisCell" id="urlCell" [attr.data-tests-id]="generateDataTestID('url_',componentName, row.name)">{{ row.url }}</span>
+ <span class="urlCopyIcon" title="Copy URL">
+ <svg-icon-label [clickable]="true" [mode]="'primary'" [labelPlacement]="'right'"
+ [label]="" [name]="'copy-o'" [testId]="'copyToClipboard'"
+ (click)="copyToClipboard(row.url)">
+ </svg-icon-label>
+ </span>
+ </div>
+ </ng-template>
+ </ngx-datatable-column>
+ <ngx-datatable-column [resizeable]="false" [width]="180" name="Time(UTC)">
+ <ng-template ngx-datatable-cell-template let-row="row">
+ <div class = "distributionRowValue ellipsisCell" [attr.data-tests-id]="generateDataTestID('time_',componentName, row.name)" sdc-tooltip [tooltip-placement]="3" [tooltip-text]="getLatestArtifact(row.name).timeStamp | date:'short':'UTC'">{{ getLatestArtifact(row.name).timeStamp | date:'short':'UTC'}}</div>
+ </ng-template>
+ </ngx-datatable-column>
+ <ngx-datatable-column [resizeable]="false" [width]="280" name="Status">
+ <ng-template ngx-datatable-cell-template let-row="row">
+ <div class = "distributionRowValue ellipsisCell" [attr.data-tests-id]="generateDataTestID('status_',componentName, row.name)" sdc-tooltip [tooltip-placement]="3" [tooltip-text]="getLatestArtifact(row.name).status">{{ getLatestArtifact(row.name).status }}</div>
+ </ng-template>
+ </ngx-datatable-column>
+ </ngx-datatable>
+</div> \ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.less b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.less
new file mode 100644
index 0000000000..81b8805792
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.less
@@ -0,0 +1,78 @@
+:host ::ng-deep {
+ .ngx-datatable {
+ > div {
+ min-height: 5px;
+ }
+ }
+}
+
+.datatable-header-cell {
+ text-align: left;
+ color: red;
+}
+
+.statusHeaderTable {
+ color: #000000;
+ font-family: OpenSans-Bold, sans-serif;
+ font-size: 12px;
+ font-weight: bold;
+ float: left;
+}
+
+.status {
+ padding-right: 30px;
+ color: #5a5a5a;
+ font-family: OpenSans-Regular, sans-serif;
+ font-size: 12px;
+}
+
+.distributionIDBlock {
+ display: inline-block;
+}
+
+.distributionRowContainer{
+ background-color: #eaeaea;
+ text-align: center;
+}
+
+.distributionRowLabel {
+ overflow: hidden;
+ padding-top: 10px;
+ color: #000000;
+ font-family: OpenSans-Semibold, sans-serif;
+ font-size: 12px;
+ font-weight: bold;
+}
+
+.distributionRowValue {
+ color: #263d4d;
+ font-family: OpenSans-Regular, sans-serif;
+ font-size: 14px;
+}
+
+.urlValue {
+ float: left;
+ color: #263d4d;
+ font-family: OpenSans-Regular, sans-serif;
+ font-size: 14px;
+}
+
+.urlCopyIcon {
+ float: right;
+ width: 8%;
+}
+
+.ellipsisCell {
+ width: 92%;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+
+
+
+
+
+
+
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.spec.ts
new file mode 100644
index 0000000000..72b930b6b8
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.spec.ts
@@ -0,0 +1,90 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { ComponentFixture } from '@angular/core/testing';
+import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+import { SdcUiServices } from 'onap-ui-angular';
+import { ConfigureFn, configureTests } from '../../../../../../../jest/test-config.helper';
+import { DistributionService } from '../../distribution.service';
+import { DistributionComponentArtifactTableComponent } from './distribution-component-artifact-table.component';
+
+describe('DistributionComponentArtifactTableComponent', () => {
+ let fixture: ComponentFixture<DistributionComponentArtifactTableComponent>;
+ let distibutionServiceMock: Partial<DistributionService>;
+
+ const mockArtifactsForDistributionAndComponentName = [
+ {
+ name: 'Artifact1',
+ statuses: [
+ {timeStamp: '7/25/2019 12:48AM', status: 'DEPLOY_OK'},
+ {timeStamp: '7/25/2019 12:48AM', status: 'DOWNLOAD_OK'},
+ {timeStamp: '7/25/2019 12:48AM', status: 'NOTIFIED'}
+ ],
+ url: 'URL1',
+ },
+ {
+ name: 'Artifact2',
+ statuses: [
+ {timeStamp: '7/26/2019 12:48AM', status: 'STATUS_TO_DISPLAY'},
+ {timeStamp: '7/25/2019 12:48AM', status: 'DOWNLOAD_OK'},
+ {timeStamp: '7/25/2019 12:48AM', status: 'NOTIFIED'}
+ ],
+ url: 'URL2',
+ },
+ {
+ name: 'ArtifactWithNoStatuses',
+ url: 'URL2',
+ }
+ ];
+
+ beforeEach(() => {
+
+ distibutionServiceMock = {
+ getArtifactstByDistributionIDAndComponentsName: jest.fn().mockReturnValue(mockArtifactsForDistributionAndComponentName),
+ };
+
+ const configure: ConfigureFn = (testBed) => {
+ testBed.configureTestingModule({
+ declarations: [DistributionComponentArtifactTableComponent],
+ imports: [NgxDatatableModule],
+ schemas: [NO_ERRORS_SCHEMA],
+ providers: [
+ {provide: DistributionService, useValue: distibutionServiceMock}
+ ],
+ });
+ };
+
+ configureTests(configure).then((testBed) => {
+ fixture = testBed.createComponent(DistributionComponentArtifactTableComponent);
+ });
+
+ });
+
+ it('Get Latest Artifact (status and timeStamp) - So the Component Table will display the last time stamp of the notification', async () => {
+ await fixture.componentInstance.ngOnInit();
+ expect(fixture.componentInstance.getLatestArtifact('Artifact2')).toEqual({status: 'STATUS_TO_DISPLAY', timeStamp: '7/26/2019 12:48AM'});
+ expect(fixture.componentInstance.getLatestArtifact('ArtifactWithNoStatuses')).toEqual(null);
+ });
+
+ it('Once the Distribution Component Artifact Table Component is created - artifacts will keep the relevant artifacts for a specific distributionID and Component Name', async () => {
+ await fixture.componentInstance.ngOnInit();
+ // tslint:disable:no-string-literal
+ expect(fixture.componentInstance.artifacts.length).toBe(3);
+ expect(fixture.componentInstance.artifacts[0].name).toBe('Artifact1');
+ expect(fixture.componentInstance.artifacts[0].url).toBe('URL1');
+ expect(fixture.componentInstance.artifacts[0].statuses.length).toBe(3);
+
+ expect(fixture.componentInstance.artifacts[1].name).toBe('Artifact2');
+ });
+
+ it('Once the Distribution Component Artifact Table Component is created for Modal- artifacts will keep the relevant artifacts for a ' +
+ 'specific distributionID and Component Name filtered by Status', async () => {
+ fixture.componentInstance.statusFilter = 'DOWNLOAD_OK';
+ await fixture.componentInstance.ngOnInit();
+ expect(fixture.componentInstance.artifacts.length).toBe(3);
+ expect(fixture.componentInstance.artifacts[0].name).toBe('Artifact1');
+ expect(fixture.componentInstance.artifacts[0].url).toBe('URL1');
+
+ expect(fixture.componentInstance.artifacts[0].statuses.length).toBe(1);
+ expect(fixture.componentInstance.artifacts[0].statuses[0]).toEqual({status: 'DOWNLOAD_OK', timeStamp: '7/25/2019 12:48AM'});
+
+ });
+});
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.ts
new file mode 100644
index 0000000000..af9aef5c64
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.ts
@@ -0,0 +1,68 @@
+import { Component, Input, OnInit, ViewChild } from '@angular/core';
+import * as _ from 'lodash';
+import { DistributionService } from '../../distribution.service';
+
+// tslint:disable:no-string-literal
+
+@Component({
+ selector: 'app-distribution-component-artifact-table',
+ templateUrl: './distribution-component-artifact-table.component.html',
+ styleUrls: ['./distribution-component-artifact-table.component.less']
+})
+export class DistributionComponentArtifactTableComponent implements OnInit {
+
+ @ViewChild('statusTable', {}) table: any;
+
+ @Input() componentName: string;
+ @Input() rowDistributionID: string;
+ @Input() statusFilter: string;
+
+ public artifacts = [];
+
+ constructor(private distributionService: DistributionService) {
+ }
+
+ ngOnInit() {
+ this.artifacts = this.distributionService.getArtifactstByDistributionIDAndComponentsName(this.rowDistributionID, this.componentName);
+ if (this.statusFilter) {
+ this.artifacts.forEach(
+ (artifact) => {
+ artifact.statuses = _.filter(artifact.statuses, {status: this.statusFilter});
+ });
+ }
+ }
+
+ public getLatestArtifact(artifactName: string) {
+ const selectedArtifact = this.artifacts.filter((artifact) => artifact.name === artifactName);
+ if (selectedArtifact && selectedArtifact[0] && selectedArtifact[0]['statuses'] && selectedArtifact[0]['statuses'][0]) {
+ return selectedArtifact[0]['statuses'][0];
+ } else {
+ return null;
+ }
+ }
+
+ private copyToClipboard(urlToCopy: any) {
+
+ const inputForCopyToClipboard = document.getElementById('inputForCopyToClipboard') as HTMLInputElement;
+ inputForCopyToClipboard.value = urlToCopy;
+ /* Select the text field */
+ inputForCopyToClipboard.select();
+
+ /* Copy the text inside the text field */
+ document.execCommand('copy');
+
+ }
+
+ private generateDataTestID(preFix: string, componentName: string, artifactName: string, status?: string) {
+ if (!status) {
+ return preFix + componentName + '_' + artifactName;
+ } else {
+ return preFix + status + '_' + componentName + '_' + artifactName;
+ }
+ }
+
+ private expandRow(row: any) {
+ this.table.rowDetail.toggleExpandRow(row);
+ }
+
+}
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.html b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.html
new file mode 100644
index 0000000000..fa5a9ad7fb
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.html
@@ -0,0 +1,47 @@
+<div >
+ <div class="distributionSummary" *ngIf="!isModal">
+ <span class= "rightVerticalSeperator titleSummaryFontSettings" data-tests-id="totalDistributionArtifactsLabel">Total Artifacts <span class="blue" data-tests-id="totalDistributionArtifactsValue">{{ getTotalArtifactsForDistributionID(rowDistributionID) }} </span></span>
+ <span class="blue rightVerticalSeperator" (click)="openModal(rowDistributionID,'NOTIFIED')" data-tests-id="totalDistributionNotifiedArtifactsLabel">Notified <span data-tests-id="totalDistributionNotifiedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'NOTIFIED') }}</span></span>
+ <span class="blue rightVerticalSeperator" (click)="openModal(rowDistributionID,'DOWNLOAD_OK')" data-tests-id="totalDistributionDownloadedArtifactsLabel">Downloaded <span data-tests-id="totalDistributionDownloadedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DOWNLOAD_OK') }}</span></span>
+ <span class="blue rightVerticalSeperator" (click)="openModal(rowDistributionID,'DEPLOY_OK')" data-tests-id="totalDistributionDeployedArtifactsLabel">Deployed <span data-tests-id="totalDistributionDeployedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DEPLOY_OK') }}</span></span>
+ <span class="blue rightVerticalSeperator" (click)="openModal(rowDistributionID,'NOT_NOTIFIED')" data-tests-id="totalDistributionNotNotifiedArtifactsLabel">Not Notified <span data-tests-id="totalDistributionNotNotifiedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'NOT_NOTIFIED') }}</span></span>
+ <span class="blue rightVerticalSeperator floatRight" (click)="openModal(rowDistributionID,'DEPLOY_ERROR')" data-tests-id="totalDistributionDeployErrorArtifactsLabel">Deploy Errors <span class="red" data-tests-id="totalDistributionDeployErrorArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DEPLOY_ERROR') }}</span></span>
+ <span class="blue rightVerticalSeperator floatRight" (click)="openModal(rowDistributionID,'DOWNLOAD_ERROR ')" data-tests-id="totalDistributionDownloadErrorArtifactsLabel">Download Errors <span class="red" data-tests-id="totalDistributionDownloadErrorArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DOWNLOAD_ERROR') }}</span></span>
+ </div>
+
+ <div class="distributionSummary" *ngIf="isModal">
+ <span data-tests-id="modalStatusLabel"><a>Status {{ statusFilter }} <span class="blue" data-tests-id="statusValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, statusFilter) }}</span></a></span>
+ </div>
+
+
+ <div class="componentShiftLeft" *ngFor="let component of components">
+ <div class="componentSummary" *ngIf="!isModal">
+ <svg-icon [clickable]="true" class="expand-collapse-icon"
+ [name]="isExpanded(component) ? 'caret1-up-o': 'caret1-down-o'" [mode]="'primary'"
+ [size]="'medium'" [attr.data-tests-id]="generateExpandDataTestID(component)" (click)="expandRow(component)"></svg-icon>
+
+
+ <span class="rightVerticalSeperatorComponent titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, '')">{{ component }} <span class="blue" data-tests-id="totalComponentArtifactsValue">{{ getTotalArtifactsForDistributionID(rowDistributionID, component) }}</span></span>
+ <span class="rightVerticalSeperatorComponent titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, 'Notified')">Notified <span class="blue" data-tests-id="totalComponentNotifiedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'NOTIFIED', component) }}</span></span>
+ <span class="rightVerticalSeperatorComponent titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, 'Downloaded')">Downloaded <span class="blue" data-tests-id="totalComponentDownloadedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DOWNLOAD_OK', component) }}</span></span>
+ <span class="rightVerticalSeperatorComponent titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, 'Deployed')">Deployed <span class="blue" data-tests-id="totalComponentDeployedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DEPLOY_OK', component) }}</span></span>
+ <span class="rightVerticalSeperatorComponent titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, 'NotNotified')">Not Notified <span class="blue" data-tests-id="totalComponentNotNotifiedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'NOT_NOTIFIED', component) }}</span></span>
+ <span class="msoStatus" [ngClass]="{'red': getMSOStatus (rowDistributionID, component) === 'COMPONENT_DONE_ERROR', 'green': getMSOStatus (rowDistributionID, component) === 'COMPONENT_DONE_OK'}">{{ getMSOStatus (rowDistributionID, component) }}</span>
+ <span class="rightVerticalSeperatorComponent floatRight titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, 'DeployErrors')">Deploy Errors <span class="red" data-tests-id="totalComponentDeployErrorArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DEPLOY_ERROR', component) }}</span></span>
+ <span class="rightVerticalSeperatorComponent floatRight titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, 'DownloadErrors')">Download Errors <span class="red" data-tests-id="totalComponentDownloadErrorArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DOWNLOAD_ERROR', component) }}</span></span>
+ </div>
+
+ <div class="componentSummary" *ngIf="isModal">
+ <svg-icon [clickable]="true" class="expand-collapse-icon"
+ [name]="isExpanded(component) ? 'caret1-up-o': 'caret1-down-o'" [mode]="'primary'"
+ [size]="'medium'" [attr.data-tests-id]="generateExpandDataTestID(component+'_ForModal')" (click)="expandRow(component)"></svg-icon>
+ <span data-tests-id="modalComponentLabel"><a>{{ component }} <span class="blue" data-tests-id="modalComponentValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, statusFilter, component) }} </span></a></span>
+ </div>
+
+ <div *ngIf="isExpanded(component)">
+ <app-distribution-component-artifact-table [rowDistributionID]= rowDistributionID [componentName]=component
+ [statusFilter]="statusFilter"></app-distribution-component-artifact-table>
+ </div>
+ </div>
+</div>
+
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.less b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.less
new file mode 100644
index 0000000000..3eab18ca14
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.less
@@ -0,0 +1,66 @@
+.red {
+ color: red;
+}
+
+.green {
+ color: green;
+}
+
+.msoStatus {
+ padding-left: 5px;
+}
+
+.blue {
+ color: #009fdb;
+ font-family: OpenSans-Semibold, sans-serif;
+ font-size: 14px;
+}
+
+.rightVerticalSeperator {
+ border-right: 1px solid #d2d2d2;
+ padding-left: 5px;
+ padding-right: 5px;
+ cursor: pointer;
+}
+
+.rightVerticalSeperatorComponent {
+ border-right: 1px solid #d2d2d2;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+.floatRight{
+ float: right;
+}
+
+.distributionSummary {
+ padding-top: 5px;
+ padding-bottom: 5px;
+ background-color: #eaeaea;
+ padding-left: 25px;
+ padding-right: 25px;
+}
+
+.componentSummary {
+ margin-top: 5px;
+ margin-bottom: 5px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ background-color: #eaeaea;
+ padding-left: 25px;
+ padding-right: 25px;
+}
+
+.componentShiftLeft {
+ margin-left: 15px;
+}
+
+.titleSummaryFontSettings {
+ color: #191919;
+ font-family: OpenSans-Regular, sans-serif;
+ font-size: 14px;
+}
+
+
+
+
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.spec.ts
new file mode 100644
index 0000000000..ff89b92fd8
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.spec.ts
@@ -0,0 +1,47 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { ComponentFixture } from '@angular/core/testing';
+import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+import { SdcUiServices } from 'onap-ui-angular';
+import { ConfigureFn, configureTests } from '../../../../../../jest/test-config.helper';
+import { DistributionService } from '../distribution.service';
+import { DistributionComponentTableComponent } from './distribution-component-table.component';
+
+describe('DistributionComponentTableComponent', () => {
+ let fixture: ComponentFixture<DistributionComponentTableComponent>;
+ let distibutionServiceMock: Partial<DistributionService>;
+
+ const mockComponentsForDistribution = ['Consumer1', 'Consumer2'];
+
+ beforeEach(() => {
+
+ distibutionServiceMock = {
+ getComponentsByDistributionID: jest.fn().mockReturnValue(mockComponentsForDistribution),
+ getArtifactstByDistributionIDAndComponentsName: jest.fn(),
+ getArtifactsForDistributionIDAndComponentByStatus: jest.fn()
+ };
+
+ const configure: ConfigureFn = (testBed) => {
+ testBed.configureTestingModule({
+ declarations: [DistributionComponentTableComponent],
+ imports: [NgxDatatableModule],
+ schemas: [NO_ERRORS_SCHEMA],
+ providers: [
+ {provide: DistributionService, useValue: distibutionServiceMock},
+ {provide: SdcUiServices.ModalService, useValue: {}}
+ ],
+ });
+ };
+
+ configureTests(configure).then((testBed) => {
+ fixture = testBed.createComponent(DistributionComponentTableComponent);
+ });
+
+ });
+
+ it('Once the Distribution Component Table Component is created - components will keep the relevant components for a specific distributionID', async () => {
+ await fixture.componentInstance.ngOnInit();
+ expect(fixture.componentInstance.components.length).toBe(2);
+ expect(fixture.componentInstance.components[0]).toBe('Consumer1');
+ expect(fixture.componentInstance.components[1]).toBe('Consumer2');
+ });
+});
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.ts
new file mode 100644
index 0000000000..e3aaf9d639
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.ts
@@ -0,0 +1,104 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { SdcUiCommon, SdcUiComponents, SdcUiServices } from 'onap-ui-angular';
+import { ModalComponent } from 'onap-ui-angular/dist/modals/modal.component';
+import { DistributionComponent } from '../distribution.component';
+import { DistributionService } from '../distribution.service';
+
+@Component({
+ selector: 'app-distribution-component-table',
+ templateUrl: './distribution-component-table.component.html',
+ styleUrls: ['./distribution-component-table.component.less']
+})
+export class DistributionComponentTableComponent implements OnInit {
+
+ @Input() rowDistributionID: string;
+ @Input() isModal: boolean = false;
+ @Input() statusFilter: string;
+ public components = [];
+ private customModalInstance: ModalComponent;
+ private expanded = [];
+ constructor(private distributionService: DistributionService,
+ private modalService: SdcUiServices.ModalService) {
+ }
+
+ ngOnInit() {
+ this.initComponents();
+ }
+
+ private generateTotalComponentArtifactsLabel(componentName: any, status: string): string {
+ return 'total' + componentName + status + 'ArtifactsLabel';
+ }
+
+ private generateExpandDataTestID(componentName: string) {
+ return 'expandIcon_' + componentName;
+ }
+
+ private initComponents() {
+ this.components = this.distributionService.getComponentsByDistributionID(this.rowDistributionID);
+ this.components.map((component) => this.expanded.push({componentName: component, expanded: false}));
+ }
+
+ private getTotalArtifactsForDistributionID(distributionID: string, componentName?: string): number {
+ return this.distributionService.getArtifactstByDistributionIDAndComponentsName(distributionID, componentName).length;
+ }
+
+ private getLengthArtifactsForDistributionIDByStatus(distributionID: string, statusToSerach: string, componentName?: string): number {
+ if (componentName) {
+ return this.distributionService.getArtifactsForDistributionIDAndComponentByStatus(distributionID, statusToSerach, componentName).length;
+ } else {
+ return this.distributionService.getArtifactsForDistributionIDAndComponentByStatus(distributionID, statusToSerach).length;
+ }
+ }
+
+ private openModal(rowDistributionID: string, statusFilter: string) {
+
+ const title: string = 'Distribution by Status';
+ const attributeModalConfig = {
+ title,
+ size: 'sdc-xl',
+ type: SdcUiCommon.ModalType.custom,
+ buttons: [
+ {
+ id: 'close',
+ text: 'Close',
+ size: 'sm',
+ closeModal: true,
+ disabled: false,
+ }
+ ] as SdcUiCommon.IModalButtonComponent[]
+ };
+
+ this.customModalInstance = this.modalService.openCustomModal(attributeModalConfig, DistributionComponent, {
+ // inputs
+ rowDistributionID,
+ statusFilter,
+ isModal: true,
+ });
+ }
+
+ private expandRow(componentName: string) {
+ console.log('Should expand componentSummary for componentName = ' + componentName);
+ const selectedComponent = this.expanded.find((component) => component.componentName === componentName);
+ // tslint:disable:no-string-literal
+ const selectedComponentExpandedVal = selectedComponent['expanded'];
+ // this.expanded = !this.expanded;
+ for (const i in this.expanded) {
+ if (this.expanded[i].componentName === componentName) {
+ this.expanded[i].expanded = !this.expanded[i].expanded;
+ break; //Stop this loop, we found it!
+ }
+ }
+ const selectedComponentAfter = this.expanded.find((component) => component.componentName === componentName);
+ const selectedComponentExpandedValAfter = selectedComponentAfter['expanded'];
+ }
+
+ private isExpanded(componentName: string) {
+ const selectedComponent = this.expanded.find((component) => component.componentName === componentName);
+ return selectedComponent['expanded'];
+ }
+
+
+ private getMSOStatus(rowDistributionID: string, componentName: string): string {
+ return this.distributionService.getMSOStatus(rowDistributionID, componentName);
+ }
+}
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.html b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.html
new file mode 100644
index 0000000000..d0cacb054e
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.html
@@ -0,0 +1,80 @@
+<div *ngIf="!isModal">
+ <div *ngIf="serviceHasDistibutions" class="w-sdc-distribution-view-header">
+ <div class="w-sdc-distribution-view-title" data-tests-id="DistributionsLabel">DISTRIBUTION <span class="blue-font" data-tests-id="totalArtifacts">[{{distributions.length}}]</span></div>
+ <div class="header-spacer"></div>
+ <input type="text" value="GeeksForGeeks" id="inputForCopyToClipboard" [ngStyle]="{'z-index': '-2', 'width': '25px'}">
+ <div class="top-search">
+ <input type="text"
+ style="width: auto;"
+ class="search-text"
+ data-tests-id="searchTextbox"
+ placeholder="Search"
+ data-ng-model="searchBind"
+ ng-model-options="{ debounce: 500 }"
+ (keyup)="updateFilter($event)"/>
+ </div>
+ <div class="sprite-new refresh-btn" data-tests-id="refreshButton" (click)="refreshDistributions()" title="Refresh"></div>
+ </div>
+ <div class="w-sdc-distribution-view-header w-sdc-distribution-view-title" data-tests-id="noDistributionsLabel" *ngIf="!serviceHasDistibutions">No Distributions To Present</div>
+</div>
+
+<div *ngIf="serviceHasDistibutions">
+ <ngx-datatable
+ [columnMode]="'flex'"
+ [rowHeight]="'auto'"
+ [reorderable]="false"
+ [swapColumns]="false"
+ [scrollbarV]="false"
+ [rows]="distributions"
+ [sorts]="[{prop: 'timestamp', dir: 'desc'}]"
+
+ #distributionTable>
+ <ngx-datatable-row-detail [rowHeight]="'auto'">
+ <ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template>
+ <app-distribution-component-table [rowDistributionID]=row.distributionID [isModal]="isModal"
+ [statusFilter]="statusFilter"></app-distribution-component-table>
+ </ng-template>
+ </ngx-datatable-row-detail>
+ <ngx-datatable-column [resizeable]="false" [flexGrow]="2" name="Distribution ID">
+ <ng-template ngx-datatable-cell-template let-row="row" let-expanded="expanded" >
+ <div class="expand-collapse-cell">
+ <a><svg-icon [clickable]="true" class="expand-collapse-icon"
+ [name]="expanded ? 'caret1-up-o': 'caret1-down-o'" [mode]="'primary'"
+ [size]="'medium'" (click)="expandRow(row, expanded)" [attr.data-tests-id]="generateDataTestID('expandIcon_', row.distributionID, isModal)"></svg-icon></a>
+
+ </div>
+ <div class="distributionIDBlock">
+ <div class = "distributionRowValue" [attr.data-tests-id]="generateDataTestID('distID_', row.distributionID, isModal)">{{ row.distributionID }}</div>
+ </div>
+ </ng-template>
+ </ngx-datatable-column>
+ <ngx-datatable-column [resizeable]="false" [flexGrow]="1" name="User id">
+ <ng-template ngx-datatable-cell-template let-row="row">
+ <div class = "distributionRowValue ellipsisCell" [attr.data-tests-id]="generateDataTestID('userID_', row.distributionID)" sdc-tooltip [tooltip-placement]="3" [tooltip-text]="row.userId">{{ row.userId }}</div>
+ </ng-template>
+ </ngx-datatable-column>
+ <ngx-datatable-column [resizeable]="false" [flexGrow]="1" name="Time[UTC]">
+ <ng-template ngx-datatable-cell-template let-row="row">
+ <div class = "distributionRowValue" [attr.data-tests-id]="generateDataTestID('timeStamp_', row.distributionID)">{{ row.timestamp }} </div>
+ </ng-template>
+ </ngx-datatable-column>
+ <ngx-datatable-column [resizeable]="false"[flexGrow]="1" name="Status" >
+ <ng-template ngx-datatable-cell-template let-row="row">
+ <div>
+ <span class="statusIcon">
+ <svg-icon [clickable]="true" class="expand-collapse-icon"
+ [name]= "getIconName(row.deployementStatus)" [mode]="'primary'"
+ [size]="'medium'"></svg-icon>
+ </span>
+ <span class = "distributionRowValue" [attr.data-tests-id]="generateDataTestID('status_', row.distributionID)">
+ {{ row.deployementStatus }}
+ </span>
+ <span class="btnMarkAsDistributed" (click)="markDeploy(row.distributionID, row.deployementStatus)">
+ <svg-icon [clickable]="true" [name]= "'success'" [mode]="getIconMode(row.deployementStatus)"
+ [size]="'medium'"></svg-icon>
+ </span>
+ </div>
+ </ng-template>
+ </ngx-datatable-column>
+ </ngx-datatable>
+</div>
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.less b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.less
new file mode 100644
index 0000000000..b630881fdc
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.less
@@ -0,0 +1,92 @@
+:host ::ng-deep {
+ .ngx-datatable {
+ > div {
+ min-height: 500px;
+ datatable-body {
+ max-height: max-content;
+ }
+ }
+ }
+}
+
+.w-sdc-distribution-view-header {
+ display: flex;
+ -webkit-justify-content: space-between;
+ margin: 0 25px 5px 40px;
+
+ .header-spacer {
+ flex-grow: 5;
+ }
+
+ .w-sdc-distribution-view-title{
+ color: #191919;
+ font-family: OpenSans-Regular, sans-serif;
+ font-size: 14px;
+ line-height: 30px;
+
+
+ .blue-font {
+ color: #009fdb;
+ font-family: OpenSans-Semibold, sans-serif;
+ font-size: 14px;
+ }
+ }
+
+}
+
+.distribution-page {
+ max-height: 150px;
+}
+
+ .distributionIDBlock {
+ display: inline-block;
+ }
+
+ .expand-collapse-cell {
+ display: inline-block;
+ }
+
+ .statusIcon {
+ display: inline-block;
+ margin-right: 10px;
+ }
+
+ .btnMarkAsDistributed {
+ float: right;
+ background-color: #E5F3FF;
+ border: 1px solid #8DCCD5;
+ width: 55px;
+ height: 21px;
+ text-align: center;
+ }
+
+ .distributionRowContainer{
+ background-color: #eaeaea;
+ text-align: center;
+ }
+
+ .distributionRowLabel {
+ overflow: hidden;
+ padding-top: 10px;
+ color: #000000;
+ font-family: OpenSans-Semibold, sans-serif;
+ font-size: 12px;
+ font-weight: bold;
+ }
+
+ .distributionRowValue {
+ color: #263d4d;
+ font-family: OpenSans-Regular, sans-serif;
+ font-size: 14px;
+ }
+
+.ellipsisCell {
+ width: 92%;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+
+
+
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.spec.ts
new file mode 100644
index 0000000000..e6c9c239e1
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.spec.ts
@@ -0,0 +1,92 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { ComponentFixture } from '@angular/core/testing';
+import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+import { SdcUiServices } from 'onap-ui-angular';
+import { ConfigureFn, configureTests } from '../../../../../jest/test-config.helper';
+import { ComponentMetadata } from '../../../../models/component-metadata';
+import { AuthenticationService } from '../../../services/authentication.service';
+import { WorkspaceService } from '../workspace.service';
+import { DistributionComponent } from './distribution.component';
+import { DistributionService } from './distribution.service';
+import {EventListenerService} from "../../../../services/event-listener-service";
+
+describe('DistributionComponent', () => {
+ let fixture: ComponentFixture<DistributionComponent>;
+ let distibutionServiceMock: Partial<DistributionService>;
+ let workspaceServiceMock: Partial<WorkspaceService>;
+ let loaderServiceMock: Partial<SdcUiServices.LoaderService>;
+ let authenticationServiceMock: Partial <AuthenticationService>;
+ let eventListenerService: Partial <EventListenerService>;
+
+ const mockDistributionListFromService = [
+ {
+ deployementStatus: 'Distributed',
+ distributionID: '1',
+ timestamp: '2019-07-21 08:37:02.834 UTC',
+ userId: 'Aretha Franklin(op0001)'
+ }, {
+ deployementStatus: 'Distributed',
+ distributionID: '2',
+ timestamp: '2019-07-21 09:37:02.834 UTC',
+ userId: 'Aretha Franklin(op0001)'
+ }];
+
+ beforeEach(() => {
+
+ distibutionServiceMock = {
+ initDistributionsList: jest.fn(),
+ getDistributionList: jest.fn().mockReturnValue(mockDistributionListFromService),
+ initDistributionsStatusForDistributionID: jest.fn()
+ };
+
+ const componentMetadata = new ComponentMetadata();
+ componentMetadata.uuid = '111';
+
+ workspaceServiceMock = {
+ metadata : componentMetadata
+ };
+
+ authenticationServiceMock = {
+ getLoggedinUser: jest.fn().mockReturnValue({role: 'designer'})
+ };
+
+ eventListenerService = {
+ registerObserverCallback: jest.fn(),
+ unRegisterObserver: jest.fn()
+ }
+
+ loaderServiceMock = {
+ activate: jest.fn(),
+ deactivate: jest.fn()
+ };
+
+ const configure: ConfigureFn = (testBed) => {
+ testBed.configureTestingModule({
+ declarations: [DistributionComponent],
+ imports: [NgxDatatableModule],
+ schemas: [NO_ERRORS_SCHEMA],
+ providers: [
+ {provide: DistributionService, useValue: distibutionServiceMock},
+ {provide: WorkspaceService, useValue: workspaceServiceMock},
+ {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock},
+ {provide: AuthenticationService, useValue: authenticationServiceMock},
+ {provide: EventListenerService, useValue: eventListenerService}
+ ],
+ });
+ };
+
+ configureTests(configure).then((testBed) => {
+ fixture = testBed.createComponent(DistributionComponent);
+ });
+
+ });
+
+ it('Once the Distribution Component is created - distributionsResponseFromServer save all the distributions from the Service', async () => {
+ fixture.componentInstance.componentUuid = 'componentUid';
+ fixture.componentInstance.rowDistributionID = null;
+ fixture.componentInstance.isModal = false;
+
+ await fixture.componentInstance.ngOnInit();
+ expect(fixture.componentInstance.distributions.length).toBe(2);
+ });
+});
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.ts
new file mode 100644
index 0000000000..ca1b6292d3
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.ts
@@ -0,0 +1,117 @@
+import { Component, Input, OnInit, ViewChild } from '@angular/core';
+import { SdcUiCommon, SdcUiServices } from 'onap-ui-angular';
+import { EventListenerService } from '../../../../services/event-listener-service';
+import { AuthenticationService } from '../../../services/authentication.service';
+import { WorkspaceService } from '../workspace.service';
+import { DistributionService } from './distribution.service';
+import { EVENTS } from '../../../../utils/constants';
+
+@Component({
+ selector: 'distribution',
+ templateUrl: './distribution.component.html',
+ styleUrls: ['../../../../../assets/styles/table-style.less', './distribution.component.less']
+})
+export class DistributionComponent implements OnInit {
+
+ @ViewChild('distributionTable', { }) table: any;
+
+ @Input() isModal: boolean = false;
+ @Input() statusFilter: string;
+ @Input() rowDistributionID: string;
+ public componentUuid: string;
+ public distributions = [];
+ private expanded: any = {};
+ private serviceHasDistibutions: boolean = false;
+ private readonly uniqueId: string;
+ private userRole: string;
+
+ constructor(private workspaceService: WorkspaceService,
+ private distributionService: DistributionService,
+ private loaderService: SdcUiServices.LoaderService,
+ private authService: AuthenticationService,
+ private eventListenerService: EventListenerService) {
+ this.componentUuid = this.workspaceService.metadata.uuid;
+ this.uniqueId = this.workspaceService.metadata.uniqueId;
+ }
+
+
+
+ async ngOnInit() {
+ this.userRole = this.authService.getLoggedinUser().role;
+ this.eventListenerService.registerObserverCallback(EVENTS.ON_DISTRIBUTION_SUCCESS, async () => {
+ await this.refreshDistributions();
+ });
+ await this.initDistributions(this.componentUuid, this.rowDistributionID);
+ }
+
+ ngOnDestroy(): void {
+ this.eventListenerService.unRegisterObserver(EVENTS.ON_DISTRIBUTION_SUCCESS);
+ }
+
+ async initDistributions(componentUuid: string, specificDistributionID?: string) {
+ this.loaderService.activate();
+ await this.distributionService.initDistributionsList(componentUuid);
+ this.distributions = this.distributionService.getDistributionList();
+ this.distributions.length > 0 ? this.serviceHasDistibutions = true : this.serviceHasDistibutions = false;
+ if (specificDistributionID) {
+ this.distributions = this.distributionService.getDistributionList(specificDistributionID);
+ }
+ this.loaderService.deactivate();
+ }
+
+ getIconName(rowStatus: string ) {
+ if (rowStatus === 'Distributed') {
+ return 'distributed';
+ }
+ if (rowStatus === 'Deployed') {
+ return 'v-circle';
+ }
+ }
+
+ getIconMode(rowStatus: string) {
+ if (rowStatus === 'Distributed') {
+ return 'primary';
+ }
+ if (rowStatus === 'Deployed') {
+ return 'secondary';
+ }
+ }
+
+ private async markDeploy(distributionID: string, status: string) {
+ if (status === 'Distributed') {
+ console.log('Should send MarkDeploy POST Request ServiceID:' + this.uniqueId + ' DISTID:' + distributionID);
+ await this.distributionService.markDeploy(this.uniqueId, distributionID);
+ this.refreshDistributions();
+ }
+ }
+
+ private async refreshDistributions() {
+ await this.initDistributions(this.componentUuid);
+ }
+
+ private updateFilter(event) {
+ const val = event.target.value.toLowerCase();
+
+ // filter our data
+ this.distributions = this.distributionService.getDistributionList().filter((distribution: any[]) => {
+ return !val ||
+ // tslint:disable:no-string-literal
+ distribution['distributionID'].toLowerCase().indexOf(val) !== -1;
+ });
+ }
+
+ private generateDataTestID(preFix: string, distributionID: string, isModal?: boolean ): string {
+ if (isModal) {
+ return preFix + distributionID.substring(0, 5) + '_Modal';
+ } else {
+ return preFix + distributionID.substring(0, 5);
+ }
+ }
+
+ private async expandRow(row: any, expanded: boolean) {
+ if (!expanded) {
+ await this.distributionService.initDistributionsStatusForDistributionID(row.distributionID);
+ }
+ this.table.rowDetail.toggleExpandRow(row);
+ }
+}
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.module.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.module.ts
new file mode 100644
index 0000000000..723a6d8c0a
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.module.ts
@@ -0,0 +1,34 @@
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+import { SdcUiComponentsModule } from 'onap-ui-angular';
+import { DistributionComponentArtifactTableComponent } from './distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component';
+import { DistributionComponentTableComponent } from './distribution-component-table/distribution-component-table.component';
+import { DistributionComponent } from './distribution.component';
+import { DistributionService } from './distribution.service';
+
+@NgModule({
+ declarations: [
+ DistributionComponent,
+ DistributionComponentTableComponent,
+ DistributionComponentArtifactTableComponent,
+ ],
+ imports: [
+ // TranslateModule,
+ CommonModule,
+ SdcUiComponentsModule,
+ NgxDatatableModule,
+ ],
+ exports: [
+ DistributionComponent,
+ DistributionComponentTableComponent
+ ],
+ entryComponents: [
+ DistributionComponent,
+ DistributionComponentTableComponent,
+ DistributionComponentArtifactTableComponent
+ ],
+ providers: [DistributionService]
+})
+export class DistributionModule {
+}
diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.service.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.service.ts
new file mode 100644
index 0000000000..ed6791c5c1
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.service.ts
@@ -0,0 +1,233 @@
+import { HttpClient } from '@angular/common/http';
+import { Inject, Injectable } from '@angular/core';
+import { tap } from 'rxjs/operators';
+import { Distribution } from '../../../../models/distribution';
+import { ISdcConfig, SdcConfigToken } from '../../../config/sdc-config.config';
+
+@Injectable()
+export class DistributionService {
+ protected baseUrl;
+ private distributionList = [];
+ private distributionStatusesMap = {};
+
+ // tslint:disable:no-string-literal
+
+ constructor(protected http: HttpClient, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) {
+ this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root;
+ }
+
+ // Once the distribution page is loaded or when the user wants to refresh the list
+ async initDistributionsList(componentUuid: string): Promise<object> {
+ const distributionsListURL = this.baseUrl + 'services/' + componentUuid + '/distribution';
+ const res = this.http.get<Distribution[]>(distributionsListURL).pipe(tap( (result) => {
+ this.distributionList = result['distributionStatusOfServiceList'];
+ this.insertDistrbutionsToMap();
+ } ));
+ return res.toPromise();
+ }
+
+ // Once the user click on the relevant distribution ID in the distribution table (open and close)
+ async initDistributionsStatusForDistributionID(distributionID: string): Promise<object> {
+ const distributionStatus = this.baseUrl + 'services/distribution/' + distributionID;
+ const res = this.http.get<object>(distributionStatus).pipe(tap( (result) => {
+ this.insertDistributionStatusToDistributionsMap(distributionID, result['distributionStatusList']);
+ } ));
+ return res.toPromise();
+ }
+
+ public getDistributionList(specificDistributionID?: string) {
+ if (specificDistributionID) {
+ return this.distributionList.filter((distribution) => {
+ return distribution['distributionID'] === specificDistributionID;
+ });
+ } else {
+ return this.distributionList;
+ }
+ }
+
+ public getComponentsByDistributionID(distributionID: string) {
+ const components = [];
+ const distributionStatusMap = this.getStatusMapForDistributionID(distributionID);
+ if (distributionStatusMap) {
+ distributionStatusMap.forEach((component) => components.push(component.componentID));
+ }
+ return components;
+ }
+
+ // get array of artifacts per distributionID w/o componentName, sliced by artifact status
+ public getArtifactsForDistributionIDAndComponentByStatus(distributionID: string, statusToSearch: string, componentName?: string) {
+ const filteredArtifactsByStatus = [];
+
+ if (componentName) {
+ this.getArtifactstByDistributionIDAndComponentsName(distributionID, componentName).forEach ( (artifact) => {
+ if (this.artifactStatusHasMatch(artifact, statusToSearch)) {
+ filteredArtifactsByStatus.push(artifact);
+ }
+ } );
+ } else {
+ this.getArtifactstByDistributionIDAndComponentsName(distributionID).forEach ( (artifact) => {
+ if (this.artifactStatusHasMatch(artifact, statusToSearch)) {
+ filteredArtifactsByStatus.push(artifact);
+ }
+ } );
+ }
+ return filteredArtifactsByStatus;
+ }
+
+ public getArtifactstByDistributionIDAndComponentsName(distributionID: string, componentName?: string): any[] {
+ const artifacts = [];
+ if (this.getStatusMapForDistributionID(distributionID)) {
+ if (componentName) {
+ if (this.getStatusMapForDistributionID(distributionID).filter((component) => component.componentID === componentName).length > 0) {
+ const artifactsArr = this.getStatusMapForDistributionID(distributionID).filter((component) => component.componentID === componentName)[0]['artifacts']
+ if (artifactsArr.length > 0) {
+ artifactsArr.forEach((artifact) => {
+ const artifactObj = {
+ url: artifact.artifactUrl,
+ name: artifact.artifactName,
+ statuses: artifact.statuses
+ };
+ artifacts.push(artifactObj);
+ });
+ }
+ }
+ } else {
+ const components = this.getComponentsByDistributionID(distributionID);
+ components.forEach((componentName) => {
+ if (this.getStatusMapForDistributionID(distributionID).filter((component) => component.componentID === componentName).length > 0) {
+ const artifactsArr = this.getStatusMapForDistributionID(distributionID).filter((component) => component.componentID === componentName)[0]['artifacts']
+ if (artifactsArr.length > 0) {
+ artifactsArr.forEach((artifact) => {
+ const artifactObj = {
+ url: artifact.artifactUrl,
+ name: artifact.artifactName,
+ statuses: artifact.statuses
+ };
+ artifacts.push(artifactObj);
+ });
+ }
+ }
+ });
+ }
+ }
+ return artifacts;
+ }
+
+ public getStatusMapForDistributionID(distributionID: string) {
+ return this.distributionStatusesMap[distributionID];
+ }
+
+ public markDeploy(uniqueId: string, distributionID: string): Promise<object> {
+ const distributionStatus = this.baseUrl + 'services/' + uniqueId + '/distribution/' + distributionID + '/markDeployed';
+ const res = this.http.post<object>(distributionStatus, {}).pipe(tap( (result) => {
+ console.log(result);
+ } ));
+ return res.toPromise();
+ }
+
+ public getMSOStatus(distributionID: string, componentName: string): string {
+ const msoStatus = this.distributionStatusesMap[distributionID].filter((component) => component.componentID === componentName)[0].msoStatus;
+ return msoStatus ? msoStatus : '';
+ }
+
+ private artifactStatusHasMatch(artifact: any, statusToSerach: string) {
+ for (let i = 0; i < artifact.statuses.length; i++) {
+ if (artifact.statuses[i].status === statusToSerach) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private insertDistributionStatusToDistributionsMap(distributionID: string, distributionStatusMapResponseFromServer: object[]) {
+
+ // // Clear the Distribution ID array - to avoid statuses duplications
+ const distribution = this.distributionStatusesMap[distributionID];
+ distribution.length = 0;
+
+ // Sort the response of statuses from Server, so it will be easy to pop the latest status when it will be required
+ const sortedResponseByTimeStamp = distributionStatusMapResponseFromServer.sort((a, b) => b['timestamp'] - a['timestamp'])
+
+ sortedResponseByTimeStamp.map((distributionStatus) => {
+ const formattedDate = this.formatDate(distributionStatus['timestamp']);
+
+ // if (distributionStatus['url'] === null) {
+ // distributionStatus['url'] = "";
+ // }
+
+ const detailedArtifactStatus = {
+ componentID: distributionStatus['omfComponentID'],
+ artifactName: distributionStatus['url']? distributionStatus['url'].split('/').pop() : '',
+ url: distributionStatus['url'],
+ time: distributionStatus['timestamp'],
+ status: distributionStatus['status'],
+ };
+
+
+
+ // Add Component to this.distributionStatusesMap in case not exist.
+ let componentPosition = _.findIndex(distribution, {componentID: detailedArtifactStatus.componentID})
+
+ if (componentPosition === -1) {
+ this.addComponentIdToDistributionStatusMap(distributionID, detailedArtifactStatus.componentID);
+ componentPosition = distribution.length - 1;
+ }
+
+ const component = distribution[componentPosition];
+
+
+ // Add Artifact to this.distributionStatusesMap[componentID] in case not exist.
+ let artifactPosition = _.findIndex(component.artifacts, {artifactUrl: detailedArtifactStatus.url})
+
+ if (artifactPosition === -1) {
+ this.addArtifactToComponentId(distributionID, componentPosition, detailedArtifactStatus.artifactName, detailedArtifactStatus.url);
+ artifactPosition = component.artifacts.length - 1;
+ }
+
+
+ // Add status to relevat artifact in relevent componentID.
+ if (detailedArtifactStatus.url) {
+ // Case where there is a url -> should add its status
+ component.artifacts[artifactPosition].statuses.push({
+ timeStamp: detailedArtifactStatus.time,
+ status: detailedArtifactStatus.status
+ });
+ } else {
+ // Should update the Component -> status from MSO
+ this.distributionStatusesMap[distributionID][componentPosition].msoStatus = detailedArtifactStatus.status;
+ }
+
+
+ });
+ }
+
+ private addComponentIdToDistributionStatusMap(distributionID: string, componentIDValue: string) {
+ this.distributionStatusesMap[distributionID].push({
+ componentID: componentIDValue,
+ msoStatus: null,
+ artifacts: []
+ });
+ }
+
+ private addArtifactToComponentId(distributionID: string, componentPosition: number, artifactNameValue: string, artifactURLValue: any) {
+ if (artifactNameValue) {
+ this.distributionStatusesMap[distributionID][componentPosition].artifacts.push({
+ artifactName: artifactNameValue,
+ artifactUrl: artifactURLValue,
+ statuses: []
+ });
+ }
+ }
+
+ private insertDistrbutionsToMap() {
+ this.distributionList.map((distribution) => this.distributionStatusesMap[distribution.distributionID] = []);
+ }
+
+ private formatDate(epochTime: string) {
+ const intEpochTime = new Date(parseInt(epochTime, 10));
+ const amOrPm = (intEpochTime.getHours() + 24) % 24 > 12 ? 'PM' : 'AM';
+ const formattedDate = (intEpochTime.getMonth() + 1) + '/' + intEpochTime.getDate() + '/' + intEpochTime.getFullYear() + ' ' + intEpochTime.getHours() + ':' +
+ intEpochTime.getMinutes() + amOrPm;
+ return formattedDate;
+ }
+}