Skip to content
Snippets Groups Projects
Commit 94ceac2d authored by Reid Givens's avatar Reid Givens
Browse files

added a service that tracks local job submission actions (can track others,...

added a service that tracks local job submission actions (can track others, but only implimented for jobs). Submitting jobs will lock that job from being submitted again (local only) for 5 minutes.
parent 4d889440
No related branches found
No related tags found
No related merge requests found
......@@ -30,5 +30,5 @@
Executions
</h5>
<app-jobspec-execution [execution]="execution" *ngFor="let execution of jobspec.executions"></app-jobspec-execution>
<p class="bg-light px-2 py-1" *ngIf="lastAction">Job recently submitted at {{lastAction.timestamp | date: 'medium'}}</p>
<button type="button" class="btn btn-success" *ngIf="canBeSubmitted()" (click)="submitJob()">Submit Job</button>
import {Component, Input, OnInit} from '@angular/core';
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {JobSpec} from "../../../model/job";
import {ConfigurationService} from "../../../env/configuration.service";
import {faBullseye, faCheckSquare, faEdit, faFileAlt} from "@fortawesome/free-solid-svg-icons";
import {JobsService} from "../../../services/jobs.service";
import {AlertService} from "../../../services/alert.service";
import {ActionsService} from "../../../services/actions.service";
import {Subject} from "rxjs";
import {takeUntil} from "rxjs/operators";
import {Action} from "../../../model/action";
@Component({
selector: 'app-jobspec-detail',
templateUrl: './jobspec-detail.component.html',
styleUrls: ['./jobspec-detail.component.scss']
})
export class JobspecDetailComponent implements OnInit {
export class JobspecDetailComponent implements OnInit, OnDestroy {
private ngUnsubscribe = new Subject();
@Input() jobspec: JobSpec;
public faBullseye = faBullseye;
......@@ -19,10 +24,24 @@ export class JobspecDetailComponent implements OnInit {
public faCheckSquare = faCheckSquare;
public faEdit = faEdit;
constructor(private configService: ConfigurationService, private jobsService: JobsService, private alertService: AlertService) {
public lastAction: Action;
constructor(
private configService: ConfigurationService,
private jobsService: JobsService,
private alertService: AlertService,
private actionsService: ActionsService
) {
}
ngOnInit() {
this.actionsService.actionsTaken$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((actions: Array<Action>) => {
actions.forEach((a: Action) => {
if (a.type === 'jobSubmittedFromSpec' && a.id === this.jobspec.id) {
this.lastAction = a;
}
});
});
}
getConfigUrl(): string {
......@@ -30,18 +49,28 @@ export class JobspecDetailComponent implements OnInit {
}
canBeSubmitted(): boolean {
if (this.lastAction && this.lastAction.timeSince < (1000 * 60 * 5)) {
return false;
}
return this.jobspec.executions.length < 1 ||
(!!this.jobspec.lastJob && (this.jobspec.lastJob.status === 'ERROR' || this.jobspec.lastJob.status === 'WAITING'));
(!!this.jobspec.lastJob && ['ERROR', 'WAITING'].indexOf(this.jobspec.lastJob.status) > -1);
}
submitJob(): void {
this.alertService.info('Submitting Job...');
this.actionsService.addAction('jobSubmittedFromSpec', this.jobspec.id);
this.jobsService.submitJob(this.jobspec.id, this.jobspec.queueName).subscribe(response => {
this.alertService.success('Job Submitted');
},
error => {
this.actionsService.removeActionByTypeAndId('jobSubmittedFromSpec', this.jobspec.id);
this.alertService.error('Job Submition Failed');
});
}
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
......@@ -162,7 +162,6 @@ export class JobspecsComponent implements OnInit, OnDestroy {
this.pages$ = this.jobService.getJobSpecRecordCount(epoch.id, queue.name, this.pattern, status).subscribe((jobSpecNumber: number) => {
this.numResults = jobSpecNumber;
this.pages = Math.ceil(jobSpecNumber / this.resultsPerPage);
console.log('num', jobSpecNumber);
if (this.currentPage > this.pages) {
this.currentPage = this.pages;
}
......
import {Action} from './action';
describe('Action', () => {
it('should create an instance', () => {
expect(new Action()).toBeTruthy();
});
});
export class Action {
type: string;
timestamp: Date;
id: string | number;
constructor(type: string, id: string | number) {
this.type = type;
this.id = id;
this.timestamp = new Date();
}
get timeSince(): number {
return Date.now() - this.timestamp.getTime();
}
}
<div class="row mb-2 pb-3 border-bottom" *ngIf="product.status === 'READY'">
<div class="col text-right">
<button type="button" class="btn btn-info btn-sm" (click)="showProductForm = true" *ngIf="!showProductForm">Create
Job
</button>
<button type="button" class="btn btn-danger btn-sm" (click)="showProductForm = false" *ngIf="showProductForm">
Cancel Create Job
</button>
<ng-container *ngIf="canBeSubmitted()">
<div class="row mb-2 pb-3 border-bottom">
<div class="col text-right">
<button type="button" class="btn btn-info btn-sm" (click)="showProductForm = true" *ngIf="!showProductForm">Create
Job
</button>
<button type="button" class="btn btn-danger btn-sm" (click)="showProductForm = false" *ngIf="showProductForm">
Cancel Create Job
</button>
</div>
</div>
</div>
<form (ngSubmit)="createJob()" *ngIf="showProductForm" [formGroup]="jobFormGroup" class="border-bottom row">
<div class="col">
<div class="form-group">
<label for="queue">Target processing queue: </label>
<select class="form-control" id="queue" formControlName="queue">
<option *ngFor="let queue of queues" value="{{queue}}">{{queue}}</option>
</select>
<form (ngSubmit)="createJob()" *ngIf="showProductForm" [formGroup]="jobFormGroup" class="border-bottom row">
<div class="col">
<div class="form-group">
<label for="queue">Target processing queue: </label>
<select class="form-control" id="queue" formControlName="queue">
<option *ngFor="let queue of queues" value="{{queue}}">{{queue}}</option>
</select>
</div>
</div>
</div>
<div class="col">
<div class="form-group">
<label for="version">Version for input {{product.preRequisites[0].name}}:</label>
<select class="form-control" id="version" formControlName="version">
<option *ngFor=" let version of product.preRequisites[0].requiredProduct.versions.reverse()"
value="{{version.versionNumber}}">{{version.versionNumber}}</option>
</select>
<div class="col">
<div class="form-group">
<label for="version">Version for input {{product.preRequisites[0].name}}:</label>
<select class="form-control" id="version" formControlName="version">
<option *ngFor=" let version of product.preRequisites[0].requiredProduct.versions.reverse()"
value="{{version.versionNumber}}">{{version.versionNumber}}</option>
</select>
</div>
</div>
</div>
<div class="col-auto"><label>&nbsp;</label><br/>
<button type="submit" class="btn btn-success create_job_btn" [disabled]="!jobFormGroup.valid">Create Job</button>
</div>
</form>
<div class="col-auto"><label>&nbsp;</label><br/>
<button type="submit" class="btn btn-success create_job_btn" [disabled]="!jobFormGroup.valid">Create Job</button>
</div>
</form>
</ng-container>
<div class="mt-2" *ngIf="product.versions.length > 0">
<h5 class="pb-2 m-0">
......
import {Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges} from '@angular/core';
import {Product, ProductType} from "../../../model/product";
import {FormControl, FormGroup} from "@angular/forms";
import {Observable, Subscription} from "rxjs";
import {Observable, Subject, Subscription} from "rxjs";
import {ProductsService} from "../../../services/products.service";
import {JobsService} from "../../../services/jobs.service";
import {faCubes, faEdit, faFileCode, faList} from "@fortawesome/free-solid-svg-icons";
import {AlertService} from "../../../services/alert.service";
import {takeUntil} from "rxjs/operators";
import {Action} from "../../../model/action";
import {ActionsService} from "../../../services/actions.service";
@Component({
selector: 'app-product-details',
......@@ -14,6 +17,7 @@ import {AlertService} from "../../../services/alert.service";
})
export class ProductDetailsComponent implements OnInit, OnChanges, OnDestroy {
private ngUnsubscribe = new Subject();
@Input() product: Product;
type: ProductType;
......@@ -33,7 +37,14 @@ export class ProductDetailsComponent implements OnInit, OnChanges, OnDestroy {
public faCubes = faCubes;
public faFileCode = faFileCode;
constructor(private productService: ProductsService, private jobsService: JobsService, private alertService: AlertService) {
private lastAction: Action;
constructor(
private productService: ProductsService,
private jobsService: JobsService,
private alertService: AlertService,
private actionsService: ActionsService
) {
this.jobFormGroup = new FormGroup({
queue: new FormControl(),
version: new FormControl()
......@@ -41,6 +52,13 @@ export class ProductDetailsComponent implements OnInit, OnChanges, OnDestroy {
}
ngOnInit() {
this.actionsService.actionsTaken$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((actions: Array<Action>) => {
actions.forEach((a: Action) => {
if (a.type === 'jobSubmittedFromProduct' && a.id === this.product.id) {
this.lastAction = a;
}
});
});
}
ngOnChanges(changes: SimpleChanges): void {
......@@ -61,7 +79,7 @@ export class ProductDetailsComponent implements OnInit, OnChanges, OnDestroy {
const productType = Product.getTypeFromName(changes.product.currentValue.typeName);
if (productType) {
this.type = productType;
this.queues$ = this.productService.getPageInfo(productType.id).subscribe((queues: Array<string>) => {
this.queues$ = this.productService.getPageInfo(productType.id).pipe(takeUntil(this.ngUnsubscribe)).subscribe((queues: Array<string>) => {
this.queues = queues;
this.jobFormGroup.get('queue').setValue(queues[0]);
});
......@@ -70,25 +88,29 @@ export class ProductDetailsComponent implements OnInit, OnChanges, OnDestroy {
}
}
canBeSubmitted(): boolean {
return this.product.status === 'READY' && (!this.lastAction || this.lastAction.timeSince > (1000 * 60 * 5));
}
createJob() {
this.alertService.info('Creating Job...');
let productId = this.product.id;
let queue = this.jobFormGroup.get('queue').value;
let version = parseInt(this.jobFormGroup.get('version').value);
if (!version || isNaN(version)) {
version = null;
}
this.jobsService.createJob(productId, queue, version).subscribe(response => {
this.actionsService.addAction('jobSubmittedFromProduct', this.product.id);
this.jobsService.createJob(this.product.id, queue, version).subscribe(response => {
this.alertService.success('Job created');
}, (error) => {
this.actionsService.removeActionByTypeAndId('jobSubmittedFromProduct', this.product.id);
this.alertService.error('Job creation failed');
});
}
ngOnDestroy(): void {
if (this.queues$) {
this.queues$.unsubscribe();
}
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
import {TestBed} from '@angular/core/testing';
import {ActionsService} from './actions.service';
describe('ActionsService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: ActionsService = TestBed.get(ActionsService);
expect(service).toBeTruthy();
});
});
import {Injectable} from '@angular/core';
import {Action} from "../model/action";
import {BehaviorSubject, Observable} from "rxjs";
import {StorageService} from "./storage.service";
@Injectable({
providedIn: 'root'
})
export class ActionsService {
private actionsTaken: Array<Action> = [];
private _actionsTaken: BehaviorSubject<Array<Action>> = new BehaviorSubject<Array<Action>>(this.actionsTaken);
public readonly actionsTaken$: Observable<Array<Action>> = this._actionsTaken.asObservable();
constructor(private storageService: StorageService) {
let savedActions = this.storageService.getLocal('actions_taken');
if (savedActions) {
savedActions = JSON.parse(savedActions);
for (let sa of savedActions) {
let tempAction = new Action(sa.type, sa.id);
tempAction.timestamp = new Date(sa.timestamp);
// we don't need day old stuff
if (tempAction.timeSince < (1000 * 60 * 60 * 24)) {
this.actionsTaken.push(tempAction);
}
}
}
}
addAction(type: string, id: string | number) {
this.actionsTaken.push(new Action(type, id));
this.publish();
}
removeActionByTypeAndId(type: string, id: string | number) {
this.actionsTaken = this.actionsTaken.filter((a: Action) => {
return a.type !== type && a.id !== id;
})
this.publish();
}
private publish(): void {
this.storageService.saveLocal('actions_taken', JSON.stringify(this.actionsTaken));
this._actionsTaken.next(this.actionsTaken);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment