diff --git a/package.json b/package.json index bfc1f46..668b7a4 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "dependencies": { "datatables.net": "^2.3.2", "datatables.net-bs5": "^2.3.2", - "dattatable": "^2.11.61", - "gd-sprest-bs": "^10.15.21", + "dattatable": "^2.11.62", + "gd-sprest-bs": "^10.15.30", "jquery": "^3.7.1", "moment": "^2.30.1" }, diff --git a/src/ds.ts b/src/ds.ts index 2a075b9..6b8d5f7 100644 --- a/src/ds.ts +++ b/src/ds.ts @@ -1,7 +1,7 @@ import { List, LoadingDialog } from "dattatable"; import { Components, ContextInfo, DirectorySession, GroupSiteManager, Helper, - Search, Site, SPTypes, Types, Web, v2 + Search, SensitivityLabels, Site, SPTypes, Types, Web, v2 } from "gd-sprest-bs"; import { Security } from "./security"; import Strings from "./strings"; @@ -45,6 +45,7 @@ export interface ISensitivityLabel { desc: string; id: string; name: string; + tooltip: string; } /** @@ -414,6 +415,32 @@ export class DataSource { }); } + // Loads the drive for a list name + static getDriveIdForList(webId: string, listName: string): PromiseLike { + // Return a promise + return new Promise((resolve, reject) => { + // Get the libraries for this site + v2.sites({ siteId: DataSource.Site.Id, webId }).drives().execute(resp => { + // Find the target drive + let drive = resp.results.find(a => { return a.name == listName; }); + + // Resolve the request + resolve(drive?.id); + }, reject); + }); + } + + // Returns the group id from the login name + static getGroupId(loginName: string): string { + // Get the group id from the login name + let userInfo = loginName.split('|'); + let groupInfo = userInfo[userInfo.length - 1]; + let groupId = groupInfo.split('_')[0]; + + // Ensure it's a guid and return null if it's not + return /^[{]?[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}[}]?$/.test(groupId) ? groupId : null; + } + // List private static _list: List = null; static get List(): List { return this._list; } @@ -481,24 +508,13 @@ export class DataSource { }); } - // Returns the group id from the login name - static getGroupId(loginName: string): string { - // Get the group id from the login name - let userInfo = loginName.split('|'); - let groupInfo = userInfo[userInfo.length - 1]; - let groupId = groupInfo.split('_')[0]; - - // Ensure it's a guid and return null if it's not - return /^[{]?[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}[}]?$/.test(groupId) ? groupId : null; - } - // Loads the files for a drive - static loadFiles(webId: string, listName?: string, folder?: Types.SP.Folder): PromiseLike { + static loadFiles(webId: string, listName?: string, folder?: Types.SP.Folder, onFile?: (file: Types.Microsoft.Graph.driveItem) => void): PromiseLike { let files = []; // Loads the files for a drive let getFiles = (driveId: string, folder: string = "") => { - let drive = v2.sites({ siteId: this.Site.Id, webId }).drives(driveId); + let drive = v2.sites({ siteId: this.Site.Id, webId, targetInfo: { disableProcessing: true } }).drives(driveId); // Return a promise return new Promise(resolve => { @@ -510,9 +526,12 @@ export class DataSource { Top: 5000 }).execute(resp => { // Parse the items - Helper.Executor(resp.results, driveItem => { + Helper.Executor(resp["d"].value, (driveItem: Types.Microsoft.Graph.driveItem) => { // See if this is a file if (driveItem.file) { + // Process the file + onFile ? onFile(driveItem) : null; + // Append the file files.push(driveItem); } else { @@ -554,6 +573,42 @@ export class DataSource { }); } + // Loads the items for a list + static loadItems(props: { + listId?: string; + listName?: string, + query: Types.IODataQuery, + onItem?: (items: Types.SP.ListItemOData) => void, + webUrl: string + }): PromiseLike { + // Return a promise + return new Promise((resolve, reject) => { + // Default the settings + props.query = props.query || {}; + props.query.GetAllItems = true; + props.query.Top = props.query.Top || 5000; + + // Get the list + let web = Web(props.webUrl, { + disableProcessing: true, + requestDigest: DataSource.SiteContext.FormDigestValue, + callbackQuery: props.onItem ? items => { + // Call the event + items.forEach(item => { props.onItem(item); }); + } : null + }); + + // Get the items + (props.listName ? web.Lists(props.listName) : web.Lists().getById(props.listId)).Items().query(props.query).execute( + items => { + // Resolve the request + resolve(items.results); + }, + reject + ) + }); + } + // Loads the sensitivity labels for the current user private static _sensitivityLabels: ISensitivityLabel[] = null; static get HasSensitivityLabels(): boolean { return this._sensitivityLabels?.length > 0; } @@ -570,45 +625,87 @@ export class DataSource { private static loadSensitivityLabels() { // Return a promise return new Promise(resolve => { - // Load the group context - GroupSiteManager().getGroupCreationContext().execute(resp => { - // Clear the labels - this._sensitivityLabels = []; - this._sensitivityLabelItems = [ - { - text: "", - value: null + // Clear the labels + this._sensitivityLabels = []; + this._sensitivityLabelItems = [ + { + text: "", + value: null + } + ]; + + // Get the sensitivity labels for the user + SensitivityLabels.getLabelsForUser().execute(labels => { + // Parse the labels + for (let i = 0; i < labels.results.length; i++) { + let label = labels.results[i]; + + // Parse the sub-labels + for (let j = 0; j < label.sublabels.length; j++) { + let subLabel = label.sublabels[j]; + let labelName = `${label.displayName} \\ ${subLabel.displayName}`; + + // Append the label and item + this._sensitivityLabels.push({ + desc: subLabel.description, + id: subLabel.id, + name: labelName, + tooltip: subLabel.tooltip + }); + this._sensitivityLabelItems.push({ + data: subLabel, + text: labelName, + value: subLabel.id + }); } - ]; - - // Parse the sensitivity labels - for (let i = 0; i < resp.DataClassificationOptionsNew.results.length; i++) { - let result = resp.DataClassificationOptionsNew.results[i]; - let desc = resp.ClassificationDescriptionsNew.results.filter(i => { return i.Key == result.Value; })[0]; - - // Append the label and item - this._sensitivityLabels.push({ - desc: desc ? desc.Value : "", - id: result.Key, - name: result.Value - }); - this._sensitivityLabelItems.push({ - text: result.Value, - value: result.Key - }); } - // Resolve the request resolve(null); - }, resolve); + }, () => { + // Load the group context + GroupSiteManager().getGroupCreationContext().execute(resp => { + // Parse the sensitivity labels + for (let i = 0; i < resp.DataClassificationOptionsNew.results.length; i++) { + let result = resp.DataClassificationOptionsNew.results[i]; + let desc = resp.ClassificationDescriptionsNew.results.filter(i => { return i.Key == result.Value; })[0]; + + // Append the label and item + this._sensitivityLabels.push({ + desc: desc ? desc.Value : "", + id: result.Key, + name: result.Value, + tooltip: "" + }); + this._sensitivityLabelItems.push({ + text: result.Value, + value: result.Key + }); + } + + // Resolve the request + resolve(null); + }, resolve); + }); + }); + } + + // Site Context + private static _siteContext: Types.SP.ContextWebInformation = null; + static get SiteContext(): Types.SP.ContextWebInformation { return this._siteContext; } + static refreshContext() { + // Attach to the refresh token event in the library + ContextInfo.enableRefreshToken(() => { + // Get the web context of the current web + ContextInfo.getWeb(this._siteContext.WebFullUrl).execute(context => { + // Set the site context + this._siteContext = context.GetContextWebInformation; + }); }); } // Loads the site collection information private static _site: Types.SP.SiteOData = null; static get Site(): Types.SP.SiteOData { return this._site; } - private static _siteContext: Types.SP.ContextWebInformation = null; - static get SiteContext(): Types.SP.ContextWebInformation { return this._siteContext; } static get SiteCustomScriptsEnabled(): boolean { return Helper.hasPermissions(DataSource.Site.RootWeb.EffectiveBasePermissions, SPTypes.BasePermissionTypes.AddAndCustomizePages); } private static _siteItems: Components.IDropdownItem[] = null; static get SiteItems(): Components.IDropdownItem[] { return this._siteItems; } diff --git a/src/reports/dlp.ts b/src/reports/dlp.ts index 10cbe82..3c95473 100644 --- a/src/reports/dlp.ts +++ b/src/reports/dlp.ts @@ -92,22 +92,21 @@ export class DLP { this._elSubNav.children[0].innerHTML = "Analyzing Library"; this._elSubNav.children[1].innerHTML = "Getting all files in this library..."; - // Get the item ids for this library - Web(webUrl, { requestDigest: DataSource.SiteContext.FormDigestValue }).Lists(libTitle).Items().query({ - Expand: ["Author"], - GetAllItems: true, - Select: ["Author/Title", "FileLeafRef", "FileRef", "File_x0020_Type", "Id"], - Top: 5000 - }).execute(items => { - let batchRequests = 0; - let completed = 0; - - // Update the dialog - this._elSubNav.children[1].innerHTML = "Creating batch job for files..."; + // Create the list for the batch requests + let batchRequests = 0; + let completed = 0; + let list = Web(webUrl, { requestDigest: DataSource.SiteContext.FormDigestValue }).Lists().getById(libId); - // Parse the items and create the batch job - let list = Web(webUrl, { requestDigest: DataSource.SiteContext.FormDigestValue }).Lists(libTitle); - items.results.forEach(item => { + // Get the item ids for this library + let itemCounter = 0; + DataSource.loadItems({ + webUrl, + listId: libId, + query: { + Expand: ["Author"], + Select: ["Author/Title", "FileLeafRef", "FileRef", "File_x0020_Type", "Id"], + }, + onItem: item => { // Create a batch request to get the dlp policy on this item list.Items(item.Id).GetDlpPolicyTip().batch(result => { // Ensure a policy exists @@ -138,8 +137,11 @@ export class DLP { // Increment the counter and update the dialog this._elSubNav.children[1].innerHTML = `Batch Requests Processed ${++completed} of ${batchRequests}...`; }, batchRequests++ % 25 == 0); - }); + // Update the dialog + this._elSubNav.children[1].innerHTML = `Creating Batch Requests - Processed ${++itemCounter} items...`; + } + }).then(() => { // Update the dialog this._elSubNav.children[1].innerHTML = `Executing Batch Request for ${batchRequests} items...`; @@ -168,17 +170,19 @@ export class DLP { let batchRequests = 0; let completed = 0; + // Set the list + let list = Web(webUrl, { requestDigest: DataSource.SiteContext.FormDigestValue }).Lists(lib.Title); + // Get the item ids for this library - Web(webUrl, { requestDigest: DataSource.SiteContext.FormDigestValue }).Lists(lib.Title).Items().query({ - Expand: ["Author"], - GetAllItems: true, - Select: ["Author/Title", "FileLeafRef", "FileRef", "File_x0020_Type", "Id"], - Top: 5000 - }).execute(items => { - let list = Web(webUrl, { requestDigest: DataSource.SiteContext.FormDigestValue }).Lists(lib.Title); - - // Parse the items and create the batch job - items.results.forEach(item => { + let itemCounter = 0; + DataSource.loadItems({ + webUrl, + listId: lib.Id, + query: { + Expand: ["Author"], + Select: ["Author/Title", "FileLeafRef", "FileRef", "File_x0020_Type", "Id"] + }, + onItem: item => { let analyzeFile = true; // See if the file extensions are provided @@ -226,14 +230,17 @@ export class DLP { this._elSubNav.children[1].innerHTML = `Batch Requests Processed ${++completed} of ${batchRequests}...`; }, batchRequests++ % 25 == 0); } - }); + // Update the dialog + this._elSubNav.children[1].innerHTML = `Creating Batch Requests - Processed ${++itemCounter} items...`; + } + }).then(() => { // Update the dialog this._elSubNav.children[1].innerHTML = `Executing Batch Request for ${batchRequests} items...`; // Execute the batch request list.execute(resolve); - }); + }, resolve); }); }).then(resolve); }); diff --git a/src/reports/searchEEEU.ts b/src/reports/searchEEEU.ts index 9307a92..899eb56 100644 --- a/src/reports/searchEEEU.ts +++ b/src/reports/searchEEEU.ts @@ -45,28 +45,32 @@ export class SearchEEEU { private static analyzeList(web: Types.SP.WebOData, list: Types.SP.ListOData): PromiseLike { // Return a promise return new Promise(resolve => { + // Set the fields to query let Select = ["Id", "HasUniqueRoleAssignments"]; - - // See if this is a document library if (list.BaseTemplate == SPTypes.ListTemplateType.DocumentLibrary || list.BaseTemplate == SPTypes.ListTemplateType.PageLibrary) { // Get the file information Select.push("FileLeafRef"); Select.push("FileRef"); } - // Get the items where it has broken inheritance - Web(web.Url, { requestDigest: DataSource.SiteContext.FormDigestValue }).Lists().getById(list.Id).Items().query({ - GetAllItems: true, - Select, - Top: 5000 - }).execute(items => { - let ctrBatchJobs = 0; - - // Create a batch job - let batch = Web(web.Url, { requestDigest: DataSource.SiteContext.FormDigestValue }).Lists().getById(list.Id); + // Create a batch job + let completed = 0; + let ctrBatchJobs = 0; + let batch = Web(web.Url, { requestDigest: DataSource.SiteContext.FormDigestValue }).Lists().getById(list.Id); + + // Update the dialog + this._elSubNav.children[1].innerHTML = `Loading the items...`; + + // Get the items for the list + let itemCounter = 0; + DataSource.loadItems({ + webUrl: web.Url, + listId: list.Id, + query: { Select }, + onItem: item => { + // Update the dialog + this._elSubNav.children[1].innerHTML = `Creating Batch Requests - Processed ${++itemCounter} items...`; - // Parse the items - Helper.Executor(items.results, item => { // See if this item doesn't have unique permissions if (!item.HasUniqueRoleAssignments) { return; } @@ -104,14 +108,20 @@ export class SearchEEEU { }; this._items.push(roleItem); this._dashboard.Datatable.addRow(roleItem); + + // Increment the counter and update the dialog + this._elSubNav.children[1].innerHTML = `Batch Requests Processed ${++completed} of ${ctrBatchJobs % 25}...`; }); }, ctrBatchJobs++ % 25 == 0); - }).then(() => { - // Execute the batch job - batch.execute(() => { - // Resolve the request - resolve(); - }); + } + }).then(() => { + // Update the dialog + this._elSubNav.children[1].innerHTML = `Executing Batch Request for ${ctrBatchJobs} items...`; + + // Execute the batch jobs + batch.execute(() => { + // Resolve the request + resolve(); }); }); }); @@ -135,11 +145,13 @@ export class SearchEEEU { Select: ["Id", "Title", "BaseTemplate", "HasUniqueRoleAssignments", "RootFolder/ServerRelativeUrl"] }).execute(lists => { let ctrList = 0; + let siteText = this._elSubNav.children[0].innerHTML; + // Parse the lists Helper.Executor(lists.results, list => { // Show a dialog - this._elSubNav.children[1].innerHTML = `Analyzing List ${++ctrList} of ${lists.results.length}...`; + this._elSubNav.children[0].innerHTML = `${siteText} - [Analyzing List ${++ctrList} of ${lists.results.length}]: ${list.Title}`; // Analyze the list return this.analyzeList(web, list); diff --git a/src/reports/sensitivityLabels.ts b/src/reports/sensitivityLabels.ts index d2dbf9a..8eb0557 100644 --- a/src/reports/sensitivityLabels.ts +++ b/src/reports/sensitivityLabels.ts @@ -88,46 +88,61 @@ export class SensitivityLabels { // Parse the libraries Helper.Executor(libraries, lib => { + let fileItems: ISensitivityLabelItem[] = []; + // Update the dialog this._elSubNav.children[0].innerHTML = `${siteText} [Analyzing Library ${++counter} of ${libraries.length}]: ${lib.Title}`; // Return a promise return new Promise(resolve => { // Update the dialog - this._elSubNav.children[1].innerHTML = `Loading the files for this library...`; + this._elSubNav.children[1].innerHTML = `Analyzing the files for this library...`; // Get the files for this library - DataSource.loadFiles(webId, lib.Title).then(files => { + let filesProcessed = 0; + DataSource.loadFiles(webId, lib.Title, null, (file: Types.Microsoft.Graph.driveItem) => { + let hasLabel = file.sensitivityLabel && file.sensitivityLabel.displayName ? true : false; + // Update the dialog - this._elSubNav.children[1].innerHTML = `Analyzing the files for this library...`; - - // Parse the files - files.forEach(file => { - let hasLabel = file.sensitivityLabel && file.sensitivityLabel.displayName ? true : false; - - // Add the file, based on the flags - if ((withLabelsFl && hasLabel) || (withoutLabelsFl && !hasLabel)) { - let fileInfo = file.name.split('.'); - let folderPath = file.parentReference.path.split('root:')[1]; - - // Append the data - let fileItem = { - Author: file.createdBy.user["email"], - File: file, - FileExtension: fileInfo[fileInfo.length - 1], - FileName: file.name, - ListId: lib.Id, - ListTitle: lib.Title, - Path: `${lib.RootFolder.ServerRelativeUrl}${folderPath}/${file.name}`, - SensitivityLabel: file.sensitivityLabel.displayName, - SensitivityLabelId: file.sensitivityLabel.id, - WebId: webId, - WebUrl: webUrl - }; - this._items.push(fileItem); - this._dashboard.Datatable.addRow(fileItem); + this._elSubNav.children[1].innerHTML = `Analyzing the files for this library. Files Analyzed: ${++filesProcessed}`; + + // Add the file, based on the flags + if ((withLabelsFl && hasLabel) || (withoutLabelsFl && !hasLabel)) { + let fileInfo = file.name.split('.'); + let folderPath = file.parentReference.path.split('root:')[1]; + + // Append the data + let fileItem = { + Author: file.createdBy.user["email"] || file.createdBy.user["displayName"], + File: file, + FileExtension: fileInfo[fileInfo.length - 1], + FileName: file.name, + ListId: lib.Id, + ListTitle: lib.Title, + Path: `${lib.RootFolder.ServerRelativeUrl}${folderPath}/${file.name}`, + SensitivityLabel: file.sensitivityLabel.displayName, + SensitivityLabelId: file.sensitivityLabel.id, + WebId: webId, + WebUrl: webUrl + }; + + // Save a reference to the item + this._items.push(fileItem); + fileItems.push(fileItem); + + // See if we have hit 100 items + if (fileItems.length >= 100) { + // Add the items to the datatable + this._dashboard.Datatable.addRow(fileItems); + fileItems = []; } - }); + } + }).then(() => { + // See if items exist + if (fileItems.length > 0) { + // Add the items to the datatable + this._dashboard.Datatable.addRow(fileItems); + } // Check the next library resolve(null); @@ -142,12 +157,14 @@ export class SensitivityLabels { // See if this file has a sensitivity label if (file.sensitivityLabel?.id && !overrideLabelFl) { // Add a response - responses.push({ + let response: ISetSensitivityLabelResponse = { errorFl: false, fileName: file.name, message: `Skipping file, it's already labelled: '${file.sensitivityLabel.displayName}'.`, url: file.webUrl - }); + }; + responses.push(response); + this._dashboard.Datatable.addRow(response); return; } @@ -171,12 +188,14 @@ export class SensitivityLabels { // Success () => { // Add the response - responses.push({ + let response: ISetSensitivityLabelResponse = { errorFl: false, fileName: file.name, message: `The file was successfully labelled: ${label}.`, url: file.webUrl - }); + }; + responses.push(response); + this._dashboard.Datatable.addRow(response); // Resolve the request resolve(responses); @@ -190,13 +209,15 @@ export class SensitivityLabels { catch { error = err; } // Add the response - responses.push({ + let response: ISetSensitivityLabelResponse = { errorFl: true, error: err, fileName: file.name, message: `There was an error tagging this file: ${error}`, url: file.webUrl - }); + }; + responses.push(response); + this._dashboard.Datatable.addRow(response); // Resolve the request resolve(responses); @@ -209,30 +230,98 @@ export class SensitivityLabels { private static labelFilesInFolder(webId: string, listName: string, folder: Types.SP.Folder, label: Components.IDropdownItem, overrideLabelFl: boolean, justification: string) { let responses: ISetSensitivityLabelResponse[] = []; - // Show a loading dialog - LoadingDialog.setHeader("Loading Items"); - LoadingDialog.setBody("Loading the files for this library..."); - LoadingDialog.show(); + // Show the responses + this.showResponses(responses); - // Load the files for this drive - DataSource.loadFiles(webId, listName, folder).then(files => { - // Update the loading dialog - LoadingDialog.setBody("Applying the sensitivity labels to the files..."); + // Update the dialog + this._elSubNav.children[0].innerHTML = `Loading files from library: ${listName}`; - // Parse the files - let counter = 0; - Helper.Executor(files, file => { - // Update the loading dialog - LoadingDialog.setBody(`Processing ${++counter} of ${files.length} files...`); + // Process the labels as we load the files + let fileCounter = 0; + let filesToProcess: Types.Microsoft.Graph.driveItem[] = []; + let isRunning = false; + let processedCounter = 0; + let startProcess = (callback?: () => void): PromiseLike => { + let onCompleted = callback; + + // Do nothing if we are already running + if (isRunning) { return; } + + // Set the flags + let isProcessing = false; + isRunning = true; + + // Loop while there are files to process + let loopIdx = setInterval(() => { + // Do nothing if we are processing an item + if (isProcessing) { return; } + + // Get the file to process + let file = filesToProcess.splice(0, 1)[0]; + + // See if this file has a sensitivity label + if (file.sensitivityLabel?.id && !overrideLabelFl) { + // Add a response + let response: ISetSensitivityLabelResponse = { + errorFl: false, + fileName: file.name, + message: `Skipping file, it's already labelled: '${file.sensitivityLabel.displayName}'.`, + url: file.webUrl + }; + responses.push(response); + this._dashboard.Datatable.addRow(response); + + // Update the dialog + this._elSubNav.children[1].innerHTML = `[Processed ${++processedCounter} of ${fileCounter}] ${response.message}`; + + // Check the next file + return; + } + + // Set the flag + isProcessing = true; + + // Update the dialog + this._elSubNav.children[1].innerHTML = `[Processed ${++processedCounter} of ${fileCounter}] Labelling File: ${file.name}`; // Label the file - return this.labelFile(file, overrideLabelFl, label.text, label.value, justification, responses); - }).then(() => { - // Show the responses - this.showResponses(responses); + this.labelFile(file, overrideLabelFl, label.text, label.value, justification, responses).then(() => { + // Set the flag + isProcessing = false; + + // Update the dialog + this._elSubNav.children[1].innerHTML = `[Processed ${++processedCounter} of ${fileCounter}] File Labelled: ${file.name}`; + + // See if we are done + if (filesToProcess.length == 0) { + // Stop the loop + clearInterval(loopIdx); + + // Set the flag + isRunning = false; + + // Call the event + onCompleted ? onCompleted() : null; + } + }); + }, 10); + } + + // Load the files for this drive + DataSource.loadFiles(webId, listName, folder, file => { + // Add the file to process + filesToProcess.push(file); - // Hide the dialog - LoadingDialog.hide(); + // Update the dialog + this._elSubNav.children[0].innerHTML = `Loading files from library: ${listName} [Files Loaded: ${++fileCounter}]`; + + // Ensure the process is running + startProcess(); + }).then(() => { + // Ensure the process is running + startProcess(() => { + // Clear the sub-nav + this._elSubNav.classList.add("d-none"); }); }); } @@ -711,36 +800,48 @@ export class SensitivityLabels { Modal.setType(Components.ModalTypes.Full); Modal.setHeader("Set Sensitivity Label Results"); - // Set the body - new DataTable({ + // Set the dashboard + this._dashboard = new Dashboard({ el: Modal.BodyElement, - rows: responses, - columns: [ - { - name: "errorFl", - title: "Error?" - }, - { - name: "", - title: "File Name", - onRenderCell: (el, col, item: ISetSensitivityLabelResponse) => { - // Render a link to the file - Components.Button({ - el, - text: item.fileName, - href: item.url, - target: "_blank", - type: Components.ButtonTypes.OutlineLink - }); + navigation: { + title: "Senstivity Labels", + showFilter: false + }, + table: { + rows: responses, + columns: [ + { + name: "errorFl", + title: "Error?" + }, + { + name: "", + title: "File Name", + onRenderCell: (el, col, item: ISetSensitivityLabelResponse) => { + // Render a link to the file + Components.Button({ + el, + text: item.fileName, + href: item.url, + target: "_blank", + type: Components.ButtonTypes.OutlineLink + }); + } + }, + { + name: "message", + title: "Status Message" } - }, - { - name: "message", - title: "Status Message" - } - ] + ] + } }); + // Set the sub-nav element + this._elSubNav = Modal.BodyElement.querySelector("#sub-navigation"); + this._elSubNav.classList.remove("d-none"); + this._elSubNav.classList.add("my-2"); + this._elSubNav.innerHTML = `
`; + // Set the footer Components.TooltipGroup({ el: Modal.FooterElement, diff --git a/src/reports/uniquePermissions.ts b/src/reports/uniquePermissions.ts index 6b7f5fe..a563d27 100644 --- a/src/reports/uniquePermissions.ts +++ b/src/reports/uniquePermissions.ts @@ -46,24 +46,23 @@ export class UniquePermissions { } // Update the dialog - this._elSubNav.children[1].innerHTML = "Getting the list items..."; + this._elSubNav.children[1].innerHTML = "Loading the list items..."; - // Get the items where it has broken inheritance - Web(webUrl, { requestDigest: DataSource.SiteContext.FormDigestValue }).Lists().getById(list.Id).Items().query({ - GetAllItems: true, - Select, - Top: 5000 - }).execute(items => { - let ctrBatchJobs = 0; - - // Create a batch job - let batch = Web(webUrl, { requestDigest: DataSource.SiteContext.FormDigestValue }).Lists().getById(list.Id); + // Create a batch job + let completed = 0; + let ctrBatchJobs = 0; + let batch = Web(webUrl, { requestDigest: DataSource.SiteContext.FormDigestValue }).Lists().getById(list.Id); - // Update the dialog - this._elSubNav.children[1].innerHTML = "Creating the batch job..."; + // Get the items where it has broken inheritance + let itemCounter = 0; + DataSource.loadItems({ + webUrl, + listId: list.Id, + query: { Select }, + onItem: item => { + // Update the dialog + this._elSubNav.children[1].innerHTML = `Creating Batch Requests - Processed ${++itemCounter} items...`; - // Parse the items - Helper.Executor(items.results, item => { // See if this item doesn't have unique permissions if (!item.HasUniqueRoleAssignments) { return; } @@ -136,14 +135,20 @@ export class UniquePermissions { this._items.push(listItem); this._dashboard.Datatable.addRow(listItem); } + + // Increment the counter and update the dialog + this._elSubNav.children[1].innerHTML = `Batch Requests Processed ${++completed} of ${ctrBatchJobs % 25}...`; }); }, ctrBatchJobs++ % 25 == 0); - }).then(() => { - // Execute the batch job - batch.execute(() => { - // Resolve the request - resolve(); - }); + } + }).then(() => { + // Update the dialog + this._elSubNav.children[1].innerHTML = `Executing Batch Request for ${ctrBatchJobs} items...`; + + // Execute the batch job + batch.execute(() => { + // Resolve the request + resolve(); }); }); }); @@ -354,8 +359,7 @@ export class UniquePermissions { // Parse the lists Helper.Executor(lists.results, list => { // Update the dialog - this._elSubNav.children[0].innerHTML = `${siteText} [Analyzing Library ${++ctrList} of ${lists.results.length}]: ${list.Title}`; - this._elSubNav.children[1].innerHTML = "Analyzing the list..."; + this._elSubNav.children[0].innerHTML = `${siteText} - [Analyzing Library ${++ctrList} of ${lists.results.length}]: ${list.Title}`; // Analyze the list return this.analyzeList(siteItem.text, list);