-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 🎸 edit project configuration #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/workspace-settings
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { JSONSchema7 } from 'json-schema'; | ||
|
||
export class SchemaDto implements JSONSchema7 { | ||
properties: JSONSchema7['properties']; | ||
title: JSONSchema7['title']; | ||
description: JSONSchema7['title']; | ||
|
||
constructor({ title, properties, description }: JSONSchema7) { | ||
this.properties = properties; | ||
this.title = title; | ||
this.description = description; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export interface ExtractValuePaths { | ||
path: string; | ||
overridePath?: string; | ||
} | ||
export const configurationsPaths: ExtractValuePaths[] = [ | ||
{ path: 'definitions.project.properties.prefix', overridePath: 'prefix' }, | ||
{ path: 'definitions.project.properties.root', overridePath: 'root' }, | ||
{ | ||
path: 'definitions.project.properties.sourceRoot', | ||
overridePath: 'sourceRoot', | ||
}, | ||
]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,10 +15,14 @@ import { | |
Logger, | ||
NotFoundException, | ||
} from '@nestjs/common'; | ||
import { JSONSchema7 } from 'json-schema'; | ||
import { Draft07 } from 'json-schema-library'; | ||
import * as angularSchema from 'node_modules/@angular/cli/lib/config/schema.json'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file should be read in runtime. Currently you're bundling & reading our installed angular pkg, instead you need to read the installed angular pkg in the currently active user's workspace by using node's fs module (readFile, readFileSync). After this change you can revert the changes you made in the tsconfig file |
||
|
||
import { SessionService } from '../session/session.service'; | ||
|
||
import { ProjectDto, UpdateProjectDto } from './dto'; | ||
import { SchemaDto } from './dto/schema.dto'; | ||
import { | ||
ANGULAR_WORKSPACE_NOT_FOUND_EXCEPTION, | ||
BAD_PATH_EXCEPTION, | ||
|
@@ -157,4 +161,18 @@ export class WorkspaceService { | |
return new InternalServerErrorException(err); | ||
} | ||
} | ||
|
||
getWorkspaceConfiguration(): Partial<JSONSchema7> { | ||
const jsonSchema = new Draft07(angularSchema); | ||
|
||
const { root, prefix, sourceRoot } = | ||
jsonSchema.getSchema()['definitions']['project']['properties']; | ||
const properties = new Draft07({ root, prefix, sourceRoot }).getSchema(); | ||
|
||
return new SchemaDto({ | ||
title: 'Angular CLI Workspace Configuration', | ||
description: 'Browser target options', | ||
properties: properties, | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
"noImplicitOverride": true, | ||
"noPropertyAccessFromIndexSignature": true, | ||
"noImplicitReturns": true, | ||
"noFallthroughCasesInSwitch": true | ||
"noFallthroughCasesInSwitch": true, | ||
"resolveJsonModule": true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is it needed? I didnt see that you import json anywhere, if its for jest, move it to tsconfig.spec.json There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. im importing angular.json file. |
||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,12 @@ | ||
<p>configuration works!</p> | ||
<h1>Edit project configuration</h1> | ||
|
||
<ng-container *ngIf="formly$ | async as formly"> | ||
<form [formGroup]="form" (ngSubmit)="onSubmit()"> | ||
<formly-form | ||
[model]="formly.formData" | ||
[fields]="formly.formFields" | ||
[form]="form" | ||
></formly-form> | ||
<button mat-button type="submit">Submit</button> | ||
</form> | ||
</ng-container> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,58 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; | ||
|
||
import { | ||
createWorkspaceSettingsServiceMockk, | ||
workspaceConfigurationMock, | ||
} from '../../testing'; | ||
import { WorkspaceSettingsService } from '../services'; | ||
|
||
import { ConfigurationComponent } from './configuration.component'; | ||
|
||
describe('ConfigurationComponent', () => { | ||
let component: ConfigurationComponent; | ||
let fixture: ComponentFixture<ConfigurationComponent>; | ||
let service: WorkspaceSettingsService; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
imports: [ConfigurationComponent], | ||
imports: [ConfigurationComponent, NoopAnimationsModule], | ||
providers: [ | ||
{ | ||
provide: WorkspaceSettingsService, | ||
useValue: createWorkspaceSettingsServiceMockk(), | ||
}, | ||
], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(ConfigurationComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
service = TestBed.inject(WorkspaceSettingsService); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
|
||
it('should formly create descriptions for fields', () => { | ||
const { root, sourceRoot, prefix } = workspaceConfigurationMock.properties; | ||
const innerHTML = fixture.debugElement.nativeElement.innerHTML; | ||
expect(innerHTML).toContain(root.description); | ||
expect(innerHTML).toContain(sourceRoot.description); | ||
expect(innerHTML).toContain(prefix.description); | ||
}); | ||
|
||
it('should submit form', () => { | ||
const updateWorkspaceProjectConfigurationSpy = jest.spyOn( | ||
service, | ||
'updateWorkspaceProjectConfiguration' | ||
); | ||
fixture.debugElement.nativeElement.querySelector('button').click(); | ||
expect(updateWorkspaceProjectConfigurationSpy).toBeCalledWith('name', { | ||
prefix: 'prefix', | ||
root: 'root', | ||
sourceRoot: 'sourceRoot', | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,82 @@ | ||
import { CommonModule } from '@angular/common'; | ||
import { Component } from '@angular/core'; | ||
import { AsyncPipe, NgIf } from '@angular/common'; | ||
import { ChangeDetectionStrategy, Component } from '@angular/core'; | ||
import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; | ||
import { Project } from '@angular-cli-gui/shared/data'; | ||
import { FormlyFieldConfig, FormlyModule } from '@ngx-formly/core'; | ||
import { FormlyJsonschema } from '@ngx-formly/core/json-schema'; | ||
import { FormlyMaterialModule } from '@ngx-formly/material'; | ||
import { | ||
combineLatest, | ||
distinctUntilChanged, | ||
map, | ||
shareReplay, | ||
switchMap, | ||
} from 'rxjs'; | ||
|
||
import { WorkspaceSettingsService } from '../services'; | ||
|
||
@Component({ | ||
selector: 'cli-configuration', | ||
standalone: true, | ||
imports: [CommonModule], | ||
imports: [ | ||
NgIf, | ||
AsyncPipe, | ||
FormlyModule, | ||
FormlyMaterialModule, | ||
ReactiveFormsModule, | ||
], | ||
templateUrl: './configuration.component.html', | ||
styleUrls: ['./configuration.component.css'], | ||
Danieliverant marked this conversation as resolved.
Show resolved
Hide resolved
|
||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
}) | ||
export class ConfigurationComponent {} | ||
export class ConfigurationComponent { | ||
private readonly projectConfiguration$ = this.workspaceSettingsService | ||
.readWorkspaceProjectConfiguration() | ||
.pipe( | ||
map( | ||
(configuration) => | ||
this.schema.toFieldConfig(configuration) | ||
.fieldGroup as FormlyFieldConfig[] | ||
) | ||
); | ||
|
||
private readonly projectName$ = this.workspaceSettingsService | ||
.readWorkspaceProjectNames() | ||
.pipe( | ||
map((names) => names[0]), | ||
shareReplay({ bufferSize: 1, refCount: true }) | ||
); | ||
|
||
private readonly workspaceProject$ = this.projectName$.pipe( | ||
switchMap((currentProjectName: string) => | ||
this.workspaceSettingsService.readWorkspaceProject(currentProjectName) | ||
), | ||
distinctUntilChanged() | ||
); | ||
|
||
readonly formly$ = combineLatest([ | ||
this.projectConfiguration$, | ||
this.workspaceProject$, | ||
]).pipe(map(([formFields, formData]) => ({ formFields, formData }))); | ||
|
||
form = this.fb.group({}); | ||
|
||
constructor( | ||
private workspaceSettingsService: WorkspaceSettingsService, | ||
private fb: FormBuilder, | ||
private schema: FormlyJsonschema | ||
) {} | ||
|
||
onSubmit(): void { | ||
this.projectName$ | ||
.pipe( | ||
switchMap((projectName) => { | ||
return this.workspaceSettingsService.updateWorkspaceProjectConfiguration( | ||
projectName, | ||
this.form.value as Project | ||
); | ||
}) | ||
) | ||
.subscribe(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './workspace-settings'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './workspace-settings.service'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { HttpClient } from '@angular/common/http'; | ||
import { Injectable } from '@angular/core'; | ||
import { Project } from '@angular-cli-gui/shared/data'; | ||
import { JsonObject } from '@angular-devkit/core/src/json/utils'; | ||
import { JSONSchema7 } from 'json-schema'; | ||
import { Observable } from 'rxjs'; | ||
|
||
@Injectable({ | ||
providedIn: 'root', | ||
}) | ||
export class WorkspaceSettingsService { | ||
constructor(private readonly http: HttpClient) {} | ||
|
||
readWorkspaceProjectNames(): Observable<string[]> { | ||
return this.http.get<string[]>(`/api/workspace/project-names`); | ||
} | ||
|
||
readWorkspaceProject(projectName: string): Observable<JsonObject> { | ||
return this.http.get<JsonObject>(`/api/workspace/project/${projectName}`); | ||
} | ||
|
||
readWorkspaceProjectConfiguration(): Observable<JSONSchema7> { | ||
return this.http.get<JSONSchema7>(`/api/workspace/workspace-configuration`); | ||
} | ||
|
||
updateWorkspaceProjectConfiguration( | ||
projectName: string, | ||
projectData: Project | ||
): Observable<Project> { | ||
return this.http.patch<Project>(`/api/workspace/project/${projectName}`, { | ||
...projectData, | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './workspace-settings.service.mock'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { BehaviorSubject, Observable, Subject } from 'rxjs'; | ||
|
||
import { WorkspaceSettingsService } from '../lib/services'; | ||
|
||
type WorkspaceSettingsServiceMock = Partial< | ||
Record<keyof WorkspaceSettingsService, jest.Mock<any, any> | Observable<any>> | ||
>; | ||
|
||
export const workspaceConfigurationMock = { | ||
properties: { | ||
root: { | ||
type: 'string', | ||
description: 'Root of the project files.', | ||
}, | ||
prefix: { | ||
type: 'string', | ||
format: 'html-selector', | ||
description: 'The prefix to apply to generated selectors.', | ||
}, | ||
sourceRoot: { | ||
type: 'string', | ||
description: | ||
'The root of the source files, assets and index.html file structure.', | ||
}, | ||
}, | ||
title: 'Angular CLI Workspace Configuration', | ||
description: 'Browser target options', | ||
}; | ||
|
||
export const workspaceNamesMock = ['name']; | ||
|
||
export const workspaceProjectMock = { | ||
root: 'root', | ||
prefix: 'prefix', | ||
sourceRoot: 'sourceRoot', | ||
extensions: { | ||
projectType: 'application', | ||
schematics: { | ||
'@schematics/angular:component': { | ||
style: 'scss', | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
export function createWorkspaceSettingsServiceMockk(): WorkspaceSettingsServiceMock { | ||
return { | ||
readWorkspaceProject: jest.fn( | ||
() => new BehaviorSubject(workspaceProjectMock) | ||
), | ||
readWorkspaceProjectNames: jest.fn( | ||
() => new BehaviorSubject(workspaceNamesMock) | ||
), | ||
readWorkspaceProjectConfiguration: jest.fn( | ||
() => new BehaviorSubject(workspaceConfigurationMock) | ||
), | ||
updateWorkspaceProjectConfiguration: jest.fn(() => new Subject()), | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this import is from 'node_modules' and not from @angular/cli like the others?