Skip to content
This repository was archived by the owner on May 14, 2025. It is now read-only.

Commit ebf6752

Browse files
committed
Stream deployment info popup
1 parent 842c90a commit ebf6752

17 files changed

+302
-57
lines changed

ui/src/app/shared/flo/support/app-metadata.ts

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,40 @@ import { Utils } from './utils';
99
* @author Alex Boyko
1010
* @author Andy Clement
1111
*/
12+
export class AppPropertyMetadata implements Flo.PropertyMetadata {
13+
14+
public options: string[];
15+
16+
public code: CodeOptions;
17+
18+
constructor(private metadata: ConfigurationMetadataProperty) {}
19+
20+
get id(): string {
21+
return this.metadata.id;
22+
}
23+
24+
get name(): string {
25+
return this.metadata.name;
26+
}
27+
28+
get description(): string {
29+
return this.metadata.description || this.metadata.shortDescription;
30+
}
31+
32+
get defaultValue() {
33+
return this.metadata.defaultValue;
34+
}
35+
36+
get type(): string {
37+
return this.metadata.type;
38+
}
39+
40+
get sourceType(): string {
41+
return this.metadata.sourceType;
42+
}
43+
44+
}
45+
1246
export class AppMetadata implements Flo.ElementMetadata {
1347

1448
private _dataPromise: Promise<DetailedAppRegistration>;
@@ -114,37 +148,3 @@ export interface CodeOptions {
114148
readonly language?: string;
115149
readonly langPropertyName?: string;
116150
}
117-
118-
export class AppPropertyMetadata implements Flo.PropertyMetadata {
119-
120-
public options: string[];
121-
122-
public code: CodeOptions;
123-
124-
constructor(private metadata: ConfigurationMetadataProperty) {}
125-
126-
get id(): string {
127-
return this.metadata.id;
128-
}
129-
130-
get name(): string {
131-
return this.metadata.name;
132-
}
133-
134-
get description(): string {
135-
return this.metadata.description || this.metadata.shortDescription;
136-
}
137-
138-
get defaultValue() {
139-
return this.metadata.defaultValue;
140-
}
141-
142-
get type(): string {
143-
return this.metadata.type;
144-
}
145-
146-
get sourceType(): string {
147-
return this.metadata.sourceType;
148-
}
149-
150-
}

ui/src/app/streams/model/stream-definition.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('StreamsDefinition', () => {
2020
expect(streamDefinition.isExpanded).toBe(true);
2121
streamDefinition.isSelected = true;
2222
expect(streamDefinition.force).toBe(true);
23-
expect(Object.keys(streamDefinition.deploymentProperties).length).toBe(0);
23+
expect(streamDefinition.deploymentProperties).toBeUndefined();
2424
});
2525
});
2626
});

ui/src/app/streams/model/stream-definition.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class StreamDefinition implements Expandable {
1515
public isExpanded = false;
1616
public force = false;
1717

18-
public deploymentProperties: any = {};
18+
public deploymentProperties: any;
1919

2020
constructor(
2121
name: String,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<div *ngIf="deploymentProperties | async as appsProperties; else loading">
2+
<div *ngFor="let app of appsProperties" class="panel panel-default">
3+
<div class="panel-heading"><label>{{getAppTitle(app)}}</label></div>
4+
<div class="table-responsive">
5+
<table *ngIf="app.props.length" class="table table-bordered">
6+
<tr *ngFor="let pair of app.props">
7+
<td class="deployment-properties-key">{{pair.key}}</td>
8+
<td class="deployment-properties-value">{{pair.value}}</td>
9+
</tr>
10+
</table>
11+
</div>
12+
</div>
13+
</div>
14+
<ng-template #loading><div>Loading...</div></ng-template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.deployment-properties-key {
2+
width: 70%;
3+
}
4+
5+
.deployment-properties-value {
6+
width: 30%;
7+
}
8+
9+
.deployment-properties-container {
10+
display: inline-table;
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import {DeployedAppProperties, DeploymentPropertiesInfoComponent} from './deployment-properties-info.component';
2+
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
3+
import {MockStreamsService} from '../../../tests/mocks/streams';
4+
import {StreamsService} from '../../streams.service';
5+
import {StreamDefinition} from '../../model/stream-definition';
6+
7+
/**
8+
* Test {@link DeploymentPropertiesInfoComponent}.
9+
*
10+
* @author Alex Boyko
11+
*/
12+
describe('DeploymentPropertiesInfoComponent', () => {
13+
let component: DeploymentPropertiesInfoComponent;
14+
let fixture: ComponentFixture<DeploymentPropertiesInfoComponent>;
15+
16+
const streamsService = new MockStreamsService();
17+
18+
const deploymentProperties = {
19+
time: {
20+
'spring.property.key1': 'time_value1',
21+
'spring.property.key2': 'time_value2',
22+
'maven://org.springframework.cloud.stream.app:time-source-rabbit': '1.3.1.RELEASE'
23+
},
24+
log: {
25+
'spring.property.key1': 'log_value1',
26+
'spring.property.key2': 'log_value2',
27+
'maven://org.springframework.cloud.stream.app:log-sink-rabbit': '1.3.0.RELEASE'
28+
}
29+
};
30+
31+
beforeEach(async(() => {
32+
TestBed.configureTestingModule({
33+
declarations: [
34+
DeploymentPropertiesInfoComponent
35+
],
36+
imports: [],
37+
providers: [
38+
{provide: StreamsService, useValue: streamsService}
39+
]
40+
})
41+
.compileComponents();
42+
}));
43+
44+
beforeEach(() => {
45+
fixture = TestBed.createComponent(DeploymentPropertiesInfoComponent);
46+
component = fixture.componentInstance;
47+
});
48+
49+
it('app title', () => {
50+
expect(component.getAppTitle(new DeployedAppProperties('test', '1.0.0', []))).toBe('test (1.0.0)');
51+
expect(component.getAppTitle(new DeployedAppProperties('test', '', []))).toBe('test');
52+
expect(component.getAppTitle(new DeployedAppProperties('test', undefined, []))).toBe('test');
53+
});
54+
55+
it('data from stream definition', (done) => {
56+
const streamDef = new StreamDefinition('tick', 'time | log', 'deployed');
57+
streamDef.deploymentProperties = deploymentProperties;
58+
component.streamDefinition = streamDef;
59+
component.deploymentProperties.subscribe(data => {
60+
expect(Array.isArray(data)).toBeTruthy();
61+
expect(data.length).toBe(2);
62+
63+
const timeData = data[0];
64+
expect(timeData.name).toBe('time');
65+
expect(timeData.version).toBe('1.3.1.RELEASE');
66+
expect(timeData.props.length).toBe(2);
67+
expect(timeData.props[0].key).toBe('spring.property.key1');
68+
expect(timeData.props[0].value).toBe('time_value1');
69+
70+
const logData = data[1];
71+
expect(logData.name).toBe('log');
72+
expect(logData.version).toBe('1.3.0.RELEASE');
73+
expect(logData.props.length).toBe(2);
74+
expect(logData.props[1].key).toBe('spring.property.key2');
75+
expect(logData.props[1].value).toBe('log_value2');
76+
77+
done();
78+
});
79+
});
80+
81+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import {Component, Input} from '@angular/core';
2+
import {Observable} from 'rxjs/Observable';
3+
import {share} from 'rxjs/operators';
4+
import {StreamDefinition} from '../../model/stream-definition';
5+
import {StreamsService} from '../../streams.service';
6+
7+
const VERSION_PROPERTY_KEY_PREFIX = 'maven://';
8+
9+
export class KeyValuePair {
10+
constructor(
11+
public key: string,
12+
public value: string
13+
) {}
14+
}
15+
16+
export class DeployedAppProperties {
17+
constructor(
18+
public name: string,
19+
public version: string,
20+
public props: KeyValuePair[]
21+
) {}
22+
}
23+
24+
25+
@Component({
26+
selector: 'app-stream-deployment-properties-info',
27+
templateUrl: './deployment-properties-info.component.html',
28+
styleUrls: [ './deployment-properties-info.component.scss' ],
29+
})
30+
/**
31+
* Component that shows stream deployment info.
32+
*
33+
* @author Alex Boyko
34+
*/
35+
export class DeploymentPropertiesInfoComponent {
36+
37+
public deploymentProperties: Observable<DeployedAppProperties[]>;
38+
39+
constructor(private streamsService: StreamsService) {}
40+
41+
@Input()
42+
set streamDefinition(streamDef: StreamDefinition) {
43+
if (streamDef.deploymentProperties) {
44+
this.deploymentProperties = Observable.of(this.extractData(streamDef.deploymentProperties)).pipe(share());
45+
} else {
46+
this.deploymentProperties = this.streamsService.getDeploymentInfo(streamDef.name.toString()).map(d => {
47+
streamDef.deploymentProperties = d.deploymentProperties;
48+
return this.extractData(streamDef.deploymentProperties);
49+
}).pipe(share());
50+
}
51+
}
52+
53+
getAppTitle(app: DeployedAppProperties): string {
54+
if (app.version) {
55+
return `${app.name} (${app.version})`;
56+
} else {
57+
return app.name;
58+
}
59+
}
60+
61+
private extractData(deploymentProperties: any) {
62+
return Object.keys(deploymentProperties).map(k => this.extractSingleApp(k, deploymentProperties[k]));
63+
}
64+
65+
private extractSingleApp(app: string, data: any) {
66+
const props = [];
67+
let version;
68+
Object.keys(data).forEach(k => {
69+
if (k.startsWith(VERSION_PROPERTY_KEY_PREFIX)) {
70+
version = data[k];
71+
} else {
72+
props.push(new KeyValuePair(k, data[k]));
73+
}
74+
});
75+
return new DeployedAppProperties(app, version, props);
76+
}
77+
78+
}

ui/src/app/streams/stream-definitions/stream-definitions.component.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,11 @@
8888
</td>
8989
<td>{{item.name}}</td>
9090
<td>{{item.dslText}}</td>
91-
<td>{{item.status}}</td>
91+
<td *ngIf="canShowDeploymentInfo(item); else deployment_status_only_content">
92+
<a href="" (click)="false" [popover]="stream_info" placement="bottom" triggers="focus" containerClass="deployment-info-popup">{{item.status}}</a>
93+
</td>
94+
<ng-template #deployment_status_only_content><td>{{item.status ? item.status : 'unknown'}}</td></ng-template>
95+
<ng-template #stream_info><app-stream-deployment-properties-info [streamDefinition]="item"></app-stream-deployment-properties-info></ng-template>
9296
<td class="action-column">
9397
<button type="button" (click)="details(item)"
9498
class="btn btn-default" title="Details">
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.deployment-info-popup {
2+
max-width: 500px;
3+
}

ui/src/app/streams/stream-definitions/stream-definitions.component.spec.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {TriStateCheckboxComponent} from '../../shared/components/tri-state-check
2727
import {DeploymentPropertiesComponent} from './deployment-properties/deployment-properties.component';
2828
import { MocksSharedAboutService } from '../../tests/mocks/shared-about';
2929
import { SharedAboutService } from '../../shared/services/shared-about.service';
30-
import { DataflowVersionInfo } from '../../tests/mocks/about';
30+
import { DeploymentPropertiesInfoComponent } from './deployment-properties-info/deployment-properties-info.component';
3131

3232
/**
3333
* Test {@link StreamDefinitionsComponent}.
@@ -46,11 +46,7 @@ describe('StreamDefinitionsComponent', () => {
4646

4747
beforeEach(async(() => {
4848
activeRoute = new MockActivatedRoute();
49-
50-
// Disable skipper mode initially to get Deploy UI tests to pass
51-
const info = new DataflowVersionInfo();
52-
info.featureInfo.skipperEnabled = false;
53-
const aboutService = new MocksSharedAboutService(info);
49+
const aboutService = new MocksSharedAboutService();
5450

5551
TestBed.configureTestingModule({
5652
declarations: [
@@ -61,7 +57,8 @@ describe('StreamDefinitionsComponent', () => {
6157
StreamDefinitionsComponent,
6258
TriStateButtonComponent,
6359
TriStateCheckboxComponent,
64-
DeploymentPropertiesComponent
60+
DeploymentPropertiesComponent,
61+
DeploymentPropertiesInfoComponent
6562
],
6663
imports: [
6764
BusyModule,
@@ -354,4 +351,30 @@ describe('StreamDefinitionsComponent', () => {
354351
expect(show2).not.toHaveBeenCalled();
355352
});
356353

354+
it('can show deployment info', () => {
355+
const stream = new StreamDefinition('test', 'time | log', 'unknown');
356+
expect(component.canShowDeploymentInfo(stream)).toBeFalsy();
357+
358+
stream.status = undefined;
359+
expect(component.canShowDeploymentInfo(stream)).toBeFalsy();
360+
361+
stream.status = 'undeployed';
362+
expect(component.canShowDeploymentInfo(stream)).toBeFalsy();
363+
364+
stream.status = 'deployed';
365+
expect(component.canShowDeploymentInfo(stream)).toBeTruthy();
366+
367+
stream.status = 'deploying';
368+
expect(component.canShowDeploymentInfo(stream)).toBeTruthy();
369+
370+
stream.status = 'failed';
371+
expect(component.canShowDeploymentInfo(stream)).toBeTruthy();
372+
373+
stream.status = 'incomplete';
374+
expect(component.canShowDeploymentInfo(stream)).toBeTruthy();
375+
376+
stream.status = 'foo';
377+
expect(component.canShowDeploymentInfo(stream)).toBeFalsy();
378+
});
379+
357380
});

0 commit comments

Comments
 (0)