Skip to content

Commit 963a660

Browse files
authored
Merge pull request #167 from OS2iot/feature/1583-MoveApplication
Feature/IOT-1583: Move application
2 parents 8e66e3f + 617dcd6 commit 963a660

File tree

14 files changed

+1453
-1206
lines changed

14 files changed

+1453
-1206
lines changed

src/app/admin/organisation/organisation.service.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ export class OrganisationService {
6363
});
6464
}
6565

66+
getMultipleWithApplicationAdmin(
67+
limit: number = 1000,
68+
offset: number = 0,
69+
orderByColumn?: string,
70+
orderByDirection?: string
71+
): Observable<OrganisationGetManyResponse> {
72+
return this.restService.get(`${this.URL}/applicationAdmin`, {
73+
limit,
74+
offset,
75+
orderOn: orderByColumn,
76+
sort: orderByDirection,
77+
});
78+
}
79+
6680
delete(id: number) {
6781
return this.restService.delete(this.URL, id);
6882
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<div class="application-change-organization-dialog">
2+
<h1 mat-dialog-title>{{ "APPLICATION.CHANGE-ORGANIZATION.TITLE" | translate }}</h1>
3+
<div mat-dialog-content>
4+
<label class="form-label" for="organizationSelect">{{
5+
"APPLICATION.CHANGE-ORGANIZATION.CHOOSE-ORGANIZATION" | translate
6+
}}</label>
7+
<mat-select
8+
id="organizationSelect"
9+
class="form-control"
10+
panelClass="overflow-x-hidden"
11+
[(value)]="application.organizationId"
12+
[compareWith]="compare"
13+
(selectionChange)="onOrganizationChange()"
14+
>
15+
<mat-option *ngFor="let organization of filteredOrganizations | async" [value]="organization.id">
16+
{{ organization.name }}
17+
</mat-option>
18+
</mat-select>
19+
<label class="form-label" for="permissionSelect">{{
20+
"APPLICATION.CHANGE-ORGANIZATION.CHOOSE-USER-GROUPS" | translate
21+
}}</label>
22+
<div *ngIf="permissions.length > 0">
23+
<mat-select
24+
id="permissionSelect"
25+
class="form-control"
26+
[multiple]="true"
27+
panelClass="overflow-x-hidden"
28+
[(value)]="application.permissionIds"
29+
[compareWith]="compare"
30+
>
31+
<mat-option *ngFor="let permission of filteredPermissionsMulti | async" [value]="permission.id">
32+
{{ permission.name }}
33+
</mat-option>
34+
</mat-select>
35+
<mat-hint>{{ "APPLICATION.CHANGE-ORGANIZATION.USER-GROUP-AUTO-SELECT" | translate }}</mat-hint>
36+
</div>
37+
</div>
38+
<div mat-dialog-actions class="d-flex flex-row">
39+
<button (click)="onSubmit()" class="btn btn-primary">
40+
{{ "GEN.SAVE" | translate }}
41+
</button>
42+
<button mat-dialog-close [mat-dialog-close]="false" class="btn btn-secondary ml-2">
43+
{{ "GEN.CANCEL" | translate }}
44+
</button>
45+
</div>
46+
</div>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.application-change-organization-dialog {
2+
width: 50vw;
3+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { Component, Inject, OnInit } from "@angular/core";
2+
import { UntypedFormControl } from "@angular/forms";
3+
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
4+
import { MatSnackBar } from "@angular/material/snack-bar";
5+
import { Organisation } from "@app/admin/organisation/organisation.model";
6+
import { OrganisationService } from "@app/admin/organisation/organisation.service";
7+
import { PermissionResponse } from "@app/admin/permission/permission.model";
8+
import { PermissionService } from "@app/admin/permission/permission.service";
9+
import { Application, UpdateApplicationOrganization } from "@applications/application.model";
10+
import { ApplicationService } from "@applications/application.service";
11+
import { TranslateService } from "@ngx-translate/core";
12+
import { ApplicationDialogModel } from "@shared/models/dialog.model";
13+
import { SharedVariableService } from "@shared/shared-variable/shared-variable.service";
14+
import { ReplaySubject, Subscription } from "rxjs";
15+
16+
@Component({
17+
selector: "app-change-organization-dialog",
18+
templateUrl: "./application-change-organization-dialog.component.html",
19+
styleUrls: ["./application-change-organization-dialog.component.scss"],
20+
})
21+
export class ApplicationChangeOrganizationDialogComponent implements OnInit {
22+
public applicationsSubscription: Subscription;
23+
public permissionsSubscription: Subscription;
24+
public organizationsSubscription: Subscription;
25+
public application: UpdateApplicationOrganization;
26+
public permissions: PermissionResponse[];
27+
public organizations: Organisation[];
28+
public filteredPermissionsMulti: ReplaySubject<PermissionResponse[]> = new ReplaySubject<PermissionResponse[]>(1);
29+
public filteredOrganizations: ReplaySubject<Organisation[]> = new ReplaySubject<Organisation[]>(1);
30+
31+
constructor(
32+
private applicationService: ApplicationService,
33+
public translate: TranslateService,
34+
private permissionService: PermissionService,
35+
private organizationService: OrganisationService,
36+
private sharedVariableService: SharedVariableService,
37+
private snackBar: MatSnackBar,
38+
private dialog: MatDialogRef<ApplicationChangeOrganizationDialogComponent>,
39+
@Inject(MAT_DIALOG_DATA) public dialogModel: ApplicationDialogModel
40+
) {
41+
this.application = {
42+
organizationId: this.dialogModel.organizationId ?? this.sharedVariableService.getSelectedOrganisationId(),
43+
permissionIds: [],
44+
};
45+
}
46+
47+
ngOnInit(): void {
48+
this.translate.use("da");
49+
if (this.dialogModel.applicationId) {
50+
this.getApplication(this.dialogModel.applicationId);
51+
}
52+
this.getOrganizations();
53+
this.getPermissions();
54+
}
55+
56+
getApplication(id: number): void {
57+
this.applicationsSubscription = this.applicationService.getApplication(id).subscribe((application: Application) => {
58+
this.application.permissionIds = application.permissionIds;
59+
});
60+
}
61+
62+
getOrganizations() {
63+
this.organizationsSubscription = this.organizationService.getMultipleWithApplicationAdmin().subscribe(res => {
64+
this.organizations = res.data;
65+
this.filteredOrganizations.next(this.organizations.slice());
66+
});
67+
}
68+
69+
getPermissions() {
70+
this.permissionsSubscription = this.permissionService.getPermissions(1000, 0).subscribe(res => {
71+
this.permissions = res.data.sort((a, b) => a.name.localeCompare(b.name, "da-DK", { numeric: true }));
72+
this.filteredPermissionsMulti.next(
73+
this.permissions.filter(p => p?.organization?.id === this?.application?.organizationId)
74+
);
75+
});
76+
}
77+
78+
public compare(o1: any, o2: any): boolean {
79+
return o1 === o2;
80+
}
81+
82+
onOrganizationChange() {
83+
this.filteredPermissionsMulti.next(
84+
this.permissions.filter(p => p?.organization?.id === this?.application?.organizationId)
85+
);
86+
this.filteredPermissionsMulti.subscribe(res => {
87+
this.application.permissionIds = res
88+
.filter(permission => permission.automaticallyAddNewApplications)
89+
.map(permission => permission.id);
90+
});
91+
}
92+
93+
onSubmit() {
94+
this.applicationsSubscription = this.applicationService
95+
.updateApplicationOrganization(this.application, this.dialogModel.applicationId)
96+
.subscribe(savedApplication => {
97+
this.snackBar.open(
98+
this.translate.instant("APPLICATION.CHANGE-ORGANIZATION.SNACKBAR-SAVED", {
99+
applicationName: savedApplication.name,
100+
organizationName: savedApplication.belongsTo.name,
101+
}),
102+
"",
103+
{
104+
duration: 10000,
105+
}
106+
);
107+
this.dialog.close(true);
108+
});
109+
}
110+
}

src/app/applications/application-detail/application-detail.component.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import { map } from "rxjs/operators";
1717
import { SharedVariableService } from "@shared/shared-variable/shared-variable.service";
1818
import { ChirpstackGatewayService } from "@shared/services/chirpstack-gateway.service";
1919
import { Gateway, GatewayResponseMany } from "@app/gateway/gateway.model";
20+
import { MatDialog } from "@angular/material/dialog";
21+
import { ApplicationChangeOrganizationDialogComponent } from "../application-change-organization-dialog/application-change-organization-dialog.component";
22+
import { ApplicationDialogModel } from "@shared/models/dialog.model";
2023

2124
@Component({
2225
selector: "app-application",
@@ -68,7 +71,8 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy, AfterViewI
6871
private deleteDialogService: DeleteDialogService,
6972
private restService: RestService,
7073
private sharedVariableService: SharedVariableService,
71-
private chirpstackGatewayService: ChirpstackGatewayService
74+
private chirpstackGatewayService: ChirpstackGatewayService,
75+
private changeOrganizationDialog: MatDialog
7276
) {}
7377

7478
ngOnInit(): void {
@@ -79,7 +83,16 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy, AfterViewI
7983
label: "",
8084
editRouterLink: "../edit-application/" + this.id,
8185
isErasable: true,
86+
extraOptions: [],
8287
};
88+
89+
this.translate.get("APPLICATION.CHANGE-ORGANIZATION.TITLE").subscribe(translation => {
90+
this.dropdownButton.extraOptions.push({
91+
id: this.id,
92+
label: translation,
93+
onClick: () => this.onOpenChangeOrganizationDialog(),
94+
});
95+
});
8396
}
8497

8598
this.translate
@@ -193,6 +206,15 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy, AfterViewI
193206
});
194207
}
195208

209+
onOpenChangeOrganizationDialog() {
210+
this.changeOrganizationDialog.open(ApplicationChangeOrganizationDialogComponent, {
211+
data: {
212+
applicationId: this.id,
213+
organizationId: this.application.belongsTo.id,
214+
} as ApplicationDialogModel,
215+
});
216+
}
217+
196218
bindApplication(id: number): void {
197219
this.applicationsSubscription = this.applicationService.getApplication(id).subscribe(application => {
198220
this.application = application;

src/app/applications/application.model.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,8 @@ export interface ApplicationData {
6161
ok?: boolean;
6262
count?: number;
6363
}
64+
65+
export class UpdateApplicationOrganization {
66+
public organizationId: number;
67+
public permissionIds: number[];
68+
}

src/app/applications/application.service.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Injectable } from "@angular/core";
2-
import { Application, ApplicationData } from "@applications/application.model";
2+
import { Application, ApplicationData, UpdateApplicationOrganization } from "@applications/application.model";
33
import { RestService } from "../shared/services/rest.service";
44
import { Observable } from "rxjs";
55
import { map } from "rxjs/operators";
@@ -76,4 +76,10 @@ export class ApplicationService {
7676
deleteApplication(id: number) {
7777
return this.restService.delete("application", id);
7878
}
79+
80+
updateApplicationOrganization(body: UpdateApplicationOrganization, id: number): Observable<Application> {
81+
return this.restService.put("application/updateApplicationOrganization", body, id, {
82+
observe: "response",
83+
});
84+
}
7985
}

src/app/applications/applications-list/applications-table/applications-table.component.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@
161161
"APPLICATION-TABLE-ROW.EDIT" | translate
162162
}}</a>
163163
</li>
164+
<li class="dropdown-item">
165+
<a (click)="onOpenChangeOrganizationDialog(element.id)" routerLinkActive="active">{{
166+
"APPLICATION.CHANGE-ORGANIZATION.TITLE" | translate
167+
}}</a>
168+
</li>
164169
<li class="dropdown-item">
165170
<a (click)="deleteApplication(element.id)" [routerLink]="[]">{{
166171
"APPLICATION-TABLE-ROW.DELETE" | translate

src/app/applications/applications-list/applications-table/applications-table.component.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import { ApplicationDeviceType } from "@applications/models/application-device-t
2424
import { Datatarget } from "@applications/datatarget/datatarget.model";
2525
import { faFlag } from "@fortawesome/free-solid-svg-icons";
2626
import { TableColumn } from "@shared/types/table.type";
27+
import { MatDialog } from "@angular/material/dialog";
28+
import { ApplicationDialogModel } from "@shared/models/dialog.model";
29+
import { ApplicationChangeOrganizationDialogComponent } from "@applications/application-change-organization-dialog/application-change-organization-dialog.component";
2730

2831
const columnDefinitions: TableColumn[] = [
2932
{
@@ -144,7 +147,8 @@ export class ApplicationsTableComponent implements AfterViewInit, OnInit {
144147
private applicationService: ApplicationService,
145148
private router: Router,
146149
private deleteDialogService: DeleteDialogService,
147-
private cdRef: ChangeDetectorRef
150+
private cdRef: ChangeDetectorRef,
151+
private changeOrganizationDialog: MatDialog
148152
) {}
149153

150154
ngOnInit() {
@@ -268,5 +272,19 @@ export class ApplicationsTableComponent implements AfterViewInit, OnInit {
268272
return !!result;
269273
}
270274

275+
onOpenChangeOrganizationDialog(id: number) {
276+
const dialog = this.changeOrganizationDialog.open(ApplicationChangeOrganizationDialogComponent, {
277+
data: {
278+
applicationId: id,
279+
} as ApplicationDialogModel,
280+
});
281+
282+
dialog.afterClosed().subscribe(res => {
283+
if (!res) return;
284+
285+
location.reload();
286+
});
287+
}
288+
271289
protected readonly columnDefinitions = columnDefinitions;
272290
}

src/app/applications/applications.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { PipesModule } from "@shared/pipes/pipes.module";
1919
import { ApplicationsTableComponent } from "./applications-list/applications-table/applications-table.component";
2020
import { MulticastModule } from "./multicast/multicast.module";
2121
import { ReactiveFormsModule } from "@angular/forms";
22+
import { ApplicationChangeOrganizationDialogComponent } from "./application-change-organization-dialog/application-change-organization-dialog.component";
2223

2324
@NgModule({
2425
declarations: [
@@ -28,6 +29,7 @@ import { ReactiveFormsModule } from "@angular/forms";
2829
ApplicationsListComponent,
2930
ApplicationsTableComponent,
3031
BulkImportComponent,
32+
ApplicationChangeOrganizationDialogComponent,
3133
],
3234
exports: [ApplicaitonsRoutingModule, ApplicationsComponent, ApplicationsTableComponent],
3335
imports: [

0 commit comments

Comments
 (0)