@mindline/sync 1.0.33 → 1.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  //index.ts - published interface - AAD implementations, facade to Mindline Config API
2
+ import * as signalR from "@microsoft/signalr"
2
3
  import { IPublicClientApplication, AuthenticationResult } from "@azure/msal-browser"
3
4
  import { deserializeArray } from 'class-transformer';
4
- import { adminDelete, adminPost, adminsGet, configDelete, configsGet, configPost, configPut, graphConfig, initPost, readerPost, tenantPut, tenantPost, tenantDelete, tenantsGet, workspacesGet} from './hybridspa';
5
+ import { adminDelete, adminPost, adminsGet, configDelete, configsGet, configPost, configPut, graphConfig, initPost, readerPost, tenantPut, tenantPost, tenantDelete, tenantsGet, workspacesGet } from './hybridspa';
5
6
  import { version } from './package.json';
6
7
  import users from "./users.json";
7
8
  import tenants from "./tenants.json";
@@ -11,1066 +12,1166 @@ import tasksData from "./tasks";
11
12
  const FILTER_FIELD = "workspaceIDs";
12
13
  // called by unit tests
13
14
  export function sum(a: number, b: number): number {
14
- return a + b;
15
+ return a + b;
15
16
  }
16
- export function helloNpm() : string {
17
- return "hello NPM";
17
+ export function helloNpm(): string {
18
+ return "hello NPM";
18
19
  }
19
20
  export class Group {
20
- id: string;
21
- displayName: string;
22
- description: string;
21
+ id: string;
22
+ displayName: string;
23
+ description: string;
23
24
  }
24
25
  export class User {
25
- oid: string;
26
- name: string;
27
- mail: string;
28
- authority: string;
29
- tid: string;
30
- companyName: string;
31
- companyDomain: string;
32
- associatedWorkspaces: string[];
33
- workspaceIDs: string;
34
- session: string; // button text
35
- spacode: string; // to get front end access token
36
- accessToken: string; // front end access token
37
- loginHint: string; // to help sign out without prompt
38
- scopes: string[]; // to detect if incremental consent has happened
39
- authTS: Date; // timestamp user was authenticated
40
- constructor() {
41
- this.oid = "";
42
- this.name = "";
43
- this.mail = "";
44
- this.authority = "https://login.microsoftonline.com/organizations/v2.0";
45
- this.tid = "";
46
- this.companyName = "";
47
- this.companyDomain = "";
48
- this.associatedWorkspaces = new Array();
49
- this.workspaceIDs = "";
50
- this.session = "Sign In";
51
- this.spacode = "";
52
- this.accessToken = "";
53
- this.loginHint = "";
54
- this.scopes = new Array();
55
- this.authTS = new Date(0);
56
- }
26
+ oid: string;
27
+ name: string;
28
+ mail: string;
29
+ authority: string;
30
+ tid: string;
31
+ companyName: string;
32
+ companyDomain: string;
33
+ associatedWorkspaces: string[];
34
+ workspaceIDs: string;
35
+ session: string; // button text
36
+ spacode: string; // to get front end access token
37
+ accessToken: string; // front end access token
38
+ loginHint: string; // to help sign out without prompt
39
+ scopes: string[]; // to detect if incremental consent has happened
40
+ authTS: Date; // timestamp user was authenticated
41
+ constructor() {
42
+ this.oid = "";
43
+ this.name = "";
44
+ this.mail = "";
45
+ this.authority = "https://login.microsoftonline.com/organizations/v2.0";
46
+ this.tid = "";
47
+ this.companyName = "";
48
+ this.companyDomain = "";
49
+ this.associatedWorkspaces = new Array();
50
+ this.workspaceIDs = "";
51
+ this.session = "Sign In";
52
+ this.spacode = "";
53
+ this.accessToken = "";
54
+ this.loginHint = "";
55
+ this.scopes = new Array();
56
+ this.authTS = new Date(0);
57
+ }
57
58
  }
58
59
  export enum TenantType {
59
- invalid = 0,
60
- aad = 1,
61
- ad = 2,
62
- googleworkspace = 3
60
+ invalid = 0,
61
+ aad = 1,
62
+ ad = 2,
63
+ googleworkspace = 3
63
64
  }
64
65
  type TenantTypeStrings = keyof typeof TenantType;
65
66
  export enum TenantPermissionType {
66
- read = 1,
67
- write = 2,
68
- notassigned = 3
67
+ read = 1,
68
+ write = 2,
69
+ notassigned = 3
69
70
  }
70
71
  export type TenantPermissionTypeStrings = keyof typeof TenantPermissionType;
71
72
  export class Tenant {
72
- tid: string;
73
- name: string;
74
- domain: string;
75
- tenantType: TenantTypeStrings;
76
- permissionType: TenantPermissionTypeStrings;
77
- onboarded: string;
78
- authority: string;
79
- readServicePrincipal: string;
80
- writeServicePrincipal: string;
81
- workspaceIDs: string;
82
- constructor(){
83
- this.tid = "";
84
- this.name = "";
85
- this.domain = "";
86
- this.tenantType = "aad";
87
- this.permissionType = "notassigned";
88
- this.onboarded = "false";
89
- this.authority = "https://login.microsoftonline.com/organizations/v2.0";
90
- this.readServicePrincipal = "";
91
- this.writeServicePrincipal = "";
92
- this.workspaceIDs = "";
93
- }
73
+ tid: string;
74
+ name: string;
75
+ domain: string;
76
+ tenantType: TenantTypeStrings;
77
+ permissionType: TenantPermissionTypeStrings;
78
+ onboarded: string;
79
+ authority: string;
80
+ readServicePrincipal: string;
81
+ writeServicePrincipal: string;
82
+ workspaceIDs: string;
83
+ constructor() {
84
+ this.tid = "";
85
+ this.name = "";
86
+ this.domain = "";
87
+ this.tenantType = "aad";
88
+ this.permissionType = "notassigned";
89
+ this.onboarded = "false";
90
+ this.authority = "https://login.microsoftonline.com/organizations/v2.0";
91
+ this.readServicePrincipal = "";
92
+ this.writeServicePrincipal = "";
93
+ this.workspaceIDs = "";
94
+ }
94
95
  }
95
96
  export enum TenantConfigType {
96
- source = 1,
97
- target = 2,
98
- sourcetarget = 3
97
+ source = 1,
98
+ target = 2,
99
+ sourcetarget = 3
99
100
  }
100
101
  export type TenantConfigTypeStrings = keyof typeof TenantConfigType;
101
102
  export class TenantConfigInfo {
102
- tid: string;
103
- sourceGroupId: string;
104
- sourceGroupName: string;
105
- configurationTenantType: TenantConfigTypeStrings;
106
- constructor(){
107
- this.tid = "";
108
- this.sourceGroupId = "";
109
- this.sourceGroupName = "";
110
- this.configurationTenantType = "source";
111
- }
103
+ tid: string;
104
+ sourceGroupId: string;
105
+ sourceGroupName: string;
106
+ configurationTenantType: TenantConfigTypeStrings;
107
+ deltaToken: string;
108
+ filesWritten: number;
109
+ constructor() {
110
+ this.tid = "";
111
+ this.sourceGroupId = "";
112
+ this.sourceGroupName = "";
113
+ this.configurationTenantType = "source";
114
+ this.deltaToken = "";
115
+ this.filesWritten = 0;
116
+ }
112
117
  }
113
118
  export class Config {
114
- id: string;
115
- workspaceId: string;
116
- name: string;
117
- description: string;
118
- tenants: TenantConfigInfo[];
119
- isEnabled: boolean;
120
- workspaceIDs: string;
121
- constructor(){
122
- this.id = "";
123
- this.name = "";
124
- this.description = "";
125
- this.tenants = new Array();
126
- this.isEnabled = false;
127
- this.workspaceIDs = "";
128
- }
119
+ id: string;
120
+ workspaceId: string;
121
+ name: string;
122
+ description: string;
123
+ tenants: TenantConfigInfo[];
124
+ isEnabled: boolean;
125
+ workspaceIDs: string;
126
+ constructor() {
127
+ this.id = "";
128
+ this.name = "";
129
+ this.description = "";
130
+ this.tenants = new Array();
131
+ this.isEnabled = false;
132
+ this.workspaceIDs = "";
133
+ }
129
134
  }
130
135
  export class Workspace {
131
- id: string;
132
- name: string;
133
- associatedUsers: string[];
134
- associatedTenants: string[];
135
- associatedConfigs: string[];
136
- constructor(){
137
- this.id = "";
138
- this.name = "";
139
- this.associatedUsers = new Array();
140
- this.associatedTenants = new Array();
141
- this.associatedConfigs = new Array();
142
- }
136
+ id: string;
137
+ name: string;
138
+ associatedUsers: string[];
139
+ associatedTenants: string[];
140
+ associatedConfigs: string[];
141
+ constructor() {
142
+ this.id = "";
143
+ this.name = "";
144
+ this.associatedUsers = new Array();
145
+ this.associatedTenants = new Array();
146
+ this.associatedConfigs = new Array();
147
+ }
143
148
  }
144
149
  // check for localStorage availability
145
150
  function storageAvailable(type) {
146
- let storage;
147
- try {
148
- storage = window[type];
149
- const x = "__storage_test__";
150
- storage.setItem(x, x);
151
- storage.removeItem(x);
152
- return true;
153
- } catch (e) {
154
- return (
155
- e instanceof DOMException &&
156
- // everything except Firefox
157
- (e.code === 22 ||
158
- // Firefox
159
- e.code === 1014 ||
160
- // test name field too, because code might not be present
161
- // everything except Firefox
162
- e.name === "QuotaExceededError" ||
163
- // Firefox
164
- e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
165
- // acknowledge QuotaExceededError only if there's something already stored
166
- storage &&
167
- storage.length !== 0
168
- );
169
- }
170
- }
171
- export class InitInfo {
172
- us: User[];
173
- ts: Tenant[];
174
- cs: Config[];
175
- ws: Workspace[];
176
- constructor(bClearLocalStorage: boolean) {
177
- this.init(bClearLocalStorage);
178
- }
179
- // get initial data from localStorage or file
180
- init(bClearLocalStorage: boolean): void {
181
- console.log(`Calling InitInfo::init(bClearLocalStorage: ${bClearLocalStorage?"true":"false"})`);
182
- // if we have a non-zero value stored, read it from localStorage
183
- if (storageAvailable("localStorage")) {
184
- let result = localStorage.getItem("InitInfo");
185
- if (result != null && typeof result === "string" && result !== "") {
186
- let initInfoString: string = result;
187
- let iiReadFromLocalStorage: InitInfo = JSON.parse(initInfoString);
188
- if (iiReadFromLocalStorage.us.length !== 0) {
189
- if(bClearLocalStorage) { localStorage.removeItem("InitInfo"); }
190
- else{
191
- this.#initFromObjects(iiReadFromLocalStorage);
192
- return;
193
- }
194
- }
195
- }
196
- }
197
- // if storage unavailable or we were just asked to clear, read from default files to enable usable UI
198
- var usersString = JSON.stringify(users);
199
- var tenantsString = JSON.stringify(tenants);
200
- var configsString = JSON.stringify(configs);
201
- var workspacesString = JSON.stringify(workspaces);
151
+ let storage;
202
152
  try {
203
- this.us = deserializeArray(User, usersString);
204
- this.ts = deserializeArray(Tenant, tenantsString);
205
- this.cs = deserializeArray(Config, configsString);
206
- this.ws = deserializeArray(Workspace, workspacesString);
207
- this.tagWithWorkspaces();
153
+ storage = window[type];
154
+ const x = "__storage_test__";
155
+ storage.setItem(x, x);
156
+ storage.removeItem(x);
157
+ return true;
208
158
  } catch (e) {
209
- debugger;
159
+ return (
160
+ e instanceof DOMException &&
161
+ // everything except Firefox
162
+ (e.code === 22 ||
163
+ // Firefox
164
+ e.code === 1014 ||
165
+ // test name field too, because code might not be present
166
+ // everything except Firefox
167
+ e.name === "QuotaExceededError" ||
168
+ // Firefox
169
+ e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
170
+ // acknowledge QuotaExceededError only if there's something already stored
171
+ storage &&
172
+ storage.length !== 0
173
+ );
210
174
  }
211
- }
212
- save(): void{
213
- let initInfoString: string = JSON.stringify(this);
214
- localStorage.setItem("InitInfo", initInfoString);
215
- }
216
- tagWithWorkspaces(): boolean {
217
- // first clear everyone's workspaceIDs
218
- this.us.map((item) => item.workspaceIDs = "");
219
- this.ts.map((item) => item.workspaceIDs = "");
220
- this.cs.map((item) => item.workspaceIDs = "");
221
- // for each workspace tag WorkspaceIDs of associated Users, Tenants, Configs
222
- for (let workspace of this.ws) {
223
- // find matching Users to tag with this workspace
224
- for (let userID of workspace.associatedUsers) {
225
- let user = this.us.find((currentUser) => currentUser.oid === userID);
226
- if (user !== undefined) {
227
- // we found the user
228
- user[FILTER_FIELD] += workspace.id;
229
- user[FILTER_FIELD] += " ";
230
- } else {
231
- // we should not have InitInfo missing Workspace components
232
- debugger;
233
- return false;
175
+ }
176
+ export class InitInfo {
177
+ us: User[];
178
+ ts: Tenant[];
179
+ cs: Config[];
180
+ ws: Workspace[];
181
+ constructor(bClearLocalStorage: boolean) {
182
+ this.init(bClearLocalStorage);
183
+ }
184
+ // get initial data from localStorage or file
185
+ init(bClearLocalStorage: boolean): void {
186
+ console.log(`Calling InitInfo::init(bClearLocalStorage: ${bClearLocalStorage ? "true" : "false"})`);
187
+ // if we have a non-zero value stored, read it from localStorage
188
+ if (storageAvailable("localStorage")) {
189
+ let result = localStorage.getItem("InitInfo");
190
+ if (result != null && typeof result === "string" && result !== "") {
191
+ let initInfoString: string = result;
192
+ let iiReadFromLocalStorage: InitInfo = JSON.parse(initInfoString);
193
+ if (iiReadFromLocalStorage.us.length !== 0) {
194
+ if (bClearLocalStorage) { localStorage.removeItem("InitInfo"); }
195
+ else {
196
+ this.#initFromObjects(iiReadFromLocalStorage);
197
+ return;
198
+ }
199
+ }
200
+ }
234
201
  }
235
- }
236
- // find matching Tenants to tag with this workspace
237
- for (let tenantID of workspace.associatedTenants) {
238
- let tenant = this.ts.find(
239
- (currentTenant) => currentTenant.tid === tenantID
240
- );
241
- if (tenant !== undefined) {
242
- // we found the tenant
243
- tenant[FILTER_FIELD] += workspace.id;
244
- tenant[FILTER_FIELD] += " ";
245
- } else {
246
- // we should not have InitInfo missing Workspace components
247
- debugger;
248
- return false;
202
+ // if storage unavailable or we were just asked to clear, read from default files to enable usable UI
203
+ var usersString = JSON.stringify(users);
204
+ var tenantsString = JSON.stringify(tenants);
205
+ var configsString = JSON.stringify(configs);
206
+ var workspacesString = JSON.stringify(workspaces);
207
+ try {
208
+ this.us = deserializeArray(User, usersString);
209
+ this.ts = deserializeArray(Tenant, tenantsString);
210
+ this.cs = deserializeArray(Config, configsString);
211
+ this.ws = deserializeArray(Workspace, workspacesString);
212
+ this.tagWithWorkspaces();
213
+ } catch (e) {
214
+ debugger;
249
215
  }
250
- }
251
- // find matching Configs to tag with this workspace
252
- for (let configID of workspace.associatedConfigs) {
253
- let config = this.cs.find(
254
- (currentConfig) => currentConfig.id === configID
255
- );
256
- if (config !== undefined) {
257
- // we found the config
258
- config[FILTER_FIELD] += workspace.id;
259
- config[FILTER_FIELD] += " ";
260
- } else {
261
- // we should not have InitInfo missing Workspace components
262
- debugger;
263
- return false;
216
+ }
217
+ save(): void {
218
+ let initInfoString: string = JSON.stringify(this);
219
+ localStorage.setItem("InitInfo", initInfoString);
220
+ }
221
+ tagWithWorkspaces(): boolean {
222
+ // first clear everyone's workspaceIDs
223
+ this.us.map((item) => item.workspaceIDs = "");
224
+ this.ts.map((item) => item.workspaceIDs = "");
225
+ this.cs.map((item) => item.workspaceIDs = "");
226
+ // for each workspace tag WorkspaceIDs of associated Users, Tenants, Configs
227
+ for (let workspace of this.ws) {
228
+ // find matching Users to tag with this workspace
229
+ for (let userID of workspace.associatedUsers) {
230
+ let user = this.us.find((currentUser) => currentUser.oid === userID);
231
+ if (user !== undefined) {
232
+ // we found the user
233
+ user[FILTER_FIELD] += workspace.id;
234
+ user[FILTER_FIELD] += " ";
235
+ } else {
236
+ // we should not have InitInfo missing Workspace components
237
+ debugger;
238
+ return false;
239
+ }
240
+ }
241
+ // find matching Tenants to tag with this workspace
242
+ for (let tenantID of workspace.associatedTenants) {
243
+ let tenant = this.ts.find(
244
+ (currentTenant) => currentTenant.tid === tenantID
245
+ );
246
+ if (tenant !== undefined) {
247
+ // we found the tenant
248
+ tenant[FILTER_FIELD] += workspace.id;
249
+ tenant[FILTER_FIELD] += " ";
250
+ } else {
251
+ // we should not have InitInfo missing Workspace components
252
+ debugger;
253
+ return false;
254
+ }
255
+ }
256
+ // find matching Configs to tag with this workspace
257
+ for (let configID of workspace.associatedConfigs) {
258
+ let config = this.cs.find(
259
+ (currentConfig) => currentConfig.id === configID
260
+ );
261
+ if (config !== undefined) {
262
+ // we found the config
263
+ config[FILTER_FIELD] += workspace.id;
264
+ config[FILTER_FIELD] += " ";
265
+ } else {
266
+ // we should not have InitInfo missing Workspace components
267
+ debugger;
268
+ return false;
269
+ }
270
+ }
264
271
  }
265
- }
272
+ return true;
273
+ }
274
+ #initFromObjects(ii: InitInfo): void {
275
+ // user array is the only one that has a Date that must be re-created on every read
276
+ if (typeof ii.us === "undefined") this.us = new Array();
277
+ else this.us = ii.us.map((user: User) => { user.authTS = new Date(user.authTS); return user });
278
+ if (typeof ii.ts === "undefined") this.ts = new Array();
279
+ else this.ts = ii.ts;
280
+ if (typeof ii.cs === "undefined") this.cs = new Array();
281
+ else this.cs = ii.cs;
282
+ if (typeof ii.ws === "undefined") this.ws = new Array();
283
+ else this.ws = ii.ws;
266
284
  }
267
- return true;
268
- }
269
- #initFromObjects(ii: InitInfo) : void {
270
- // user array is the only one that has a Date that must be re-created on every read
271
- if(typeof ii.us === "undefined") this.us = new Array();
272
- else this.us = ii.us.map((user: User) => { user.authTS = new Date(user.authTS); return user } );
273
- if(typeof ii.ts === "undefined") this.ts = new Array();
274
- else this.ts = ii.ts;
275
- if(typeof ii.cs === "undefined") this.cs = new Array();
276
- else this.cs = ii.cs;
277
- if(typeof ii.ws === "undefined") this.ws = new Array();
278
- else this.ws = ii.ws;
279
- }
280
285
  }
281
- export type TaskType = "initialization" |
282
- "authenticate user" |
283
- "reload React" |
284
- "GET tenant details" |
285
- "POST config init" |
286
- "GET workspaces" |
287
- "onboard tenant" |
288
- "create 2nd tenant" |
289
- "invite 2nd admin" |
290
- "onboard 2nd tenant" |
291
- "create config";
286
+ export type TaskType = "initialization" |
287
+ "authenticate user" |
288
+ "reload React" |
289
+ "GET tenant details" |
290
+ "POST config init" |
291
+ "GET workspaces" |
292
+ "onboard tenant" |
293
+ "create 2nd tenant" |
294
+ "invite 2nd admin" |
295
+ "onboard 2nd tenant" |
296
+ "create config";
292
297
  export class TaskArray {
293
- tasks: Task[];
294
- constructor(bClearLocalStorage: boolean) {
295
- this.tasks = [ new Task() ];
296
- this.init(bClearLocalStorage);
297
- }
298
- // get initial data from localStorage or file
299
- init(bClearLocalStorage: boolean): void {
300
- console.log(`Calling TaskArray::init(bClearLocalStorage: ${bClearLocalStorage ? "true" : "false"})`);
301
- // first clear task array
302
- this.tasks.length = 0;
303
- // then clear localStorage if we have been asked to
304
- if (bClearLocalStorage) {
305
- if (storageAvailable("localStorage")) localStorage.removeItem("Tasks");
298
+ tasks: Task[];
299
+ constructor(bClearLocalStorage: boolean) {
300
+ this.tasks = [new Task()];
301
+ this.init(bClearLocalStorage);
306
302
  }
307
- // then try localStorage
308
- if (storageAvailable("localStorage")) {
309
- let result = localStorage.getItem("Tasks");
310
- if (result != null && typeof result === "string" && result !== "") {
311
- // properly create Tasks and Dates from retrieved string
312
- let tasksString: string = result;
313
- let taskArray: TaskArray = JSON.parse(tasksString);
314
- this.tasks = this.#initTasksFromObjects(taskArray.tasks);
315
- let l = this.tasks.length;
316
- if (l !== 0) return;
303
+ // get initial data from localStorage or file
304
+ init(bClearLocalStorage: boolean): void {
305
+ console.log(`Calling TaskArray::init(bClearLocalStorage: ${bClearLocalStorage ? "true" : "false"})`);
306
+ // first clear task array
307
+ this.tasks.length = 0;
308
+ // then clear localStorage if we have been asked to
309
+ if (bClearLocalStorage) {
310
+ if (storageAvailable("localStorage")) localStorage.removeItem("Tasks");
311
+ }
312
+ // then try localStorage
313
+ if (storageAvailable("localStorage")) {
314
+ let result = localStorage.getItem("Tasks");
315
+ if (result != null && typeof result === "string" && result !== "") {
316
+ // properly create Tasks and Dates from retrieved string
317
+ let tasksString: string = result;
318
+ let taskArray: TaskArray = JSON.parse(tasksString);
319
+ this.tasks = this.#initTasksFromObjects(taskArray.tasks);
320
+ let l = this.tasks.length;
321
+ if (l !== 0) return;
322
+ }
317
323
  }
324
+ // if here, there was nothing in localStorage, use initialization file
325
+ this.tasks = this.#initTasksFromObjects(tasksData);
318
326
  }
319
- // if here, there was nothing in localStorage, use initialization file
320
- this.tasks = this.#initTasksFromObjects(tasksData);
321
- }
322
- // set start time for a task
323
- setTaskStart(taskType: TaskType, startDate: Date): void {
324
- let task: Task | undefined = this.#findTask(taskType);
325
- if (task != undefined && task != null) {
326
- task.setStart(startDate);
327
- task.status = "in progress";
328
- this.#save();
327
+ // set start time for a task
328
+ setTaskStart(taskType: TaskType, startDate: Date): void {
329
+ let task: Task | undefined = this.#findTask(taskType);
330
+ if (task != undefined && task != null) {
331
+ task.setStart(startDate);
332
+ task.status = "in progress";
333
+ this.#save();
334
+ }
335
+ else {
336
+ debugger;
337
+ }
329
338
  }
330
- else {
331
- debugger;
339
+ // set end time for a task
340
+ setTaskEnd(taskType: TaskType, endDate: Date, status: string): void {
341
+ let task: Task | undefined = this.#findTask(taskType);
342
+ if (task != undefined && task != null) {
343
+ task.setEnd(endDate);
344
+ task.status = status;
345
+ this.#save();
346
+ }
347
+ else {
348
+ debugger;
349
+ }
332
350
  }
333
- }
334
- // set end time for a task
335
- setTaskEnd(taskType: TaskType, endDate: Date, status: string): void {
336
- let task: Task | undefined = this.#findTask(taskType);
337
- if (task != undefined && task != null) {
338
- task.setEnd(endDate);
339
- task.status = status;
340
- this.#save();
351
+ //
352
+ // private
353
+ //
354
+ #findTask(taskType: TaskType): Task | undefined {
355
+ let task: Task | undefined = this.tasks.find(t => t.task == taskType);
356
+ if (task == undefined || task == null) {
357
+ for (task of this.tasks) {
358
+ if (task.subtasks != undefined && task.subtasks != null) {
359
+ task = task.subtasks.find(t => t.task == taskType);
360
+ if (task != undefined && task != null) break;
361
+ }
362
+ }
363
+ }
364
+ return task;
341
365
  }
342
- else {
343
- debugger;
366
+ #initTasksFromObjects(tasks: Task[]): Task[] {
367
+ return tasks.map((t: Task) => {
368
+ let newTask: Task = new Task();
369
+ newTask.id = t.id;
370
+ newTask.task = t.task;
371
+ newTask.setStart(new Date(t.start));
372
+ newTask.setEnd(new Date(t.end));
373
+ newTask.expected = t.expected;
374
+ newTask.status = t.status;
375
+ newTask.expanded = t.expanded;
376
+ if (typeof t.subtasks !== "undefined" && t.subtasks != null) {
377
+ newTask.subtasks = t.subtasks.map((st: Task) => {
378
+ let newSubtask: Task = new Task();
379
+ newSubtask.id = st.id;
380
+ newSubtask.task = st.task;
381
+ newSubtask.setStart(new Date(st.start))
382
+ newSubtask.setEnd(new Date(st.end));
383
+ newSubtask.expected = st.expected;
384
+ newSubtask.status = st.status;
385
+ newSubtask.expanded = st.expanded;
386
+ return newSubtask;
387
+ })
388
+ }
389
+ return newTask;
390
+ });
344
391
  }
345
- }
346
- //
347
- // private
348
- //
349
- #findTask(taskType: TaskType): Task | undefined {
350
- let task: Task | undefined = this.tasks.find(t => t.task == taskType);
351
- if (task == undefined || task == null) {
352
- for(task of this.tasks){
353
- if(task.subtasks != undefined && task.subtasks != null){
354
- task = task.subtasks.find(t => t.task == taskType);
355
- if(task != undefined && task != null) break;
392
+ #save(): void {
393
+ let taskArrayString: string = JSON.stringify(this);
394
+ if (storageAvailable("localStorage")) {
395
+ localStorage.setItem("Tasks", taskArrayString);
356
396
  }
357
- }
358
397
  }
359
- return task;
360
- }
361
- #initTasksFromObjects(tasks: Task[]): Task[]{
362
- return tasks.map((t: Task) => {
363
- let newTask: Task = new Task();
364
- newTask.id = t.id;
365
- newTask.task = t.task;
366
- newTask.setStart(new Date(t.start));
367
- newTask.setEnd(new Date(t.end));
368
- newTask.expected = t.expected;
369
- newTask.status = t.status;
370
- newTask.expanded = t.expanded;
371
- if(typeof t.subtasks !== "undefined" && t.subtasks != null){
372
- newTask.subtasks = t.subtasks.map((st: Task) => {
373
- let newSubtask: Task = new Task();
374
- newSubtask.id = st.id;
375
- newSubtask.task = st.task;
376
- newSubtask.setStart(new Date(st.start))
377
- newSubtask.setEnd(new Date(st.end));
378
- newSubtask.expected = st.expected;
379
- newSubtask.status = st.status;
380
- newSubtask.expanded = st.expanded;
381
- return newSubtask;
382
- } )
383
- }
384
- return newTask;
385
- } );
386
- }
387
- #save(): void{
388
- let taskArrayString: string = JSON.stringify(this);
389
- if (storageAvailable("localStorage")) {
390
- localStorage.setItem("Tasks", taskArrayString);
391
- }
392
- }
393
398
  }
394
399
  export class Task {
395
- id: number;
396
- task: string;
397
- start: Date;
398
- startDisplay: string;
399
- end: Date;
400
- endDisplay: string;
401
- elapsedDisplay: string;
402
- expected: number;
403
- status: string;
404
- expanded: boolean;
405
- subtasks: Task[];
406
- setEnd(endDate: Date): void{
407
- this.end = endDate;
408
- this.endDisplay = `${this.end.getMinutes().toString().padStart(2, "0")}:${this.end.getSeconds().toString().padStart(2, "0")}`;
409
- let minuteAdjustment: number = 0;
410
- let elapsedSeconds: number = this.end.getSeconds() - this.start.getSeconds();
411
- if (elapsedSeconds < 0) { elapsedSeconds += 60; minuteAdjustment = -1; }
412
- let elapsedMinutes: number = this.end.getMinutes() - this.start.getMinutes() + minuteAdjustment;
413
- if (elapsedMinutes < 0) elapsedMinutes += 60;
414
- this.elapsedDisplay = `${elapsedMinutes.toString().padStart(2, "0")}:${elapsedSeconds.toString().padStart(2, "0")}`;
415
- };
416
- setStart(startDate: Date): void{
417
- this.start = startDate;
418
- this.startDisplay = `${this.start.getMinutes().toString().padStart(2, "0")}:${this.start.getSeconds().toString().padStart(2, "0")}`;
419
- };
400
+ id: number;
401
+ task: string;
402
+ start: Date;
403
+ startDisplay: string;
404
+ end: Date;
405
+ endDisplay: string;
406
+ elapsedDisplay: string;
407
+ expected: number;
408
+ status: string;
409
+ expanded: boolean;
410
+ subtasks: Task[];
411
+ setEnd(endDate: Date): void {
412
+ this.end = endDate;
413
+ this.endDisplay = `${this.end.getMinutes().toString().padStart(2, "0")}:${this.end.getSeconds().toString().padStart(2, "0")}`;
414
+ let minuteAdjustment: number = 0;
415
+ let elapsedSeconds: number = this.end.getSeconds() - this.start.getSeconds();
416
+ if (elapsedSeconds < 0) { elapsedSeconds += 60; minuteAdjustment = -1; }
417
+ let elapsedMinutes: number = this.end.getMinutes() - this.start.getMinutes() + minuteAdjustment;
418
+ if (elapsedMinutes < 0) elapsedMinutes += 60;
419
+ this.elapsedDisplay = `${elapsedMinutes.toString().padStart(2, "0")}:${elapsedSeconds.toString().padStart(2, "0")}`;
420
+ };
421
+ setStart(startDate: Date): void {
422
+ this.start = startDate;
423
+ this.startDisplay = `${this.start.getMinutes().toString().padStart(2, "0")}:${this.start.getSeconds().toString().padStart(2, "0")}`;
424
+ };
420
425
  }
421
426
  // class corresponding to an execution of a Config - a *TenantNode* for each source tenant, each with a *TenantNode* array of target tenants
422
427
  export class BatchArray {
423
- tenantNodes: TenantNode[];
424
- constructor(
425
- config: Config | null,
426
- syncPortalGlobalState: InitInfo | null,
427
- bClearLocalStorage: boolean
428
- ) {
429
- this.tenantNodes = new Array<TenantNode>();
430
- this.init(config, syncPortalGlobalState, bClearLocalStorage);
431
- }
432
- // populate tenantNodes based on config tenants
433
- init(
434
- config: Config | null,
435
- syncPortalGlobalState: InitInfo | null,
436
- bClearLocalStorage: boolean
437
- ) : void {
438
- console.log(
439
- `Calling BatchArray::init(config: "${
440
- config ? config.name : "null"
441
- }", bClearLocalStorage: ${bClearLocalStorage ? "true" : "false"})`
442
- );
443
- // first clear batch array
444
- this.tenantNodes.length = 0;
445
- // then clear localStorage if we have been asked to
446
- if (bClearLocalStorage) {
447
- if (storageAvailable("localStorage"))
448
- localStorage.removeItem(config.name);
449
- }
450
- // create BatchArray if passed Config and InitInfo
451
- if (
452
- config != null &&
453
- config.tenants != null &&
454
- syncPortalGlobalState != null
428
+ tenantNodes: TenantNode[];
429
+ constructor(
430
+ config: Config | null,
431
+ syncPortalGlobalState: InitInfo | null,
432
+ bClearLocalStorage: boolean
455
433
  ) {
456
- // create a sourceTenantNode for each Source and SourceTarget
457
- config.tenants.map((tciPotentialSource: TenantConfigInfo) => {
434
+ this.tenantNodes = new Array<TenantNode>();
435
+ this.init(config, syncPortalGlobalState, bClearLocalStorage);
436
+ }
437
+ // populate tenantNodes based on config tenants
438
+ init(
439
+ config: Config | null,
440
+ syncPortalGlobalState: InitInfo | null,
441
+ bClearLocalStorage: boolean
442
+ ): void {
443
+ console.log(
444
+ `Calling BatchArray::init(config: "${config ? config.name : "null"
445
+ }", bClearLocalStorage: ${bClearLocalStorage ? "true" : "false"})`
446
+ );
447
+ // first clear batch array
448
+ this.tenantNodes.length = 0;
449
+ // then clear localStorage if we have been asked to
450
+ if (bClearLocalStorage) {
451
+ if (storageAvailable("localStorage"))
452
+ localStorage.removeItem(config.name);
453
+ }
454
+ // create BatchArray if passed Config and InitInfo
458
455
  if (
459
- tciPotentialSource.configurationTenantType === "source" ||
460
- tciPotentialSource.configurationTenantType === "sourcetarget"
456
+ config != null &&
457
+ config.tenants != null &&
458
+ syncPortalGlobalState != null
461
459
  ) {
462
- let sourceTenant = syncPortalGlobalState.ts.find(
463
- (t) => t.tid === tciPotentialSource.tid
464
- );
465
- if (sourceTenant != null) {
466
- let sourceTenantNode: TenantNode = new TenantNode(
467
- tciPotentialSource.tid,
468
- sourceTenant.name
469
- );
470
- this.tenantNodes.push(sourceTenantNode);
471
- } else {
472
- console.log(
473
- `Error: no tenant found for config source tenant ${config.name}`
474
- );
475
- debugger;
476
- return;
477
- }
460
+ // create a sourceTenantNode for each Source and SourceTarget
461
+ config.tenants.map((tciPotentialSource: TenantConfigInfo) => {
462
+ if (
463
+ tciPotentialSource.configurationTenantType === "source" ||
464
+ tciPotentialSource.configurationTenantType === "sourcetarget"
465
+ ) {
466
+ let sourceTenant = syncPortalGlobalState.ts.find(
467
+ (t) => t.tid === tciPotentialSource.tid
468
+ );
469
+ if (sourceTenant != null) {
470
+ let sourceTenantNode: TenantNode = new TenantNode(
471
+ tciPotentialSource.tid,
472
+ sourceTenant.name
473
+ );
474
+ this.tenantNodes.push(sourceTenantNode);
475
+ } else {
476
+ console.log(
477
+ `Error: no tenant found for config source tenant ${config.name}`
478
+ );
479
+ debugger;
480
+ return;
481
+ }
482
+ }
483
+ });
484
+ // create targetTenantNodes for each non-matching Target and SourceTarget
485
+ this.tenantNodes.map((sourceTenantNode: TenantNode) => {
486
+ config.tenants.map((tciPotentialTarget: TenantConfigInfo) => {
487
+ // is this a valid target?
488
+ if (
489
+ tciPotentialTarget.configurationTenantType === "target" ||
490
+ tciPotentialTarget.configurationTenantType === "sourcetarget"
491
+ ) {
492
+ // is this a valid target that does not match this source?
493
+ if (tciPotentialTarget.tid !== sourceTenantNode.tid) {
494
+ let targetTenant = syncPortalGlobalState.ts.find(
495
+ (t) => t.tid === tciPotentialTarget.tid
496
+ );
497
+ if (targetTenant != null) {
498
+ let targetTenantNode: TenantNode = new TenantNode(
499
+ tciPotentialTarget.tid,
500
+ targetTenant.name
501
+ );
502
+ sourceTenantNode.targets.push(targetTenantNode);
503
+ sourceTenantNode.expanded = true;
504
+ } else {
505
+ console.log(
506
+ `Error: no tenant found for config target tenant ${config.name}`
507
+ );
508
+ debugger;
509
+ return;
510
+ }
511
+ }
512
+ }
513
+ });
514
+ });
515
+ // then try localStorage to find any matching source tenant metrics
516
+ if (storageAvailable("localStorage")) {
517
+ let result = localStorage.getItem(config.name);
518
+ if (result != null && typeof result === "string" && result !== "") {
519
+ // TODO: retrieve any relevant stored statistics from localStorage
520
+ // let batchArrayString: string = result;
521
+ // let batchArray: BatchArray = JSON.parse(batchArrayString);
522
+ // batchArray.batches.map((batch: Batch) => {
523
+ // config.tenants.map((tciTarget: TenantConfigInfo) => {
524
+ // if(tciTarget.tid !== batch.tid) {
525
+ // let target: Target = new Target(tciTarget.tid);
526
+ // batch.targets.push(target);
527
+ // }
528
+ // });
529
+ // });
530
+ }
531
+ }
478
532
  }
479
- });
480
- // create targetTenantNodes for each non-matching Target and SourceTarget
481
- this.tenantNodes.map((sourceTenantNode: TenantNode) => {
482
- config.tenants.map((tciPotentialTarget: TenantConfigInfo) => {
483
- // is this a valid target?
484
- if (
485
- tciPotentialTarget.configurationTenantType === "target" ||
486
- tciPotentialTarget.configurationTenantType === "sourcetarget"
487
- ) {
488
- // is this a valid target that does not match this source?
489
- if (tciPotentialTarget.tid !== sourceTenantNode.tid) {
490
- let targetTenant = syncPortalGlobalState.ts.find(
491
- (t) => t.tid === tciPotentialTarget.tid
492
- );
493
- if (targetTenant != null) {
494
- let targetTenantNode: TenantNode = new TenantNode(
495
- tciPotentialTarget.tid,
496
- targetTenant.name
497
- );
498
- sourceTenantNode.targets.push(targetTenantNode);
499
- sourceTenantNode.expanded = true;
500
- } else {
501
- console.log(
502
- `Error: no tenant found for config target tenant ${config.name}`
503
- );
504
- debugger;
505
- return;
506
- }
533
+ }
534
+ /*
535
+ monitorSyncProgress(): void {
536
+ const connection = new signalR.HubConnectionBuilder()
537
+ .withUrl("https://dev-signalrdispatcher-westus.azurewebsites.net/?statsId=df9c2e0a-f6fe-43bb-a155-d51f66dffe0e")
538
+ .configureLogging(signalR.LogLevel.Information)
539
+ .build();
540
+
541
+ // when you get a message
542
+ connection.on("newMessage", function (message) {
543
+ console.log(message); // log the message
544
+ const item = JSON.parse(message); //parse it into an object
545
+ const statsarray = item.Stats; // get the array of statistics
546
+ const statskeys = Object.keys(statsarray); // get the keys of the array
547
+ const statsvalues = Object.values(statsarray); // get the values of the array
548
+ let statistics = ""; // initialize statistics
549
+ let total = 1;
550
+ let currentR = 0;
551
+ let currentW = 0;
552
+ let currentD = 0;
553
+ for (let j = 0; j < statskeys.length; j++) { // store the TotalCount and store as total
554
+ if (statskeys[j].endsWith("TotalCount")) {
555
+ total = statsvalues[j];
556
+ }
557
+ if (statskeys[j].endsWith("CurrentCount")) { // store the Reader value as currentR
558
+ if (statskeys[j].startsWith("Reader")) {
559
+ currentR = statsvalues[j];
560
+ }
561
+ if (statskeys[j].startsWith("Writer")) { // store the Writer value as currentW
562
+ currentW = statsvalues[j];
563
+ }
564
+ }
565
+ if (statskeys[j].endsWith("DeferredCount")) { // store the deferred count
566
+ currentD = statsvalues[j];
567
+ }
568
+ statistics = statistics + statskeys[j] + "=" + statsvalues[j] + "<br />" //
507
569
  }
508
- }
570
+ updateProgress(total, currentR, currentW, currentD);
571
+ myFunction(item.TargetID, statistics);
509
572
  });
510
- });
511
- // then try localStorage to find any matching source tenant metrics
512
- if (storageAvailable("localStorage")) {
513
- let result = localStorage.getItem(config.name);
514
- if (result != null && typeof result === "string" && result !== "") {
515
- // TODO: retrieve any relevant stored statistics from localStorage
516
- // let batchArrayString: string = result;
517
- // let batchArray: BatchArray = JSON.parse(batchArrayString);
518
- // batchArray.batches.map((batch: Batch) => {
519
- // config.tenants.map((tciTarget: TenantConfigInfo) => {
520
- // if(tciTarget.tid !== batch.tid) {
521
- // let target: Target = new Target(tciTarget.tid);
522
- // batch.targets.push(target);
523
- // }
524
- // });
525
- // });
573
+
574
+ connection.start().catch(console.error);
575
+
576
+ function myFunction(targetid, str) {
577
+ const table = document.getElementById("the-table");
578
+ let row = document.getElementById(targetid);
579
+ if (row) {
580
+ row.cells[1].innerHTML = "<code>" + str + "</ code>";
581
+ } else {
582
+ row = table.insertRow(1);
583
+ row.id = targetid;
584
+ let cell1 = row.insertCell(0);
585
+ let cell2 = row.insertCell(1);
586
+ let caption = (targetid === "00000000-0000-0000-0000-000000000000") ? "<B>Status of ServiceBus QUEUES</B>" : targetid;
587
+ cell1.innerHTML = "<code>" + caption + "</ code>";
588
+ cell2.innerHTML = "<code>" + str + "</ code>";
589
+ }
590
+ }
591
+
592
+ function updateProgress(total, currentR, currentW, currentD) {
593
+ updateRead(total, currentR);
594
+ updateWrite(total, currentW, currentD);
595
+ }
596
+
597
+ function updateRead(total, current) {
598
+ const element = document.getElementById("readBar");
599
+ const width = Math.round(100 * current / total);
600
+ element.style.width = width + '%';
601
+ element.innerHTML = "R=" + width + '%';
526
602
  }
527
- }
528
- }
529
- }
530
- // cycle through test state machine
531
- test(instance: IPublicClientApplication, authorizedUser: User|undefined, config: Config|null|undefined) : void {
532
- if (this.tenantNodes == null || this.tenantNodes.length == 0) {
533
- // we should not have an empty batch array for a test
534
- debugger;
535
- }
536
- // execute post to reader endpoint
537
- debugger;
538
- readerPost(instance, authorizedUser, config);
539
603
 
540
- // start SignalR connection
604
+ function updateWrite(total, w, d) {
605
+ const elementW = document.getElementById("writeBar");
606
+ const widthW = Math.round(100 * w / total);
607
+ elementW.style.width = widthW + '%';
608
+ elementW.innerHTML = "W=" + widthW + '%';
541
609
 
542
- // cycle through desired states for sources and targets
543
- if (this.tenantNodes != null) {
544
- this.tenantNodes.map((sourceTenantNode: TenantNode) => {
545
- if (sourceTenantNode.read == 0) sourceTenantNode.update(100, 50, 0, 0);
546
- else if (sourceTenantNode.read == 50) sourceTenantNode.update(100, 100, 0, 0);
547
- else sourceTenantNode.update(0, 0, 0, 0);
548
- if (sourceTenantNode.targets != null) {
549
- sourceTenantNode.targets.map((targetTenantNode: TenantNode) => {
550
- if (targetTenantNode.written == 0) targetTenantNode.update(100, 0, 50, 0);
551
- else if (targetTenantNode.written == 50) targetTenantNode.update(100, 0, 100, 0);
552
- else if (targetTenantNode.written == 100) targetTenantNode.update(100, 0, 99, 1);
553
- else targetTenantNode.update(0, 0, 0, 0);
554
- });
610
+ const elementD = document.getElementById("deferBar");
611
+ let width = 0;
612
+ let widthScaled = 0;
613
+ if (d > 0) {
614
+ width = Math.round(100 * d / total) + 1;
615
+ if (width < 10) {
616
+ widthScaled = width * 10;
617
+ }
618
+ else if (width < 20) {
619
+ widthScaled = width * 5;
620
+ }
621
+ else {
622
+ widthScaled = width;
623
+ }
624
+ }
625
+ if (widthScaled + widthW >= 99) {
626
+ widthScaled = 99 - widthW;
627
+ }
628
+ elementD.style.width = widthScaled + '%';
629
+ elementD.innerHTML = width + '%';
630
+ document.getElementById("deferred").innerHTML = 'Deferred:' + width + '% (' + d + ' of ' + total + ')';
631
+ // cycle through desired states for sources and targets
632
+ if (this.tenantNodes != null) {
633
+ this.tenantNodes.map((sourceTenantNode: TenantNode) => {
634
+ if (sourceTenantNode.read == 0) sourceTenantNode.update(100, 50, 0, 0);
635
+ else if (sourceTenantNode.read == 50) sourceTenantNode.update(100, 100, 0, 0);
636
+ else sourceTenantNode.update(0, 0, 0, 0);
637
+ if (sourceTenantNode.targets != null) {
638
+ sourceTenantNode.targets.map((targetTenantNode: TenantNode) => {
639
+ if (targetTenantNode.written == 0) targetTenantNode.update(100, 0, 50, 0);
640
+ else if (targetTenantNode.written == 50) targetTenantNode.update(100, 0, 100, 0);
641
+ else if (targetTenantNode.written == 100) targetTenantNode.update(100, 0, 99, 1);
642
+ else targetTenantNode.update(0, 0, 0, 0);
643
+ });
644
+ }
645
+ });
646
+ }
555
647
  }
556
- });
557
648
  }
558
- }
649
+ */
650
+ // cycle through test state machine
651
+ startSync(instance: IPublicClientApplication, authorizedUser: User | null | undefined, config: Config | null | undefined): void {
652
+ if (this.tenantNodes == null || this.tenantNodes.length == 0) {
653
+ // we should not have an empty batch array for a test
654
+ debugger;
655
+ }
656
+ // execute post to reader endpoint
657
+ readerPost(instance, authorizedUser, config);
658
+
659
+ // start SignalR connection
660
+ //debugger;
661
+ //monitorSyncProgress();
662
+ }
559
663
  }
560
664
  export class TenantNode {
561
- expanded: boolean;
562
- status: string;
563
- name: string;
564
- tid: string;
565
- total: number;
566
- read: number;
567
- written: number;
568
- deferred: number;
569
- targets: TenantNode[];
570
- constructor(tid: string, name: string) {
571
- this.expanded = false;
572
- this.name = name;
573
- this.tid = tid;
574
- this.targets = new Array<TenantNode>();
575
- this.update(0, 0, 0, 0);
576
- }
577
- update(total: number, read: number, written: number, deferred: number): void {
578
- this.total = total;
579
- this.read = read;
580
- this.written = written;
581
- this.deferred = deferred;
582
- if(this.read === 0 && this.written === 0) this.status = "not started";
583
- if(this.read > 0) {
584
- if(this.read < this.total) this.status = "in progress";
585
- else if(this.read === this.total) this.status = "complete";
665
+ expanded: boolean;
666
+ status: string;
667
+ name: string;
668
+ tid: string;
669
+ total: number;
670
+ read: number;
671
+ written: number;
672
+ deferred: number;
673
+ targets: TenantNode[];
674
+ constructor(tid: string, name: string) {
675
+ this.expanded = false;
676
+ this.name = name;
677
+ this.tid = tid;
678
+ this.targets = new Array<TenantNode>();
679
+ this.update(0, 0, 0, 0);
586
680
  }
587
- else if(this.written > 0) {
588
- if(this.written + this.deferred < this.total) this.status = "in progress";
589
- else if(this.written === this.total) this.status = "complete";
590
- else if(this.written + this.deferred === this.total) this.status = "failed";
681
+ update(total: number, read: number, written: number, deferred: number): void {
682
+ this.total = total;
683
+ this.read = read;
684
+ this.written = written;
685
+ this.deferred = deferred;
686
+ if (this.read === 0 && this.written === 0) this.status = "not started";
687
+ if (this.read > 0) {
688
+ if (this.read < this.total) this.status = "in progress";
689
+ else if (this.read === this.total) this.status = "complete";
690
+ }
691
+ else if (this.written > 0) {
692
+ if (this.written + this.deferred < this.total) this.status = "in progress";
693
+ else if (this.written === this.total) this.status = "complete";
694
+ else if (this.written + this.deferred === this.total) this.status = "failed";
695
+ }
591
696
  }
592
- }
593
697
  }
594
698
  export class APIResult {
595
- result: boolean;
596
- status: number;
597
- error: string;
598
- array: Array<Object> | null;
599
- constructor() { this.result = true; this.status = 200; this.error = ""; this.array = null; }
699
+ result: boolean;
700
+ status: number;
701
+ error: string;
702
+ array: Array<Object> | null;
703
+ constructor() { this.result = true; this.status = 200; this.error = ""; this.array = null; }
600
704
  }
601
705
  //
602
706
  // Azure AD Graph API
603
707
  //
604
708
  //groupGet - GET /groups/{id}
605
- export async function groupGet(tenant: Tenant, groupid: string): Promise<{group: string, error: string}> {
606
- // need a read or write access token to get graph users
607
- let accessToken: string = "";
608
- if(tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
609
- accessToken = tenant.readServicePrincipal;
610
- if(tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
611
- accessToken = tenant.writeServicePrincipal;
612
- if(accessToken === "") return { group: "", error: "no access token specified" };
613
- // prepare Authorization headers as part of options
614
- const headers = new Headers();
615
- const bearer = `Bearer ${accessToken}`;
616
- headers.append("Authorization", bearer);
617
- let options = { method: "GET", headers: headers };
618
- // make /groups endpoint call
619
- try {
620
- let groupsEndpoint = `${graphConfig.graphGroupsEndpoint}/${groupid}`;
621
- let response = await fetch(groupsEndpoint, options);
622
- let data = await response.json();
623
- if(typeof data.error !== "undefined"){
624
- return { group: "", error: `${data.error.code}: ${data.error.message}` };
709
+ export async function groupGet(tenant: Tenant, groupid: string): Promise<{ group: string, error: string }> {
710
+ // need a read or write access token to get graph users
711
+ let accessToken: string = "";
712
+ if (tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
713
+ accessToken = tenant.readServicePrincipal;
714
+ if (tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
715
+ accessToken = tenant.writeServicePrincipal;
716
+ if (accessToken === "") return { group: "", error: "no access token specified" };
717
+ // prepare Authorization headers as part of options
718
+ const headers = new Headers();
719
+ const bearer = `Bearer ${accessToken}`;
720
+ headers.append("Authorization", bearer);
721
+ let options = { method: "GET", headers: headers };
722
+ // make /groups endpoint call
723
+ try {
724
+ let groupsEndpoint = `${graphConfig.graphGroupsEndpoint}/${groupid}`;
725
+ let response = await fetch(groupsEndpoint, options);
726
+ let data = await response.json();
727
+ if (typeof data.error !== "undefined") {
728
+ return { group: "", error: `${data.error.code}: ${data.error.message}` };
729
+ }
730
+ return { group: data.value, error: `` };
731
+ }
732
+ catch (error: any) {
733
+ console.log(error);
734
+ return { group: "", error: `Exception: ${error}` };
625
735
  }
626
- return { group: data.value, error: `` };
627
- }
628
- catch(error: any) {
629
- console.log(error);
630
- return { group: "", error: `Exception: ${error}` };
631
- }
632
736
  }
633
737
  //groupsGet - GET /groups
634
- export async function groupsGet(tenant: Tenant, groupSearchString: string): Promise<{groups: Group[], error: string}> {
635
- // need a read or write access token to get graph users
636
- let accessToken: string = "";
637
- if(tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
638
- accessToken = tenant.readServicePrincipal;
639
- if(tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
640
- accessToken = tenant.writeServicePrincipal;
641
- if(accessToken === "") return { groups: [], error: "no access token specified" };
642
- // prepare Authorization headers as part of options
643
- const headers = new Headers();
644
- const bearer = `Bearer ${accessToken}`;
645
- headers.append("Authorization", bearer);
646
- let options = { method: "GET", headers: headers };
647
- // make /groups endpoint call
648
- try {
649
- let groupsEndpoint = `${graphConfig.graphGroupsEndpoint}/?$filter=startsWith(displayName, '${groupSearchString}')`;
650
- let response = await fetch(groupsEndpoint, options);
651
- let data = await response.json();
652
- if(typeof data.error !== "undefined"){
653
- return { groups: [], error: `${data.error.code}: ${data.error.message}` };
738
+ export async function groupsGet(tenant: Tenant, groupSearchString: string): Promise<{ groups: Group[], error: string }> {
739
+ // need a read or write access token to get graph users
740
+ let accessToken: string = "";
741
+ if (tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
742
+ accessToken = tenant.readServicePrincipal;
743
+ if (tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
744
+ accessToken = tenant.writeServicePrincipal;
745
+ if (accessToken === "") return { groups: [], error: "no access token specified" };
746
+ // prepare Authorization headers as part of options
747
+ const headers = new Headers();
748
+ const bearer = `Bearer ${accessToken}`;
749
+ headers.append("Authorization", bearer);
750
+ let options = { method: "GET", headers: headers };
751
+ // make /groups endpoint call
752
+ try {
753
+ let groupsEndpoint = `${graphConfig.graphGroupsEndpoint}/?$filter=startsWith(displayName, '${groupSearchString}')`;
754
+ let response = await fetch(groupsEndpoint, options);
755
+ let data = await response.json();
756
+ if (typeof data.error !== "undefined") {
757
+ return { groups: [], error: `${data.error.code}: ${data.error.message}` };
758
+ }
759
+ return { groups: data.value, error: `` };
760
+ }
761
+ catch (error: any) {
762
+ console.log(error);
763
+ return { group: "", error: `Exception: ${error}` };
654
764
  }
655
- return { groups: data.value, error: `` };
656
- }
657
- catch(error: any) {
658
- console.log(error);
659
- return { group: "", error: `Exception: ${error}` };
660
- }
661
765
  }
662
766
  export function signIn(user: User, tasks: TaskArray): void {
663
- let tenantURL: string = window.location.href;
664
- tenantURL += "MicrosoftIdentity/Account/Challenge";
665
- let url: URL = new URL(tenantURL);
666
- url.searchParams.append("redirectUri", window.location.origin);
667
- url.searchParams.append("scope", "openid offline_access profile user.read contacts.read CrossTenantInformation.ReadBasic.All");
668
- url.searchParams.append("domainHint", "organizations");
669
- if (user.oid !== "1"){
670
- url.searchParams.append("loginHint", user.mail);
671
- }
672
- tasks.setTaskStart("initialization", new Date());
673
- tasks.setTaskStart("authenticate user", new Date());
674
- window.location.assign(url.href);
767
+ let tenantURL: string = window.location.href;
768
+ tenantURL += "MicrosoftIdentity/Account/Challenge";
769
+ let url: URL = new URL(tenantURL);
770
+ url.searchParams.append("redirectUri", window.location.origin);
771
+ url.searchParams.append("scope", "openid offline_access profile user.read contacts.read CrossTenantInformation.ReadBasic.All");
772
+ url.searchParams.append("domainHint", "organizations");
773
+ if (user.oid !== "1") {
774
+ url.searchParams.append("loginHint", user.mail);
775
+ }
776
+ tasks.setTaskStart("initialization", new Date());
777
+ tasks.setTaskStart("authenticate user", new Date());
778
+ window.location.assign(url.href);
675
779
  }
676
780
  export function signInIncrementally(user: User, scope: string): void {
677
- if (user.oid == "1") return;
678
- let tenantURL: string = window.location.href;
679
- tenantURL += "MicrosoftIdentity/Account/Challenge";
680
- let url: URL = new URL(tenantURL);
681
- url.searchParams.append("redirectUri", window.location.origin);
682
- let scopes = scope;
683
- url.searchParams.append("scope", scopes);
684
- url.searchParams.append("domainHint", "organizations");
685
- url.searchParams.append("loginHint", user.mail);
686
- window.location.assign(url.href);
781
+ if (user.oid == "1") return;
782
+ let tenantURL: string = window.location.href;
783
+ tenantURL += "MicrosoftIdentity/Account/Challenge";
784
+ let url: URL = new URL(tenantURL);
785
+ url.searchParams.append("redirectUri", window.location.origin);
786
+ let scopes = scope;
787
+ url.searchParams.append("scope", scopes);
788
+ url.searchParams.append("domainHint", "organizations");
789
+ url.searchParams.append("loginHint", user.mail);
790
+ window.location.assign(url.href);
687
791
  }
688
792
  export function signOut(user: User): void {
689
- if (user.oid == "1") return;
690
- // these lines provide more callbacks during logout
691
- //let tenantURL: string = window.location.href;
692
- //tenantURL += "MicrosoftIdentity/Account/SignOut";
693
- // this line takes advantage of our saved loginHint to logout right away, but requires additional cleanup logic
694
- // https://aaddevsup.azurewebsites.net/2022/03/how-to-logout-of-an-oauth2-application-without-getting-prompted-to-select-a-user/
695
- let tenantURL: string = "https://login.microsoftonline.com/common/oauth2/logout";
696
- let url: URL = new URL(tenantURL);
697
- url.searchParams.append("post_logout_redirect_uri", window.location.origin);
698
- url.searchParams.append("logout_hint", user.loginHint);
699
- window.location.assign(url.href);
793
+ if (user.oid == "1") return;
794
+ // these lines provide more callbacks during logout
795
+ //let tenantURL: string = window.location.href;
796
+ //tenantURL += "MicrosoftIdentity/Account/SignOut";
797
+ // this line takes advantage of our saved loginHint to logout right away, but requires additional cleanup logic
798
+ // https://aaddevsup.azurewebsites.net/2022/03/how-to-logout-of-an-oauth2-application-without-getting-prompted-to-select-a-user/
799
+ let tenantURL: string = "https://login.microsoftonline.com/common/oauth2/logout";
800
+ let url: URL = new URL(tenantURL);
801
+ url.searchParams.append("post_logout_redirect_uri", window.location.origin);
802
+ url.searchParams.append("logout_hint", user.loginHint);
803
+ window.location.assign(url.href);
700
804
  }
701
805
  //tenantRelationshipsGetByDomain - query AAD for associated company name and id
702
806
  export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant: Tenant, instance: IPublicClientApplication, debug: boolean): Promise<boolean> {
703
- if (debug) debugger;
704
- // do we already have a valid tenant name? if so, nothing to add
705
- if (typeof tenant.name !== 'undefined' && tenant.name !== "") return false;
706
- // if needed, retrieve and cache access token
707
- if (typeof loggedInUser.accessToken === 'undefined' || loggedInUser.accessToken === "") {
708
- console.log(`tenantRelationshipsGetByDomain called with invalid logged in user: ${loggedInUser.name}`);
709
- try {
710
- let response: AuthenticationResult = await instance.acquireTokenByCode({ code: loggedInUser.spacode });
711
- loggedInUser.accessToken = response.accessToken; // cache access token on the user
712
- console.log("Front end token acquired: " + loggedInUser.accessToken.slice(0,20));
713
- }
714
- catch(error: any) {
715
- console.log("Front end token failure: " + error);
716
- return false; // failed to get access token, no need to re-render
807
+ if (debug) debugger;
808
+ // do we already have a valid tenant name? if so, nothing to add
809
+ if (typeof tenant.name !== 'undefined' && tenant.name !== "") return false;
810
+ // if needed, retrieve and cache access token
811
+ if (typeof loggedInUser.accessToken === 'undefined' || loggedInUser.accessToken === "") {
812
+ console.log(`tenantRelationshipsGetByDomain called with invalid logged in user: ${loggedInUser.name}`);
813
+ try {
814
+ let response: AuthenticationResult = await instance.acquireTokenByCode({ code: loggedInUser.spacode });
815
+ loggedInUser.accessToken = response.accessToken; // cache access token on the user
816
+ console.log("Front end token acquired: " + loggedInUser.accessToken.slice(0, 20));
817
+ }
818
+ catch (error: any) {
819
+ console.log("Front end token failure: " + error);
820
+ return false; // failed to get access token, no need to re-render
821
+ }
717
822
  }
718
- }
719
- // prepare Authorization headers as part of options
720
- const headers = new Headers();
721
- const bearer = `Bearer ${loggedInUser.accessToken}`;
722
- headers.append("Authorization", bearer);
723
- let options = { method: "GET", headers: headers };
724
- // make tenant endpoint call
725
- try {
726
- // create tenant info endpoint
727
- var tenantEndpoint = graphConfig.graphTenantByDomainEndpoint;
728
- tenantEndpoint += "(domainName='";
729
- tenantEndpoint += tenant.domain;
730
- tenantEndpoint += "')";
731
- console.log("Attempting GET from /findTenantInformationByDomainName:", tenantEndpoint);
732
- let response = await fetch(tenantEndpoint, options);
733
- let data = await response.json();
734
- if(data) {
735
- if(typeof data.error !== "undefined") {
736
- console.log("Failed GET from /findTenantInformationByDomainName: ", data.error.message);
737
- return false;
738
- }
739
- else if (typeof data.displayName !== undefined && data.displayName !== "") {
740
- // set domain information on passed tenant
741
- tenant.tid = data.tenantId;
742
- tenant.name = data.displayName;
743
- console.log("Successful GET from /findTenantInformationByDomainName: ", data.displayName);
744
- return true; // success, need UX to re-render
745
- }
823
+ // prepare Authorization headers as part of options
824
+ const headers = new Headers();
825
+ const bearer = `Bearer ${loggedInUser.accessToken}`;
826
+ headers.append("Authorization", bearer);
827
+ let options = { method: "GET", headers: headers };
828
+ // make tenant endpoint call
829
+ try {
830
+ // create tenant info endpoint
831
+ var tenantEndpoint = graphConfig.graphTenantByDomainEndpoint;
832
+ tenantEndpoint += "(domainName='";
833
+ tenantEndpoint += tenant.domain;
834
+ tenantEndpoint += "')";
835
+ console.log("Attempting GET from /findTenantInformationByDomainName:", tenantEndpoint);
836
+ let response = await fetch(tenantEndpoint, options);
837
+ let data = await response.json();
838
+ if (data) {
839
+ if (typeof data.error !== "undefined") {
840
+ console.log("Failed GET from /findTenantInformationByDomainName: ", data.error.message);
841
+ return false;
842
+ }
843
+ else if (typeof data.displayName !== undefined && data.displayName !== "") {
844
+ // set domain information on passed tenant
845
+ tenant.tid = data.tenantId;
846
+ tenant.name = data.displayName;
847
+ console.log("Successful GET from /findTenantInformationByDomainName: ", data.displayName);
848
+ return true; // success, need UX to re-render
849
+ }
850
+ }
851
+ else {
852
+ console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
853
+ }
746
854
  }
747
- else{
748
- console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
855
+ catch (error: any) {
856
+ console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
857
+ return false; // failed, no need for UX to re-render
749
858
  }
750
- }
751
- catch(error: any) {
752
- console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
753
859
  return false; // failed, no need for UX to re-render
754
- }
755
- return false; // failed, no need for UX to re-render
756
860
  }
757
861
  //tenantRelationshipsGetById - query AAD for associated company name and domain
758
862
  export async function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): Promise<boolean> {
759
- if (debug) debugger;
760
- // do we already have a valid company name? if so, nothing to add, no need for UX to re-render
761
- if (typeof user.companyName !== 'undefined' && user.companyName !== "") return false;
762
- // if needed, retrieve and cache access token
763
- if (typeof user.accessToken === 'undefined' || user.accessToken === "") {
764
- try {
765
- let response: AuthenticationResult = await instance.acquireTokenByCode({ code: user.spacode });
766
- user.accessToken = response.accessToken; // cache access token
767
- console.log("Front end token acquired: " + user.accessToken.slice(0,20));
768
- }
769
- catch(error: any) {
770
- console.log("Front end token failure: " + error);
771
- return false; // failed to get access token, no need to re-render
863
+ if (debug) debugger;
864
+ // do we already have a valid company name? if so, nothing to add, no need for UX to re-render
865
+ if (typeof user.companyName !== 'undefined' && user.companyName !== "") return false;
866
+ // if needed, retrieve and cache access token
867
+ if (typeof user.accessToken === 'undefined' || user.accessToken === "") {
868
+ try {
869
+ let response: AuthenticationResult = await instance.acquireTokenByCode({ code: user.spacode });
870
+ user.accessToken = response.accessToken; // cache access token
871
+ console.log("Front end token acquired: " + user.accessToken.slice(0, 20));
872
+ }
873
+ catch (error: any) {
874
+ console.log("Front end token failure: " + error);
875
+ return false; // failed to get access token, no need to re-render
876
+ }
772
877
  }
773
- }
774
- // prepare Authorization headers as part of options
775
- const headers = new Headers();
776
- const bearer = `Bearer ${user.accessToken}`;
777
- headers.append("Authorization", bearer);
778
- let options = { method: "GET", headers: headers };
779
- // make tenant endpoint call
780
- try {
781
- // create tenant info endpoint
782
- var tenantEndpoint = graphConfig.graphTenantByIdEndpoint;
783
- tenantEndpoint += "(tenantId='";
784
- tenantEndpoint += user.tid;
785
- tenantEndpoint += "')";
786
- // track time of tenant details query
787
- tasks.setTaskStart("GET tenant details", new Date());
788
- console.log("Attempting GET from /findTenantInformationByTenantId:", tenantEndpoint);
789
- let response = await fetch(tenantEndpoint, options);
790
- let data = await response.json();
791
- if(data && typeof data.displayName !== undefined && data.displayName !== "") {
792
- // set domain information on user
793
- user.companyName = data.displayName;
794
- user.companyDomain = data.defaultDomainName;
795
- // set domain information on tenant
796
- let tenant: Tenant | undefined = ii.ts.find((t) => t.tid === user.tid);
797
- if(tenant !== undefined){
798
- tenant.name = data.displayName;
799
- tenant.domain = data.defaultDomainName;
800
- }
801
- else{
802
- console.log("tenantRelationshipsGetById: missing associated tenant for logged in user.");
803
- debugger;
804
- }
805
- console.log("Successful GET from /findTenantInformationByTenantId: ", data.displayName);
806
- tasks.setTaskEnd("GET tenant details", new Date(), "complete");
807
- return true; // success, need UX to re-render
878
+ // prepare Authorization headers as part of options
879
+ const headers = new Headers();
880
+ const bearer = `Bearer ${user.accessToken}`;
881
+ headers.append("Authorization", bearer);
882
+ let options = { method: "GET", headers: headers };
883
+ // make tenant endpoint call
884
+ try {
885
+ // create tenant info endpoint
886
+ var tenantEndpoint = graphConfig.graphTenantByIdEndpoint;
887
+ tenantEndpoint += "(tenantId='";
888
+ tenantEndpoint += user.tid;
889
+ tenantEndpoint += "')";
890
+ // track time of tenant details query
891
+ tasks.setTaskStart("GET tenant details", new Date());
892
+ console.log("Attempting GET from /findTenantInformationByTenantId:", tenantEndpoint);
893
+ let response = await fetch(tenantEndpoint, options);
894
+ let data = await response.json();
895
+ if (data && typeof data.displayName !== undefined && data.displayName !== "") {
896
+ // set domain information on user
897
+ user.companyName = data.displayName;
898
+ user.companyDomain = data.defaultDomainName;
899
+ // set domain information on tenant
900
+ let tenant: Tenant | undefined = ii.ts.find((t) => t.tid === user.tid);
901
+ if (tenant !== undefined) {
902
+ tenant.name = data.displayName;
903
+ tenant.domain = data.defaultDomainName;
904
+ }
905
+ else {
906
+ console.log("tenantRelationshipsGetById: missing associated tenant for logged in user.");
907
+ debugger;
908
+ }
909
+ console.log("Successful GET from /findTenantInformationByTenantId: ", data.displayName);
910
+ tasks.setTaskEnd("GET tenant details", new Date(), "complete");
911
+ return true; // success, need UX to re-render
912
+ }
913
+ else {
914
+ console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
915
+ }
808
916
  }
809
- else{
810
- console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
917
+ catch (error: any) {
918
+ console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
919
+ tasks.setTaskEnd("GET tenant details", new Date(), "failed");
920
+ return false; // failed, no need for UX to re-render
811
921
  }
812
- }
813
- catch(error: any) {
814
- console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
815
922
  tasks.setTaskEnd("GET tenant details", new Date(), "failed");
816
923
  return false; // failed, no need for UX to re-render
817
- }
818
- tasks.setTaskEnd("GET tenant details", new Date(), "failed");
819
- return false; // failed, no need for UX to re-render
820
924
  }
821
925
  //usersGet - GET from AAD Users endpoint
822
- export async function usersGet(tenant: Tenant): Promise<{users: string[], error: string}> {
823
- // need a read or write access token to get graph users
824
- let accessToken: string = "";
825
- if(tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
826
- accessToken = tenant.readServicePrincipal;
827
- if(tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
828
- accessToken = tenant.writeServicePrincipal;
829
- if(accessToken === "") return { users: [], error: "no access token specified" };
830
- // prepare Authorization headers as part of options
831
- const headers = new Headers();
832
- const bearer = `Bearer ${accessToken}`;
833
- headers.append("Authorization", bearer);
834
- let options = { method: "GET", headers: headers };
835
- // make /users endpoint call
836
- try {
837
- let response = await fetch(graphConfig.graphUsersEndpoint, options);
838
- let data = await response.json();
839
- if(typeof data.error !== "undefined"){
840
- return { users: [], error: `${data.error.code}: ${data.error.message}` };
926
+ export async function usersGet(tenant: Tenant): Promise<{ users: string[], error: string }> {
927
+ // need a read or write access token to get graph users
928
+ let accessToken: string = "";
929
+ if (tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
930
+ accessToken = tenant.readServicePrincipal;
931
+ if (tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
932
+ accessToken = tenant.writeServicePrincipal;
933
+ if (accessToken === "") return { users: [], error: "no access token specified" };
934
+ // prepare Authorization headers as part of options
935
+ const headers = new Headers();
936
+ const bearer = `Bearer ${accessToken}`;
937
+ headers.append("Authorization", bearer);
938
+ let options = { method: "GET", headers: headers };
939
+ // make /users endpoint call
940
+ try {
941
+ let response = await fetch(graphConfig.graphUsersEndpoint, options);
942
+ let data = await response.json();
943
+ if (typeof data.error !== "undefined") {
944
+ return { users: [], error: `${data.error.code}: ${data.error.message}` };
945
+ }
946
+ let users = new Array<User>();
947
+ for (let user of data.value) {
948
+ users.push(user.mail);
949
+ }
950
+ return { users: users, error: `` };
841
951
  }
842
- let users = new Array<User>();
843
- for (let user of data.value) {
844
- users.push(user.mail);
952
+ catch (error: any) {
953
+ console.log(error);
954
+ return { users: [], error: `Exception: ${error}` };
845
955
  }
846
- return { users: users, error: `` };
847
- }
848
- catch(error: any) {
849
- console.log(error);
850
- return { users: [], error: `Exception: ${error}` };
851
- }
852
956
  }
853
957
  //
854
958
  // Mindline Config API
855
959
  //
856
960
  export async function configEdit(instance: IPublicClientApplication, authorizedUser: User, config: Config, workspaceId: string, debug: boolean): Promise<APIResult> {
857
- let result: APIResult = new APIResult();
858
- if (config.id === "1") {
859
- result = await configPost(instance, authorizedUser, config, workspaceId, debug);
860
- }
861
- else {
862
- result = await configPut(instance, authorizedUser, config, debug);
863
- }
864
- return result;
961
+ let result: APIResult = new APIResult();
962
+ if (config.id === "1") {
963
+ result = await configPost(instance, authorizedUser, config, workspaceId, debug);
964
+ }
965
+ else {
966
+ result = await configPut(instance, authorizedUser, config, debug);
967
+ }
968
+ return result;
865
969
  }
866
970
  export async function configRemove(instance: IPublicClientApplication, authorizedUser: User, config: Config, workspaceId: string, debug: boolean): Promise<APIResult> {
867
- return configDelete(instance, authorizedUser, config, workspaceId, debug);
971
+ return configDelete(instance, authorizedUser, config, workspaceId, debug);
868
972
  }
869
973
  // retrieve Workspace(s), User(s), Tenant(s), Config(s) given newly logged in user
870
- export async function initGet(instance: IPublicClientApplication, authorizedUser: User, user: User, ii: InitInfo, tasks: TaskArray, debug: boolean): Promise<APIResult>
871
- {
872
- let result: APIResult = new APIResult();
873
- if (debug) debugger;
874
- // get tenant name and domain from AAD
875
- result.result = await tenantRelationshipsGetById(user, ii, instance, tasks, debug);
876
- // if this is the first time, we have just gotten tenant info, then we must POST user and not-yet-onboarded tenant to back end
877
- if (result.result) {
878
- tasks.setTaskStart("POST config init", new Date());
879
- result = await initPost(instance, authorizedUser, user, debug);
880
- tasks.setTaskEnd("POST config init", new Date(), result.result ? "complete" : "failed");
881
- }
882
- // simlarly, if we just did our first post, then query config backend for workspace(s) associated with this user
883
- if (result.result) {
884
- tasks.setTaskStart("GET workspaces", new Date());
885
- result = await workspaceInfoGet(instance, authorizedUser, user, ii, debug);
886
- tasks.setTaskEnd("GET workspaces", new Date(), result ? "complete" : "failed");
887
- }
888
- if(result.result) result.error = version;
889
- return result;
974
+ export async function initGet(instance: IPublicClientApplication, authorizedUser: User, user: User, ii: InitInfo, tasks: TaskArray, debug: boolean): Promise<APIResult> {
975
+ let result: APIResult = new APIResult();
976
+ if (debug) debugger;
977
+ // get tenant name and domain from AAD
978
+ result.result = await tenantRelationshipsGetById(user, ii, instance, tasks, debug);
979
+ // if this is the first time, we have just gotten tenant info, then we must POST user and not-yet-onboarded tenant to back end
980
+ if (result.result) {
981
+ tasks.setTaskStart("POST config init", new Date());
982
+ result = await initPost(instance, authorizedUser, user, debug);
983
+ tasks.setTaskEnd("POST config init", new Date(), result.result ? "complete" : "failed");
984
+ }
985
+ // simlarly, if we just did our first post, then query config backend for workspace(s) associated with this user
986
+ if (result.result) {
987
+ tasks.setTaskStart("GET workspaces", new Date());
988
+ result = await workspaceInfoGet(instance, authorizedUser, user, ii, debug);
989
+ tasks.setTaskEnd("GET workspaces", new Date(), result.result ? "complete" : "failed");
990
+ }
991
+ if (result.result) result.error = version;
992
+ return result;
890
993
  }
891
994
  export async function tenantAdd(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, workspaceId: string): Promise<APIResult> {
892
- return tenantPost(instance, authorizedUser, tenant, workspaceId);
995
+ return tenantPost(instance, authorizedUser, tenant, workspaceId);
893
996
  }
894
997
  export async function tenantComplete(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, debug: boolean): Promise<APIResult> {
895
- return tenantPut(instance, authorizedUser, tenant, debug);
998
+ return tenantPut(instance, authorizedUser, tenant, debug);
896
999
  }
897
1000
  export async function tenantRemove(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, workspaceId: string, debug: boolean): Promise<APIResult> {
898
- return tenantDelete(instance, authorizedUser, tenant, workspaceId, debug);
1001
+ return tenantDelete(instance, authorizedUser, tenant, workspaceId, debug);
899
1002
  }
900
1003
  export async function userAdd(instance: IPublicClientApplication, authorizedUser: User, user: User, workspaceId: string): Promise<APIResult> {
901
- return adminPost(instance, authorizedUser, user, workspaceId);
1004
+ return adminPost(instance, authorizedUser, user, workspaceId);
902
1005
  }
903
1006
  export async function userRemove(instance: IPublicClientApplication, authorizedUser: User, user: User, workspaceId: string): Promise<APIResult> {
904
- return adminDelete(instance, authorizedUser, user, workspaceId);
1007
+ return adminDelete(instance, authorizedUser, user, workspaceId);
905
1008
  }
906
1009
  //
907
1010
  // Mindline Config API internal helper functions
908
1011
  //
909
- function processReturnedAdmins(workspace: Workspace, ii: InitInfo, returnedAdmins: Array<Object>)
910
- {
911
- returnedAdmins.map((item) => {
912
- // are we already tracking this user?
913
- let user: User|null = null;
914
- let usIndex = ii.us.findIndex((u) => u.oid === item.userId);
915
- if(usIndex===-1) {
916
- // start tracking
917
- let dummyIndex = ii.us.findIndex((u) => u.oid === "1");
918
- if(dummyIndex!==-1) {
919
- // clear and overwrite dummy
920
- user = ii.us.at(dummyIndex);
921
- user.associatedWorkspaces.length = 0;
922
- }
923
- else {
924
- // create and track new user
925
- user = new User();
926
- ii.us.push(user);
927
- }
928
- } else {
929
- // already tracking this user
930
- user = ii.us.at(usIndex);
931
- }
932
- // refresh all the data available from the server
933
- user.oid = item.userId;
934
- user.name = item.firstName;
935
- user.mail = item.email;
936
- user.tid = item.tenantId;
937
- // ensure this workspace tracks this user
938
- let idx = workspace.associatedUsers.findIndex((u) => u === item.userId);
939
- if(idx == -1) workspace.associatedUsers.push(item.userId);
940
- });
1012
+ function processReturnedAdmins(workspace: Workspace, ii: InitInfo, returnedAdmins: Array<Object>) {
1013
+ returnedAdmins.map((item) => {
1014
+ // are we already tracking this user?
1015
+ let user: User | null = null;
1016
+ let usIndex = ii.us.findIndex((u) => u.oid === item.userId);
1017
+ if (usIndex === -1) {
1018
+ // start tracking
1019
+ let dummyIndex = ii.us.findIndex((u) => u.oid === "1");
1020
+ if (dummyIndex !== -1) {
1021
+ // clear and overwrite dummy
1022
+ user = ii.us.at(dummyIndex);
1023
+ user.associatedWorkspaces.length = 0;
1024
+ }
1025
+ else {
1026
+ // create and track new user
1027
+ user = new User();
1028
+ ii.us.push(user);
1029
+ }
1030
+ } else {
1031
+ // already tracking this user
1032
+ user = ii.us.at(usIndex);
1033
+ }
1034
+ // refresh all the data available from the server
1035
+ user.oid = item.userId;
1036
+ user.name = item.firstName;
1037
+ user.mail = item.email;
1038
+ user.tid = item.tenantId;
1039
+ // ensure this workspace tracks this user
1040
+ let idx = workspace.associatedUsers.findIndex((u) => u === item.userId);
1041
+ if (idx == -1) workspace.associatedUsers.push(item.userId);
1042
+ });
941
1043
  }
942
- function processReturnedTenants(workspace: Workspace, ii: InitInfo, returnedTenants: Array<Object>)
943
- {
944
- returnedTenants.map((item) => {
945
- // are we already tracking this tenant?
946
- let tenant: Tenant|null = null;
947
- let tsIndex = ii.ts.findIndex((t) => t.tid === item.tenantId);
948
- if (tsIndex === -1) {
949
- // start tracking
950
- let dummyIndex = ii.ts.findIndex((t) => t.tid === "1");
951
- if (dummyIndex !== -1) {
952
- // clear and overwrite dummy
953
- tenant = ii.ts.at(dummyIndex);
954
- } else {
955
- // create and track new workspace
956
- tenant = new Tenant();
957
- ii.ts.push(tenant);
958
- }
959
- } else {
960
- // already tracking this tenant
961
- tenant = ii.ts.at(tsIndex);
962
- }
963
- tenant.tid = item.tenantId;
964
- tenant.name = item.name;
965
- tenant.domain = item.domain;
966
- tenant.tenantType = item.type.toLowerCase(); // should now be strings
967
- tenant.permissionType = item.permissionType.toLowerCase(); // should now be strings
968
- tenant.onboarded = item.isOnboarded ? "true" : "false";
969
- tenant.authority = item.authority;
970
- tenant.readServicePrincipal = item.readServicePrincipal;
971
- tenant.writeServicePrincipal = item.writeServicePrincipal;
972
- // ensure this workspace tracks this tenant
973
- let idx = workspace.associatedTenants.findIndex((t) => t === item.tenantId);
974
- if (idx == -1) workspace.associatedTenants.push(item.tenantId);
975
- });
1044
+ function processReturnedTenants(workspace: Workspace, ii: InitInfo, returnedTenants: Array<Object>) {
1045
+ returnedTenants.map((item) => {
1046
+ // are we already tracking this tenant?
1047
+ let tenant: Tenant | null = null;
1048
+ let tsIndex = ii.ts.findIndex((t) => t.tid === item.tenantId);
1049
+ if (tsIndex === -1) {
1050
+ // start tracking
1051
+ let dummyIndex = ii.ts.findIndex((t) => t.tid === "1");
1052
+ if (dummyIndex !== -1) {
1053
+ // clear and overwrite dummy
1054
+ tenant = ii.ts.at(dummyIndex);
1055
+ } else {
1056
+ // create and track new workspace
1057
+ tenant = new Tenant();
1058
+ ii.ts.push(tenant);
1059
+ }
1060
+ } else {
1061
+ // already tracking this tenant
1062
+ tenant = ii.ts.at(tsIndex);
1063
+ }
1064
+ tenant.tid = item.tenantId;
1065
+ tenant.name = item.name;
1066
+ tenant.domain = item.domain;
1067
+ tenant.tenantType = item.type.toLowerCase(); // should now be strings
1068
+ tenant.permissionType = item.permissionType.toLowerCase(); // should now be strings
1069
+ tenant.onboarded = item.isOnboarded ? "true" : "false";
1070
+ tenant.authority = item.authority;
1071
+ tenant.readServicePrincipal = item.readServicePrincipal;
1072
+ tenant.writeServicePrincipal = item.writeServicePrincipal;
1073
+ // ensure this workspace tracks this tenant
1074
+ let idx = workspace.associatedTenants.findIndex((t) => t === item.tenantId);
1075
+ if (idx == -1) workspace.associatedTenants.push(item.tenantId);
1076
+ });
976
1077
  }
977
- function processReturnedConfigs(workspace: Workspace, ii: InitInfo, returnedConfigs: Array<Object>)
978
- {
979
- // process returned configs
980
- returnedConfigs.map((item) => {
981
- // are we already tracking this config?
982
- let config: Config | null = null;
983
- let csIndex = ii.cs.findIndex((c) => c.id === item.id);
984
- if (csIndex === -1) {
985
- // start tracking
986
- let dummyIndex = ii.cs.findIndex((c) => c.id === "1");
987
- if (dummyIndex !== -1) {
988
- // clear and overwrite dummy
989
- config = ii.cs.at(dummyIndex);
990
- } else {
991
- // create and track new workspace
992
- config = new Config();
993
- ii.cs.push(config);
994
- }
995
- } else {
996
- // already tracking this config
997
- config = ii.cs.at(csIndex);
998
- }
999
- config!.id = item.id;
1000
- config!.name = item.name;
1001
- config!.description = item.description;
1002
- config!.isEnabled = item.isEnabled;
1003
- // create TenantConfigInfo array
1004
- config!.tenants.length = 0;
1005
- item.tenants.map((tci) => {
1006
- let tenantConfigInfo = new TenantConfigInfo();
1007
- tenantConfigInfo.tid = tci.tenantId;
1008
- tenantConfigInfo.sourceGroupId = tci.sourceGroupId;
1009
- tenantConfigInfo.sourceGroupName = tci.sourceGroupName;
1010
- tenantConfigInfo.configurationTenantType = tci.configurationTenantType.toLowerCase();
1011
- config!.tenants.push(tenantConfigInfo);
1078
+ function processReturnedConfigs(workspace: Workspace, ii: InitInfo, returnedConfigs: Array<Object>) {
1079
+ // process returned configs
1080
+ returnedConfigs.map((item) => {
1081
+ // are we already tracking this config?
1082
+ let config: Config | null = null;
1083
+ let csIndex = ii.cs.findIndex((c) => c.id === item.id);
1084
+ if (csIndex === -1) {
1085
+ // start tracking
1086
+ let dummyIndex = ii.cs.findIndex((c) => c.id === "1");
1087
+ if (dummyIndex !== -1) {
1088
+ // clear and overwrite dummy
1089
+ config = ii.cs.at(dummyIndex);
1090
+ } else {
1091
+ // create and track new workspace
1092
+ config = new Config();
1093
+ ii.cs.push(config);
1094
+ }
1095
+ } else {
1096
+ // already tracking this config
1097
+ config = ii.cs.at(csIndex);
1098
+ }
1099
+ config!.id = item.id;
1100
+ config!.name = item.name;
1101
+ config!.description = item.description;
1102
+ config!.isEnabled = item.isEnabled;
1103
+ // create TenantConfigInfo array
1104
+ config!.tenants.length = 0;
1105
+ item.tenants.map((tci) => {
1106
+ let tenantConfigInfo = new TenantConfigInfo();
1107
+ tenantConfigInfo.tid = tci.tenantId;
1108
+ tenantConfigInfo.sourceGroupId = tci.sourceGroupId;
1109
+ tenantConfigInfo.sourceGroupName = tci.sourceGroupName;
1110
+ tenantConfigInfo.configurationTenantType = tci.configurationTenantType.toLowerCase();
1111
+ tenantConfigInfo.deltaToken = tci.deltaToken ?? "";
1112
+ config!.tenants.push(tenantConfigInfo);
1113
+ });
1114
+ // ensure this workspace tracks this config
1115
+ let idx = workspace.associatedConfigs.findIndex((c) => c === item.id);
1116
+ if (idx == -1) workspace.associatedConfigs.push(item.id);
1012
1117
  });
1013
- // ensure this workspace tracks this config
1014
- let idx = workspace.associatedConfigs.findIndex((c) => c === item.id);
1015
- if (idx == -1) workspace.associatedConfigs.push(item.id);
1016
- });
1017
1118
  }
1018
1119
  async function workspaceInfoGet(instance: IPublicClientApplication, authorizedUser: User, user: User, ii: InitInfo, debug: boolean): Promise<APIResult> {
1019
- let result: APIResult = new APIResult();
1020
- if (debug) debugger;
1021
- try {
1022
- result = await workspacesGet(instance, authorizedUser, user, debug);
1023
- if (result.result) {
1024
- for (let o of result.array!) {
1025
- // are we already tracking this workspace?
1026
- let workspace: Workspace = null;
1027
- let wsIndex = ii.ws.findIndex((w) => w.id === o.id);
1028
- if (wsIndex === -1) {
1029
- // start tracking
1030
- let dummyIndex = ii.ws.findIndex((w) => w.id === "1");
1031
- if(dummyIndex !== -1) {
1032
- // clear and overwrite dummy
1033
- workspace = ii.ws.at(dummyIndex);
1034
- }
1035
- else {
1036
- // create and track new workspace
1037
- workspace = new Workspace();
1038
- ii.ws.push(workspace);
1039
- }
1040
- } else {
1041
- // already tracking this workspace
1042
- workspace = ii.ws.at(wsIndex);
1120
+ let result: APIResult = new APIResult();
1121
+ if (debug) debugger;
1122
+ try {
1123
+ result = await workspacesGet(instance, authorizedUser, user, debug);
1124
+ if (result.result) {
1125
+ for (let o of result.array!) {
1126
+ // are we already tracking this workspace?
1127
+ let workspace: Workspace = null;
1128
+ let wsIndex = ii.ws.findIndex((w) => w.id === o.id);
1129
+ if (wsIndex === -1) {
1130
+ // start tracking
1131
+ let dummyIndex = ii.ws.findIndex((w) => w.id === "1");
1132
+ if (dummyIndex !== -1) {
1133
+ // clear and overwrite dummy
1134
+ workspace = ii.ws.at(dummyIndex);
1135
+ }
1136
+ else {
1137
+ // create and track new workspace
1138
+ workspace = new Workspace();
1139
+ ii.ws.push(workspace);
1140
+ }
1141
+ } else {
1142
+ // already tracking this workspace
1143
+ workspace = ii.ws.at(wsIndex);
1144
+ }
1145
+ // clear associations as we are about to reset
1146
+ workspace.associatedUsers.length = 0;
1147
+ workspace.associatedTenants.length = 0;
1148
+ workspace.associatedConfigs.length = 0;
1149
+ workspace.id = o.id;
1150
+ workspace.name = o.name;
1151
+ // parallel GET admins, tenants, configs associated with this workspace
1152
+ let adminsPromise: Promise<APIResult> = adminsGet(instance, authorizedUser, workspace.id, debug);
1153
+ let tenantsPromise: Promise<APIResult> = tenantsGet(instance, authorizedUser, workspace.id, debug);
1154
+ let configsPromise: Promise<APIResult> = configsGet(instance, authorizedUser, workspace.id, debug);
1155
+ // wait for all to finish, return on any failure
1156
+ let [adminsResult, tenantsResult, configsResult] = await Promise.all([adminsPromise, tenantsPromise, configsPromise]);
1157
+ if (!adminsResult.result) return adminsResult;
1158
+ if (!tenantsResult.result) return tenantsResult;
1159
+ if (!configsResult.result) return configsResult;
1160
+ // process returned workspace components
1161
+ processReturnedAdmins(workspace, ii, adminsResult.array!);
1162
+ processReturnedTenants(workspace, ii, tenantsResult.array!);
1163
+ processReturnedConfigs(workspace, ii, configsResult.array!);
1164
+ // tag components with workspaceIDs
1165
+ ii.tagWithWorkspaces();
1166
+ }
1167
+ return result;
1043
1168
  }
1044
- // clear associations as we are about to reset
1045
- workspace.associatedUsers.length = 0;
1046
- workspace.associatedTenants.length = 0;
1047
- workspace.associatedConfigs.length = 0;
1048
- workspace.id = o.id;
1049
- workspace.name = o.name;
1050
- // parallel GET admins, tenants, configs associated with this workspace
1051
- let adminsPromise: Promise<APIResult> = adminsGet(instance, authorizedUser, workspace.id, debug);
1052
- let tenantsPromise: Promise<APIResult> = tenantsGet(instance, authorizedUser, workspace.id, debug);
1053
- let configsPromise: Promise<APIResult> = configsGet(instance, authorizedUser, workspace.id, debug);
1054
- // wait for all to finish, return on any failure
1055
- let [adminsResult, tenantsResult, configsResult] = await Promise.all([adminsPromise, tenantsPromise, configsPromise]);
1056
- if(!adminsResult.result) return adminsResult;
1057
- if(!tenantsResult.result) return tenantsResult;
1058
- if(!configsResult.result) return configsResult;
1059
- // process returned workspace components
1060
- processReturnedAdmins(workspace, ii, adminsResult.array!);
1061
- processReturnedTenants(workspace, ii, tenantsResult.array!);
1062
- processReturnedConfigs(workspace, ii, configsResult.array!);
1063
- // tag components with workspaceIDs
1064
- ii.tagWithWorkspaces();
1065
- }
1066
- return result;
1067
1169
  }
1068
- }
1069
- catch (error: any) {
1070
- console.log(error.message);
1071
- result.error = error.message;
1072
- }
1073
- result.result = false;
1074
- result.status = 500;
1075
- return result;
1170
+ catch (error: any) {
1171
+ console.log(error.message);
1172
+ result.error = error.message;
1173
+ }
1174
+ result.result = false;
1175
+ result.status = 500;
1176
+ return result;
1076
1177
  }