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

Commit e338366

Browse files
oodamienghillert
authored andcommitted
Schedules
+ Add Schedule List + Add Schedule Details + Add support for multiple-schedules per each Task + Integrate Scheduling workflows in Task List Resolve #810, #811, #812, #813
1 parent 11b4a42 commit e338366

File tree

67 files changed

+4643
-525
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+4643
-525
lines changed

ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@angular/platform-browser": "5.2.9",
3232
"@angular/platform-browser-dynamic": "5.2.9",
3333
"@angular/router": "5.2.9",
34+
"angular-2-local-storage": "2.0.0",
3435
"core-js": "2.5.3",
3536
"rxjs": "5.5.7",
3637
"zone.js": "0.8.20",

ui/src/app/about/components/about-more/about-details.component.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ <h2>Enabled Features</h2>
4141
<span class="fa fa-close"></span>
4242
</strong>
4343
</div>
44+
<div class="line">
45+
<span>Schedules:</span>
46+
<strong *ngIf="dataflowVersionInfo.featureInfo.schedulerEnabled" id="schedulerEnabled">
47+
<span class="fa fa-check"></span>
48+
</strong>
49+
<strong *ngIf="!dataflowVersionInfo.featureInfo.schedulerEnabled" id="schedulerDisabled">
50+
<span class="fa fa-close"></span>
51+
</strong>
52+
</div>
4453
<div class="line">
4554
<span>Skipper Mode:</span>
4655
<strong *ngIf="dataflowVersionInfo.featureInfo.skipperEnabled" id="skipperEnabled">

ui/src/app/about/components/about-more/about-details.component.spec.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { MockActivatedRoute } from '../../../tests/mocks/activated-route';
55
import { ActivatedRoute } from '@angular/router';
66
import { MockAboutService } from '../../../tests/mocks/about';
77
import { AboutService } from '../../about.service';
8-
import { DebugElement } from '@angular/core';
98
import { By } from '@angular/platform-browser';
109
import { RouterTestingModule } from '@angular/router/testing';
1110
import { AboutDetailsComponent } from './about-details.component';
@@ -59,8 +58,8 @@ describe('AboutDetailsComponent', () => {
5958
expect(component).toBeTruthy();
6059

6160
// Verify Enabled Features
62-
validateColumnValues('enabledFeaturesTable', ['Analytics', 'Streams', 'Tasks', 'Skipper Mode'], 0);
63-
validateSpansExists(['analyticsEnabled', 'streamsEnabled', 'tasksEnabled', 'skipperEnabled']);
61+
validateColumnValues('enabledFeaturesTable', ['Analytics', 'Streams', 'Tasks', 'Schedules', 'Skipper Mode'], 0);
62+
validateSpansExists(['analyticsEnabled', 'streamsEnabled', 'tasksEnabled', 'schedulerEnabled', 'skipperEnabled']);
6463

6564
// Verify Security Information
6665
validateColumnValues('securityInformationTable', ['Authentication', 'Authorization',
@@ -106,8 +105,8 @@ describe('AboutDetailsComponent', () => {
106105
expect(component).toBeTruthy();
107106

108107
// Verify Enabled Features
109-
validateColumnValues('enabledFeaturesTable', ['Analytics', 'Streams', 'Tasks', 'Skipper Mode'], 0);
110-
validateSpansExists(['analyticsDisabled', 'streamsDisabled', 'tasksDisabled', 'skipperDisabled']);
108+
validateColumnValues('enabledFeaturesTable', ['Analytics', 'Streams', 'Tasks', 'Schedules', 'Skipper Mode'], 0);
109+
validateSpansExists(['analyticsDisabled', 'streamsDisabled', 'tasksDisabled', 'schedulerDisabled', 'skipperDisabled']);
111110

112111
// Verify Security Information
113112
validateColumnValues('securityInformationTable', ['Authentication', 'Authorization',

ui/src/app/apps/apps/apps.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ <h1>Apps</h1>
164164
</div>
165165
<div class="col-xs-12" *ngIf="appRegistrations && appRegistrations?.totalElements > 0">
166166
<app-pager [page]="params.page" [total]="appRegistrations.totalElements" [size]="params.size"
167-
(onSizeChange)="changeSize($event)">
167+
(sizeChange)="changeSize($event)">
168168
</app-pager>
169169
</div>
170170
</div>

ui/src/app/jobs/jobs/jobs.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ <h1>Batch Job Executions</h1>
7979
</div>
8080
<div class="col-xs-12" *ngIf="jobExecutions && jobExecutions?.totalElements > 0">
8181
<app-pager [page]="params.page" [total]="jobExecutions.totalElements" [size]="params.size"
82-
(onSizeChange)="changeSize($event)">
82+
(sizeChange)="changeSize($event)">
8383
</app-pager>
8484
</div>
8585
</div>

ui/src/app/runtime/runtime-apps/runtime-apps.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ <h1>Runtime applications</h1>
7272
</div>
7373
<div class="col-xs-12" *ngIf="runtimeApps && runtimeApps?.totalElements > 0">
7474
<app-pager [page]="pagination.page" [total]="runtimeApps.totalElements" [size]="pagination.size"
75-
(onSizeChange)="changeSize($event)">
75+
(sizeChange)="changeSize($event)">
7676
</app-pager>
7777
</div>
7878
</div>

ui/src/app/shared/components/pager/pager.component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
</button>
2323
<ul class="dropdown-menu dropdown-menu-up" id="dropdown-basic" *dropdownMenu>
2424
<li *ngFor="let s of SIZE_LIST" [class.active]="s === size">
25-
<a (click)="sizeChange(s)" class="dropdown-item">{{ s }}</a>
25+
<a (click)="doSizeChange(s)" class="dropdown-item">{{ s }}</a>
2626
</li>
2727
</ul>
2828
</div>
@@ -44,7 +44,7 @@ export class PagerComponent implements OnChanges {
4444

4545
@Input() size: number;
4646

47-
@Output() onSizeChange = new EventEmitter<number>();
47+
@Output() sizeChange = new EventEmitter<number>();
4848

4949
values = {
5050
from: 0,
@@ -76,8 +76,8 @@ export class PagerComponent implements OnChanges {
7676
}
7777
}
7878

79-
sizeChange(size) {
80-
this.onSizeChange.emit(size);
79+
doSizeChange(size) {
80+
this.sizeChange.emit(size);
8181
}
8282

8383
}

ui/src/app/shared/components/timepicker/styles.scss

Whitespace-only changes.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { Component, forwardRef, ViewChild } from '@angular/core';
2+
import { NG_VALUE_ACCESSOR, FormControl } from '@angular/forms';
3+
import * as moment from 'moment';
4+
import { TimepickerValidator } from './timepicker.validator';
5+
6+
export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
7+
provide: NG_VALUE_ACCESSOR,
8+
useExisting: forwardRef(() => TimepickerComponent),
9+
multi: true
10+
};
11+
12+
@Component({
13+
selector: 'app-dataflow-timepicker',
14+
styleUrls: ['./styles.scss'],
15+
template: `
16+
<div class="form-control-timepicker">
17+
<input placeholder="HH:MM:SS" [(ngModel)]="value" [popover]="popTemplate" [outsideClick]="true" placement="bottom"
18+
containerClass="popover-timepicker" class="form-control input-sm input-time" #pop="bs-popover"
19+
(onShown)="onShown()">
20+
<span (click)="show()" class="timepicker-icon fa fa-clock-o"></span>
21+
<ng-template #popTemplate>
22+
<timepicker [(ngModel)]="currentValue" showSeconds="true" [showMeridian]="false" [hourStep]="1" [minuteStep]="1"
23+
[secondsStep]="1" (ngModelChange)="changedCurrentValue()"></timepicker>
24+
</ng-template>
25+
</div>
26+
`,
27+
providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
28+
})
29+
export class TimepickerComponent {
30+
31+
@ViewChild('pop') popover;
32+
33+
private valueProp: any = '';
34+
35+
private currentValue;
36+
37+
private isNull = true;
38+
39+
get value(): any {
40+
return this.valueProp;
41+
}
42+
43+
set value(val: any) {
44+
if (val !== this.valueProp) {
45+
this.valueProp = val;
46+
}
47+
}
48+
49+
show() {
50+
this.popover.show();
51+
}
52+
53+
onShown() {
54+
let setDefault = false;
55+
this.isNull = !(this.value && this.value.toString() !== '');
56+
if (!this.isNull) {
57+
if (TimepickerValidator.time(new FormControl(this.value))) {
58+
setDefault = true;
59+
this.isNull = true;
60+
} else {
61+
this.currentValue = moment(this.value, 'HH:mm:ss')
62+
.toDate();
63+
}
64+
} else {
65+
setDefault = true;
66+
}
67+
68+
if (setDefault) {
69+
this.currentValue = moment(new Date())
70+
.hours(12)
71+
.minutes(0)
72+
.seconds(0)
73+
.toDate();
74+
}
75+
}
76+
77+
writeValue(value: any) {
78+
this.valueProp = value;
79+
}
80+
81+
changedCurrentValue() {
82+
if (this.currentValue) {
83+
const date = moment(this.currentValue);
84+
const val = `${date.format('HH')}:${date.format('mm')}:${date.format('ss')}`;
85+
if (this.isNull && val === '12:00:00') {
86+
this.writeValue(null);
87+
} else {
88+
this.writeValue(val);
89+
}
90+
} else {
91+
this.writeValue(null);
92+
}
93+
}
94+
95+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { FormControl } from '@angular/forms';
2+
3+
/**
4+
* Validators for Timepicker Component
5+
* Static methods
6+
*
7+
* @author Damien Vitrac
8+
*/
9+
export class TimepickerValidator {
10+
11+
/**
12+
* Uri time
13+
*/
14+
static uriTimeRegex = /^(?:(?:([01]?\d|2[0-3]):)([0-5]?\d):)([0-5]?\d)$/;
15+
16+
17+
/**
18+
* Validate the name conditions: no space, 2 characters min, no specials characters
19+
*
20+
* @param {FormControl} formControl
21+
* @returns {any}
22+
*/
23+
static time(formControl: FormControl): any {
24+
if (!formControl.value) {
25+
return null;
26+
}
27+
if (!TimepickerValidator.uriTimeRegex.test(formControl.value)) {
28+
return { invalid: true };
29+
}
30+
return null;
31+
}
32+
33+
}

ui/src/app/shared/model/about/feature-info.model.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@ import { LoggerService } from '../../services/logger.service';
55
* Contains meta data about the supported features.
66
*
77
* @author Gunnar Hillert
8+
* @author Damien Vitrac
89
*/
910
export class FeatureInfo implements Serializable<FeatureInfo> {
1011

1112
public analyticsEnabled = false;
1213
public streamsEnabled = false;
1314
public tasksEnabled = false;
1415
public skipperEnabled = false;
16+
public schedulerEnabled = false;
1517

16-
constructor() {
17-
18-
}
1918

2019
/**
2120
* Set the FeatureInfo object to default values.
@@ -25,13 +24,15 @@ export class FeatureInfo implements Serializable<FeatureInfo> {
2524
this.streamsEnabled = false;
2625
this.tasksEnabled = false;
2726
this.skipperEnabled = false;
27+
this.schedulerEnabled = false;
2828
}
2929

3030
public deserialize(input) {
3131
this.analyticsEnabled = input.analyticsEnabled;
3232
this.streamsEnabled = input.streamsEnabled;
3333
this.tasksEnabled = input.tasksEnabled;
3434
this.skipperEnabled = input.skipperEnabled;
35+
this.schedulerEnabled = input.schedulerEnabled;
3536
return this;
3637
}
3738

@@ -56,6 +57,9 @@ export class FeatureInfo implements Serializable<FeatureInfo> {
5657
case 'skipperEnabled': {
5758
return this.skipperEnabled;
5859
}
60+
case 'schedulerEnabled': {
61+
return this.schedulerEnabled;
62+
}
5963
default: {
6064
LoggerService.error(`Unsupported feature parameter '${feature}'.`);
6165
return false;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Injectable } from '@angular/core';
2+
import { LocalStorageService } from 'angular-2-local-storage';
3+
4+
/**
5+
* A service for global logs.
6+
*
7+
* @author Damien Vitrac
8+
*/
9+
@Injectable()
10+
export class GroupRouteService {
11+
12+
constructor(private localStorageService: LocalStorageService) {
13+
}
14+
15+
create(args): string {
16+
const key = `group-${'xxxxx-xxxxx-xxxxx-xxxxx'.replace(/[xy]/g, function (c) {
17+
const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
18+
return v.toString(16);
19+
})}`;
20+
this.localStorageService.set(key, args);
21+
return key;
22+
}
23+
24+
isSimilar(str: string): boolean {
25+
str = str || '';
26+
if (!str.startsWith('group-')) {
27+
return false;
28+
}
29+
if (str.length !== 29 || str[11] !== '-' || str[17] !== '-' || str[23] !== '-') {
30+
return false;
31+
}
32+
return true;
33+
}
34+
35+
group(name) {
36+
return this.localStorageService.get(name);
37+
}
38+
39+
}

ui/src/app/shared/shared.module.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ import { RoutingStateService } from './services/routing-state.service';
4545
import { ToastContainerModule, ToastrModule } from 'ngx-toastr';
4646
import { NotificationService } from './services/notification.service';
4747
import { LoggerService } from './services/logger.service';
48+
import { GroupRouteService } from './services/group-route.service';
49+
import { LocalStorageModule } from 'angular-2-local-storage';
50+
import { TimepickerComponent } from './components/timepicker/timepicker.component';
51+
import { PopoverModule, TimepickerModule } from 'ngx-bootstrap';
4852

4953
const busyConfig: BusyConfig = {
5054
message: 'Processing..',
@@ -72,6 +76,8 @@ const busyConfig: BusyConfig = {
7276
ModalModule.forRoot(),
7377
TooltipModule.forRoot(),
7478
BsDropdownModule.forRoot(),
79+
PopoverModule.forRoot(),
80+
TimepickerModule.forRoot(),
7581
NgxPaginationModule,
7682
ToastContainerModule,
7783
ProgressbarModule.forRoot(),
@@ -82,6 +88,10 @@ const busyConfig: BusyConfig = {
8288
maxOpened: 6,
8389
enableHtml: true
8490
}),
91+
LocalStorageModule.withConfig({
92+
prefix: 'dataflow-',
93+
storageType: 'localStorage'
94+
})
8595
],
8696
declarations: [
8797
CapitalizePipe,
@@ -110,7 +120,8 @@ const busyConfig: BusyConfig = {
110120
OrderByPipe,
111121
ConfirmComponent,
112122
LoaderComponent,
113-
PagerComponent
123+
PagerComponent,
124+
TimepickerComponent
114125
],
115126
entryComponents: [
116127
ConfirmComponent
@@ -124,7 +135,8 @@ const busyConfig: BusyConfig = {
124135
BusyService,
125136
RoutingStateService,
126137
NotificationService,
127-
LoggerService
138+
LoggerService,
139+
GroupRouteService
128140
],
129141
exports: [
130142
StreamDslComponent,
@@ -158,7 +170,8 @@ const busyConfig: BusyConfig = {
158170
AutoResizeDirective,
159171
LoaderComponent,
160172
PagerComponent,
161-
ToastContainerModule
173+
ToastContainerModule,
174+
TimepickerComponent
162175
]
163176
})
164177
export class SharedModule {

ui/src/app/streams/stream-deploy/builder/builder.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ export class StreamDeployBuilderComponent implements OnInit, OnDestroy {
293293
// Error: app not found
294294
builder.errors.global.push(line);
295295
} else {
296-
const app = this.refBuilder.streamDeployConfig.apps.find((app: any) => app.name === appKey);
296+
const app = this.refBuilder.streamDeployConfig.apps.find((app_: any) => app_.name === appKey);
297297
// Populate if it's not the default version
298298
if (!app || app.version !== value) {
299299
builder.formGroup.get('appsVersion').get(appKey).setValue(value);

0 commit comments

Comments
 (0)