...
 
Commits (2)
# Outputs
src/**/*.js
src/**/*.js.map
src/**/*.d.ts
# IDEs
.idea/
jsconfig.json
.vscode/
# Misc
node_modules/
npm-debug.log*
yarn-error.log*
# Mac OSX Finder files.
**/.DS_Store
.DS_Store
# Ignores TypeScript files, but keeps definitions.
*.ts
!*.d.ts
# Getting Started With Schematics
This repository is a basic Schematic implementation that serves as a starting point to create and publish Schematics to NPM.
### Testing
To test locally, install `@angular-devkit/schematics-cli` globally and use the `schematics` command line tool. That tool acts the same as the `generate` command of the Angular CLI, but also has a debug mode.
Check the documentation with
```bash
schematics --help
```
### Unit Testing
`npm run test` will run the unit tests, using Jasmine as a runner and test framework.
### Publishing
To publish, simply do:
```bash
npm run build
npm publish
```
That's it!
\ No newline at end of file
This diff is collapsed.
{
"name": "generate-task",
"version": "0.0.0",
"description": "A blank schematics",
"scripts": {
"build": "node_modules/.bin/tsc -p tsconfig.json",
"test": "npm run build && node_modules/.bin/jasmine src/**/*_spec.js"
},
"keywords": [
"schematics"
],
"author": "",
"license": "MIT",
"schematics": "./src/collection.json",
"dependencies": {
"@angular-devkit/core": "^10.0.4",
"@angular-devkit/schematics": "^10.0.4",
"@angular/cli": "^10.0.4",
"typescript": "~3.9.7"
},
"devDependencies": {
"@schematics/angular": "^10.0.4",
"@types/jasmine": "~3.5.0",
"@types/node": "^12.11.1",
"jasmine": "^3.5.0"
}
}
{
"$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"generate-model": {
"description": "A blank schematic.",
"factory": "./generate-task/index#generateTask",
"schema": "./generate-task/schema.json"
}
}
}
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {<%= classify(name) %>Component} from './<%= dasherize(name) %>.component';
const routes: Routes = [
{
path: '',
canActivate: [AuthGuard],
component: <%= classify(name) %>Component,
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class <%= classify(name) %>RoutingModule {
}
<div class="wrapper max-height" fxLayout="column">
<div class="table-paginator-row">
<div class="loading-bar">
<mat-progress-bar *ngIf="isLoadingResults" mode="indeterminate"></mat-progress-bar>
</div>
<div class="paginator-row">
<button (click)="openCreateDialog()" class="paginator-margin-left"
color="primary" data-cy="app<%= classify(name) %>CreateBtn" mat-raised-button>
CREATE
</button>
<button (click)="onDeleteAllSelected()" [disabled]="selectedRows.selected.length < 1" class="new-resource-btn"
color="warn" data-cy="app<%= classify(name) %>DeleteBtn"
mat-icon-button matTooltip="Delete selected">
<mat-icon>delete</mat-icon>
</button>
<mat-paginator [length]="resultsLength" [pageSizeOptions]="[25, 50, 100]" showFirstLastButtons>
</mat-paginator>
</div>
</div>
<div [appScrollTop]="tableData" class="tasks-table overflow-auto">
<table [dataSource]="tableData" mat-table
matSort matSortDirection="asc"
multiTemplateDataRows>
<ng-container matColumnDef="select">
<th *matHeaderCellDef mat-header-cell>
<mat-checkbox (change)="$event ? masterToggle() : null" [checked]="selectedRows.hasValue() && isAllSelected()"
[indeterminate]="selectedRows.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td *matCellDef="let row" mat-cell>
<mat-checkbox (change)="$event ? selectedRows.toggle(row) : null" (click)="$event.stopPropagation()"
[checked]="selectedRows.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="id">
<th *matHeaderCellDef mat-header-cell mat-sort-header> Id</th>
<td *matCellDef="let element" mat-cell> {{element.id}}</td>
</ng-container>
<ng-container matColumnDef="description">
<th *matHeaderCellDef mat-header-cell mat-sort-header> Description</th>
<td *matCellDef="let element" mat-cell> {{element.description}}</td>
</ng-container>
<ng-container matColumnDef="task__time_started">
<th *matHeaderCellDef mat-header-cell mat-sort-header> Time started</th>
<td *matCellDef="let element" mat-cell><span
*ngIf="element.task">{{element.task.time_started | date:'y-M-d H:mm:ss'}}</span>
</td>
</ng-container>
<ng-container matColumnDef="task__time_completed">
<th *matHeaderCellDef mat-header-cell mat-sort-header> Time completed</th>
<td *matCellDef="let element" mat-cell>
<span
*ngIf="element.task && element.task.time_completed">{{element.task.time_completed | date:'y-M-d H:mm:ss'}}</span>
<span *ngIf="element.task && !element.task.time_completed">-</span>
</td>
</ng-container>
<ng-container matColumnDef="task__status">
<th *matHeaderCellDef mat-header-cell mat-sort-header> Task</th>
<td *matCellDef="let element" mat-cell>
<mat-progress-bar *ngIf="element.task && element.task.step !== 'training'" [value]="element.task.progress"
mode="determinate">
</mat-progress-bar>
<mat-progress-bar *ngIf="element.task && element.task.step === 'training' && element.task.status !== 'failed'"
[value]="element.task.progress" mode="indeterminate">
</mat-progress-bar>
<mat-hint *ngIf="element.task">{{element.task.status}} {{element.task.step}}</mat-hint>
</td>
</ng-container>
<ng-container matColumnDef="expandedDetail">
<td *matCellDef="let element" [attr.colspan]="displayedColumns.length" mat-cell>
<div *ngIf="element == expandedElement" [@detailExpand] class="element-detail">
<div class="flex-col m-l-r-5" fxFlex="50">
<table class="simple-table">
<tr>
<th class="mat-body-strong">Resource id</th>
<td>{{element.id}}</td>
</tr>
<tr>
<th class="mat-body-strong">Task id</th>
<td>{{element.task.id}}</td>
</tr>
<tr>
<th class="mat-body-strong">Status</th>
<td>{{element.task.status}}</td>
</tr>
<tr>
<th class="mat-body-strong">Progress</th>
<td>{{element.task.progress}}</td>
</tr>
<tr>
<th class="mat-body-strong">Step</th>
<td>{{element.task.step}}</td>
</tr>
<tr>
<th class="mat-body-strong">Errors</th>
<td>{{element.task.errors}}</td>
</tr>
<tr>
<th class="mat-body-strong">Last Update</th>
<td>{{element.task.last_update | date:'y-M-d H:mm:ss'}}</td>
</tr>
</table>
</div>
</div>
</td>
</ng-container>
<tr *matHeaderRowDef="displayedColumns; sticky: true" mat-header-row></tr>
<tr (click)="expandedElement = expandedElement === element ? null : element" *matRowDef="let element; columns: displayedColumns;"
[class.expanded-row]="expandedElement === element"
class="element-row"
mat-row></tr>
<tr *matRowDef="let row; columns: ['expandedDetail']" class="detail-row" mat-row></tr>
</table>
</div>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {RouterTestingModule} from '@angular/router/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
describe('<%= classify(name) %>Component', () => {
let component: <%= classify(name) %>Component;
let fixture: ComponentFixture<<%= classify(name) %>Component>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ <%= classify(name) %>Component ],
imports: [
SharedModule, HttpClientTestingModule, RouterTestingModule, BrowserAnimationsModule
],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(<%= classify(name) %>Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {AfterViewInit, Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {HttpErrorResponse} from '@angular/common/http';
import {merge, of, Subject} from 'rxjs';
import {debounceTime, startWith, switchMap, takeUntil} from 'rxjs/operators';
import {MatTableDataSource} from '@angular/material/table';
import {SelectionModel} from '@angular/cdk/collections';
import {MatSort} from '@angular/material/sort';
import {MatPaginator} from '@angular/material/paginator';
import { Create<%= classify(name) %>DialogComponent } from './create-<%= dasherize(name) %>-dialog/create-<%= dasherize(name) %>-dialog.component';
@Component({
selector: 'app-<%= dasherize(name) %>',
templateUrl: './<%= dasherize(name) %>.component.html',
styleUrls: ['./<%= dasherize(name) %>.component.scss'],
animations: [
expandRowAnimation
]
})
export class <%= classify(name) %>Component implements OnInit, OnDestroy, AfterViewInit {
expandedElement: <%= classify(name) %> | null;
public tableData: MatTableDataSource<<%= classify(name) %>> = new MatTableDataSource();
selectedRows = new SelectionModel<<%= classify(name) %>>(true, []);
public displayedColumns = ['select', 'id', 'description', 'task__time_started', 'task__time_completed', 'task__status'];
public isLoadingResults = true;
@ViewChild(MatSort) sort: MatSort;
@ViewChild(MatPaginator) paginator: MatPaginator;
resultsLength: number;
destroyed$: Subject<boolean> = new Subject<boolean>();
currentProject: Project;
constructor(private projectStore: ProjectStore,
private <%= camelize(name) %>Service: <%= classify(name) %>Service,
public dialog: MatDialog,
private logService: LogService) {
}
ngOnInit(): void {
this.tableData.sort = this.sort;
this.tableData.paginator = this.paginator;
this.projectStore.getCurrentProject().pipe(takeUntil(this.destroyed$)).subscribe(x => {
if (x) {
this.isLoadingResults = true;
this.currentProject = x;
if (this.paginator) {
this.paginator.pageIndex = 0;
}
} else {
this.isLoadingResults = false;
}
});
}
ngAfterViewInit(): void {
// If the user changes the sort order, reset back to the first page.
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
merge(this.sort.sortChange, this.paginator.page)
.pipe(debounceTime(250), startWith({}),
switchMap(() => {
this.isLoadingResults = true;
return this.projectStore.getCurrentProject().pipe(takeUntil(this.destroyed$));
}))
.pipe(
switchMap(proj => {
if (proj) {
const sortDirection = this.sort.direction === 'desc' ? '-' : '';
return this.<%= camelize(name) %>Service.get<%= classify(name) %>Tasks(
this.currentProject.id,
// Add 1 to to index because Material paginator starts from 0 and DRF paginator from 1
`ordering=${sortDirection}${this.sort.active}&page=${this.paginator.pageIndex + 1}&page_size=${this.paginator.pageSize}`);
} else {
return of(null);
}
})).subscribe(data => {
// Flip flag to show that loading has finished.
this.isLoadingResults = false;
if (data && !(data instanceof HttpErrorResponse)) {
this.resultsLength = data.count;
this.tableData.data = data.results;
}
});
}
openCreateDialog(): void {
const dialogRef = this.dialog.open(Create<%= classify(name) %>DialogComponent, {
maxHeight: '650px',
width: '700px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp && !(resp instanceof HttpErrorResponse)) {
this.tableData.data = [...this.tableData.data, resp];
}
});
}
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected(): boolean {
const numSelected = this.selectedRows.selected.length;
const numRows = this.tableData.data.length;
return numSelected === numRows;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
masterToggle(): void {
this.isAllSelected() ?
this.selectedRows.clear() :
(this.tableData.data as <%= classify(name) %>[]).forEach(row => this.selectedRows.select(row));
}
onDeleteAllSelected(): void {
if (this.selectedRows.selected.length > 0) {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
data: {
confirmText: 'Delete',
mainText: `Are you sure you want to delete ${this.selectedRows.selected.length} Tasks?`
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
const idsToDelete = this.selectedRows.selected.map((<%= camelize(name) %>: <%= classify(name) %>) => <%= camelize(name) %>.id);
const body = {ids: idsToDelete};
this.<%= camelize(name) %>Service.bulkDelete<%= classify(name) %>Tasks(this.currentProject.id, body).subscribe(() => {
this.logService.snackBarMessage(`Deleted ${this.selectedRows.selected.length} Tasks.`, 2000);
this.removeSelectedRows();
});
}
});
}
}
removeSelectedRows(): void {
this.selectedRows.selected.forEach((selected: <%= classify(name) %>) => {
const index: number = (this.tableData.data as <%= classify(name) %>[]).findIndex(<%= camelize(name) %> => <%= camelize(name) %>.id === selected.id);
this.tableData.data.splice(index, 1);
this.tableData.data = [...this.tableData.data];
});
this.selectedRows.clear();
}
ngOnDestroy(): void {
this.destroyed$.next(true);
this.destroyed$.complete();
}
}
import { ModuleWithProviders, NgModule } from '@angular/core';
import {<%= classify(name) %>Component} from './<%= dasherize(name) %>.component';
import { Create<%= classify(name) %>DialogComponent } from './create-<%= dasherize(name) %>-dialog/create-<%= dasherize(name) %>-dialog.component';
import {<%= classify(name) %>RoutingModule} from './<%= dasherize(name) %>-routing.module';
@NgModule({
imports: [
SharedModule,
<%= classify(name) %>RoutingModule,
],
declarations: [
<%= classify(name) %>Component,
Create<%= classify(name) %>DialogComponent
],
providers: [],
})
export class <%= classify(name) %>Module {
}
<h1 mat-dialog-title>New <%= classify(name) %> task</h1>
<div mat-dialog-content>
<form (ngSubmit)="onSubmit(<%= camelize(name) %>Form.value)" [formGroup]="<%= camelize(name) %>Form" class="flex-col">
<mat-form-field data-cy="app<%= classify(name) %>CreateDialogDesc">
<input [errorStateMatcher]="matcher" autocomplete="off" formControlName="descriptionFormControl" matInput
placeholder="Description" required>
<mat-error *ngIf="<%= camelize(name) %>Form.get('descriptionFormControl')?.hasError('required')">
Description is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field data-cy="app<%= classify(name) %>CreateDialogIndices">
<mat-label>Indices</mat-label>
<mat-select (openedChange)="indicesOpenedChange($event)" [disableOptionCentering]="true"
formControlName="indicesFormControl" multiple panelClass="select-panel-reveal-input" required>
<mat-option *ngFor="let projectIndex of projectIndices" [value]="projectIndex">
{{projectIndex.index}}
</mat-option>
</mat-select>
<mat-error *ngIf="<%= camelize(name) %>Form.get('indicesFormControl')?.hasError('required')">
Select at least <strong>1 index</strong>
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Select Fields</mat-label>
<mat-select [disableOptionCentering]="true" formControlName="fieldsFormControl"
multiple panelClass="select-panel-reveal-input">
<mat-option *ngFor="let fields of fieldsUnique" [value]="fields.path">
{{fields.path}}
</mat-option>
</mat-select>
</mat-form-field>
<div class="flex-col">
<div class="flex-row">
<div class="flex-item-left">
<button class="flex-item-left" mat-button mat-dialog-close type="button">Close</button>
</div>
<div class="flex-item-right">
<button [disabled]="!<%= camelize(name) %>Form.valid" data-cy="app<%= classify(name) %>CreateDialogSubmit" mat-button type="submit">
Create
</button>
</div>
</div>
</div>
</form>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Create<%= classify(name) %>DialogComponent } from './create-<%= dasherize(name) %>-dialog.component';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {RouterTestingModule} from '@angular/router/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {MatDialogRef} from '@angular/material/dialog';
describe('Create<%= classify(name) %>DialogComponent', () => {
let component: Create<%= classify(name) %>DialogComponent;
let fixture: ComponentFixture<Create<%= classify(name) %>DialogComponent>;
const mockDialogRef = {
close: jasmine.createSpy('close')
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule, HttpClientTestingModule, RouterTestingModule, BrowserAnimationsModule
],
providers: [
{
provide: MatDialogRef,
useValue: mockDialogRef
}],
declarations: [ Create<%= classify(name) %>DialogComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(Create<%= classify(name) %>DialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, OnDestroy, OnInit} from '@angular/core';
import {MatDialogRef} from '@angular/material/dialog';
import {of, Subject} from 'rxjs';
import {ErrorStateMatcher} from '@angular/material/core';
import {mergeMap, takeUntil} from 'rxjs/operators';
import {FormControl, FormGroup, Validators} from '@angular/forms';
import {HttpErrorResponse} from '@angular/common/http';
@Component({
selector: 'app-create-<%= dasherize(name) %>-dialog',
templateUrl: './create-<%= dasherize(name) %>-dialog.component.html',
styleUrls: ['./create-<%= dasherize(name) %>-dialog.component.scss']
})
export class Create<%= classify(name) %>DialogComponent implements OnInit, OnDestroy {
<%= camelize(name) %>Form = new FormGroup({
descriptionFormControl: new FormControl('', [Validators.required]),
indicesFormControl: new FormControl([], [Validators.required]),
fieldsFormControl: new FormControl([]),
});
matcher: ErrorStateMatcher = new LiveErrorStateMatcher();
currentProject: Project;
destroyed$: Subject<boolean> = new Subject<boolean>();
fieldsUnique: Field[] = [];
projectIndices: ProjectIndex[] = [];
projectFields: ProjectIndex[];
constructor(private dialogRef: MatDialogRef<Create<%= classify(name) %>DialogComponent>,
private projectService: ProjectService,
private <%= camelize(name) %>Service: <%= classify(name) %>Service,
private logService: LogService,
private projectStore: ProjectStore) {
}
ngOnInit(): void {
this.projectStore.getSelectedProjectIndices().pipe(takeUntil(this.destroyed$)).subscribe(currentProjIndices => {
if (currentProjIndices) {
const indicesForm = this.<%= camelize(name) %>Form.get('indicesFormControl');
indicesForm?.setValue(currentProjIndices);
this.getFieldsForIndices(currentProjIndices);
}
});
this.projectStore.getProjectIndices().pipe(takeUntil(this.destroyed$)).subscribe(projIndices => {
if (projIndices) {
this.projectIndices = projIndices;
}
});
}
onSubmit(formData: {
descriptionFormControl: string;
indicesFormControl: ProjectIndex[]; fieldsFormControl: string[];
}): void {
const body = {
description: formData.descriptionFormControl,
indices: formData.indicesFormControl.map(x => [{name: x.index}]).flat(),
fields: formData.fieldsFormControl
};
this.<%= camelize(name) %>Service.create<%= classify(name) %>Task(this.currentProject.id, body).subscribe(resp => {
if (resp && !(resp instanceof HttpErrorResponse)) {
this.logService.snackBarMessage(`Created new task: ${resp.description}`, 2000);
this.dialogRef.close(resp);
} else if (resp instanceof HttpErrorResponse) {
this.logService.snackBarError(resp, 5000);
}
});
}
getFieldsForIndices(indices: ProjectIndex[]): void {
this.projectFields = ProjectIndex.cleanProjectIndicesFields(indices, ['text'], []);
this.fieldsUnique = UtilityFunctions.getDistinctByProperty<Field>(this.projectFields.map(y => y.fields).flat(), (y => y.path));
}
public indicesOpenedChange(opened: unknown): void {
const indicesForm = this.<%= camelize(name) %>Form.get('indicesFormControl');
// true is opened, false is closed, when selecting something and then deselecting it the formcontrol returns empty array
if (!opened && (indicesForm?.value && indicesForm.value.length > 0)) {
this.getFieldsForIndices(indicesForm?.value);
}
}
ngOnDestroy(): void {
this.destroyed$.next(true);
this.destroyed$.complete();
}
}
import {
apply, applyTemplates,
chain,
mergeWith,
move,
Rule,
SchematicContext, SchematicsException,
Tree,
url
} from '@angular-devkit/schematics';
import {getWorkspace} from '@schematics/angular/utility/config';
import {parseName} from '@schematics/angular/utility/parse-name';
import {experimental, normalize, strings} from '@angular-devkit/core';
export function setupOptions(host: Tree, options: any): Tree {
const workspace = getWorkspace(host);
const projectName = options.project as string;
if (!options.project) {
options.project = workspace.projects[projectName];
}
const projectType = options.project.projectType === 'application' ? 'app' : 'lib';
if (options.path === undefined) {
options.path = `${options.project.sourceRoot}/${projectType}/models`;
}
const parsedPath = parseName(options.path, options.name);
options.name = parsedPath.name;
options.path = parsedPath.path;
return host;
}
// You don't have to export the function as default. You can also have more than one rule factory
// per file.
export function generateTask(options: any): Rule {
return (tree: Tree, _context: SchematicContext) => {
const workspaceConfig = tree.read('/angular.json');
if (!workspaceConfig) {
throw new SchematicsException('Could not find Angular workspace configuration');
}
// convert workspace to string
const workspaceContent = workspaceConfig.toString();
// parse workspace string into JSON object
const workspace: experimental.workspace.WorkspaceSchema = JSON.parse(workspaceContent);
if (!options.project) {
options.project = workspace.defaultProject;
}
if (options.path === undefined) {
options.path = `${__dirname}`;
}
const templateSource = apply(url('./files'), [
applyTemplates({
classify: strings.classify,
dasherize: strings.dasherize,
camelize: strings.camelize,
name: options.name
}),
move((options.flat) ?
normalize(options.path) :
normalize(options.path + '/' + strings.dasherize(options.name)))
]);
return chain([
generateTaskServices(options),
generateTaskTypes(options),
mergeWith(templateSource)
]);
};
}
export function generateTaskServices(options: any): Rule {
return (tree: Tree, _context: SchematicContext) => {
const workspaceConfig = tree.read('/angular.json');
if (!workspaceConfig) {
throw new SchematicsException('Could not find Angular workspace configuration');
}
// convert workspace to string
const workspaceContent = workspaceConfig.toString();
// parse workspace string into JSON object
const workspace: experimental.workspace.WorkspaceSchema = JSON.parse(workspaceContent);
if (!options.project) {
options.project = workspace.defaultProject;
}
if (options.path === undefined) {
options.path = `${options.sourceDir}`;
}
const templateSource = apply(url('./services'), [
applyTemplates({
classify: strings.classify,
dasherize: strings.dasherize,
camelize: strings.camelize,
name: options.name
}),
move('/src/app/core/' + strings.dasherize(options.name))
]);
return mergeWith(templateSource);
};
}
export function generateTaskTypes(options: any): Rule {
return (tree: Tree, _context: SchematicContext) => {
const workspaceConfig = tree.read('/angular.json');
if (!workspaceConfig) {
throw new SchematicsException('Could not find Angular workspace configuration');
}
// convert workspace to string
const workspaceContent = workspaceConfig.toString();
// parse workspace string into JSON object
const workspace: experimental.workspace.WorkspaceSchema = JSON.parse(workspaceContent);
if (!options.project) {
options.project = workspace.defaultProject;
}
if (options.path === undefined) {
options.path = `${options.sourceDir}`;
}
const templateSource = apply(url('./types'), [
applyTemplates({
classify: strings.classify,
dasherize: strings.dasherize,
camelize: strings.camelize,
name: options.name
}),
move('/src/app/shared/types/tasks/')
]);
return mergeWith(templateSource);
};
}
import { Tree } from '@angular-devkit/schematics';
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import * as path from 'path';
const collectionPath = path.join(__dirname, '../collection.json');
describe('generate-model', () => {
it('works', async () => {
const runner = new SchematicTestRunner('schematics', collectionPath);
const tree = await runner.runSchematicAsync('generate-model', {}, Tree.empty()).toPromise();
expect(tree.files).toEqual([]);
});
});
{
"$schema": "http://json-schema.org/schema",
"id": "simpleSchema",
"title": "Creates a simple schematic.",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "The name of the project.",
"$default": {
"$source": "projectName"
}
},
"path": {
"type": "string",
"format": "path",
"description": "The path to create the simple schematic within.",
"visible": false
},
"name": {
"description": "Specifies the name of the generated Class.",
"type": "string",
"$default": {
"$source": "argv",
"index": 0
}
},
"spec": {
"type": "boolean",
"description": "Specifies if a spec file is generated.",
"default": true
},
"flat": {
"type": "boolean",
"description": "Flag to indicate if a directory is created.",
"default": false
}
},
"required": [
"name"
],
"additionalProperties": false
}
import { TestBed } from '@angular/core/testing';
import { <%= classify(name) %>Service } from './<%= dasherize(name) %>.service';
import {RouterTestingModule} from '@angular/router/testing';
import {HttpClientTestingModule} from '@angular/common/http/testing';
describe('<%= classify(name) %>Service', () => {
let service: <%= classify(name) %>Service;
beforeEach(() => {
TestBed.configureTestingModule({imports: [
RouterTestingModule,
SharedModule,
HttpClientTestingModule
]});
service = TestBed.inject(<%= classify(name) %>Service);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
import {Injectable} from '@angular/core';
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
import {Observable} from 'rxjs';
import {catchError, tap} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class <%= classify(name) %>Service {
apiUrl = environment.apiHost + environment.apiBasePath;
constructor(private http: HttpClient,
private logService: LogService) {
}
get<%= classify(name) %>Tasks(projectId: number, params = ''): Observable<ResultsWrapper<<%= classify(name) %>> | HttpErrorResponse> {
return this.http.get<ResultsWrapper<<%= classify(name) %>>>(`${this.apiUrl}/projects/${projectId}/<%= dasherize(name) %>/?${params}`).pipe(
tap(e => this.logService.logStatus(e, 'get<%= classify(name) %>Tasks')),
catchError(this.logService.handleError<ResultsWrapper<<%= classify(name) %>>>('get<%= classify(name) %>Tasks')));
}
create<%= classify(name) %>Task(projectId: number, body: unknown): Observable<<%= classify(name) %> | HttpErrorResponse> {
return this.http.post<<%= classify(name) %>>(`${this.apiUrl}/projects/${projectId}/<%= dasherize(name) %>/`, body).pipe(
tap(e => this.logService.logStatus(e, 'create<%= classify(name) %>Task')),
catchError(this.logService.handleError<<%= classify(name) %>>('create<%= classify(name) %>Task')));
}
bulkDelete<%= classify(name) %>Tasks(projectId: number, body: unknown): Observable<{ 'num_deleted': number, 'deleted_types': { string: number }[] } | HttpErrorResponse> {
return this.http.post<{ 'num_deleted': number, 'deleted_types': { string: number }[] }>
(`${this.apiUrl}/projects/${projectId}/<%= dasherize(name) %>/bulk_delete/`, body).pipe(
tap(e => this.logService.logStatus(e, 'bulkDelete<%= classify(name) %>Tasks')),
catchError(this.logService.handleError<{ 'num_deleted': number, 'deleted_types': { string: number }[] }>('bulkDelete<%= classify(name) %>Tasks')));
}
}
interface Index {
id: number;
is_open: boolean;
url: string;
name: string;
}
interface Task {
id: number;
status: string;
progress: number;
step: string;
errors: string;
time_started: Date;
last_update: Date;
time_completed?: any;
total: number;
num_processed: number;
}
export interface <%= classify(name) %> {
id: number;
url: string;
indices: Index[];
description: string;
task: Task;
fields: string[];
}
{
"compilerOptions": {
"baseUrl": "tsconfig",
"lib": [
"es2018",
"dom"
],
"declaration": true,
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitThis": true,
"noUnusedParameters": true,
"noUnusedLocals": true,
"rootDir": "src/",
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"sourceMap": true,
"strictNullChecks": true,
"target": "es6",
"types": [
"jasmine",
"node"
]
},
"include": [
"src/**/*"
],
"exclude": [
"src/*/files/**/*"
]
}
@import "../../../../variables/variables";
table {
width: 100%;
......
@import "../../../../variables/variables";
table {
width: 100%;
......
@import "src/variables/variables";
table {
width: 100%;
......
@import "src/variables/variables";
table {
width: 100%;
......
@import "src/variables/variables";
table {
width: 100%;
......
@import "src/variables/variables";
table {
width: 100%;
......
@import "src/variables/variables";
@import "../../variables/variables";
table {
width: 100%;
......
@import "../../../../variables/variables";
.wrapper {
height: 100%;
......
@import "../../../variables/variables";
table {
width: 100%;
......
@import "../../../variables/variables";
table {
width: 100%;
......
......@@ -34,3 +34,41 @@
white-space: pre-line;
}
}
.tasks-table{
table {
width: 100%;
}
.mat-cell {
word-break: break-word;
}
.mat-column-task__status {
max-width: 160px;
}
tr.element-row:not(.expanded-row):hover {
background: #eeeeee;
}
tr.element-row:not(.expanded-row):active {
background: #efefef;
}
tr.detail-row {
height: 0;
}
.element-row td {
border-bottom-width: 0;
}
.element-detail {
overflow: hidden;
display: flex;
flex-direction: row;
}
}
......@@ -4,7 +4,7 @@
export const environment = {
// apiUrl: 'https://rest-dev.texta.ee/api/v1',
apiHost: 'http://localhost',
apiHost: 'https://rest-dev.texta.ee',
apiBasePath: '/api/v1',
production: false
};
......
/* You can add global styles to this file, and also import other style files */
@import "./variables/variables";
@import "library-overrides";
@import "components";
@use "sass:map";
@use "./variables/variables";
@use "library-overrides";
@use "components";
// might be problematic todo
div {
box-sizing: border-box;
......@@ -116,19 +117,19 @@ pre {
}
.accent-text {
color: $accent;
color: variables.$accent;
}
.primary-text {
color: $primary;
color: variables.$primary;
}
.warn-text {
color: $warn;
color: variables.$warn;
}
.action-text {
color: $primary;
color: variables.$primary;
}
......@@ -149,7 +150,7 @@ pre {
}
.table-actions-wrapper {
background-color: map-get($background, card);
background-color: map.get(variables.$background, card);
border-top-left-radius: 8.5px;
border-top-right-radius: 8.5px;
......
@import '~@angular/material/theming';
@use "sass:map";
@use '~@angular/material/theming';
// Define the default theme (same as the example above).
$texta-primary: mat-palette($mat-indigo);
$texta-accent: mat-palette($mat-pink, A200, A100, A400);
$texta-warn: mat-palette($mat-red);
$texta-primary: theming.mat-palette(theming.$mat-indigo);
$texta-accent: theming.mat-palette(theming.$mat-pink, A200, A100, A400);
$texta-warn: theming.mat-palette(theming.$mat-red);
$texta-theme: mat-light-theme($texta-primary, $texta-accent);
$texta-theme: theming.mat-light-theme($texta-primary, $texta-accent);
$navbar-height: 56px;
$primary: mat-color($texta-primary);
$accent: mat-color($texta-accent);
$warn: mat-color($texta-warn);
$primary: theming.mat-color($texta-primary);
$accent: theming.mat-color($texta-accent);
$warn: theming.mat-color($texta-warn);
$foreground: map-get($texta-theme, foreground);
$background: map-get($texta-theme, background);
$foreground: map.get($texta-theme, foreground);
$background: map.get($texta-theme, background);