diff --git a/src/extensions/enhancedPowerAutomateTrigger/components/FlowButton.tsx b/src/extensions/enhancedPowerAutomateTrigger/components/FlowButton.tsx
index e5eb7be..b5bda99 100644
--- a/src/extensions/enhancedPowerAutomateTrigger/components/FlowButton.tsx
+++ b/src/extensions/enhancedPowerAutomateTrigger/components/FlowButton.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable @microsoft/spfx/no-async-await */
import { PrimaryButton } from "@fluentui/react";
import * as React from "react";
import { IFlowConfig } from "../../../models";
diff --git a/src/extensions/enhancedPowerAutomateTrigger/components/index.ts b/src/extensions/enhancedPowerAutomateTrigger/components/index.ts
index a8dce3d..3e67dfa 100644
--- a/src/extensions/enhancedPowerAutomateTrigger/components/index.ts
+++ b/src/extensions/enhancedPowerAutomateTrigger/components/index.ts
@@ -1,3 +1,2 @@
export { EnhancedPowerAutomateTriggerDialog, IEnhancedPowerAutomateTriggerDialogProps } from './EnhancedPowerAutomateTriggerDialog';
export { FlowButton, IFlowButtonProps } from './FlowButton';
-export { useToggle } from './UseToggle';
diff --git a/src/models/IFlowConfig.ts b/src/models/IFlowConfig.ts
index dfc7c49..97de608 100644
--- a/src/models/IFlowConfig.ts
+++ b/src/models/IFlowConfig.ts
@@ -1,4 +1,4 @@
-import { stringIsNullOrEmpty } from "@pnp/pnpjs";
+import { stringIsNullOrEmpty } from "../util";
export interface IFlowConfig {
actionName: string;
@@ -14,7 +14,7 @@ export const isFlowConfigValid = (flowConfig: IFlowConfig): boolean => {
} else {
return false;
}
- } catch (ex) {
+ } catch (err) {
return false;
}
};
diff --git a/src/services/FlowService.ts b/src/services/FlowService.ts
index 7f179aa..a04f490 100644
--- a/src/services/FlowService.ts
+++ b/src/services/FlowService.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @microsoft/spfx/no-async-await */
import { ServiceKey } from "@microsoft/sp-core-library";
import { HttpClient, HttpClientResponse, IHttpClientOptions } from '@microsoft/sp-http';
import { ListViewCommandSetContext, RowAccessor } from "@microsoft/sp-listview-extensibility";
@@ -8,36 +9,35 @@ export interface IFlowService {
}
export class FlowService implements IFlowService {
- constructor() { }
public invokeFlow = async (context: ListViewCommandSetContext, flowConfig: IFlowConfig, selectedItems: readonly RowAccessor[]): Promise
=> {
try {
if (!isFlowConfigValid(flowConfig)) {
- throw "Flow configuration is invalid.";
+ throw new Error("Flow configuration is invalid.");
}
switch (flowConfig.method) {
case 'GET':
- let httpClientGetOptions: IHttpClientOptions = this._createHttpClientGetOptions();
+ const httpClientGetOptions: IHttpClientOptions = this._createHttpClientGetOptions();
if (!httpClientGetOptions) {
- throw "HTTP client options are invalid.";
+ throw new Error("HTTP client options are invalid.");
}
return await context.httpClient.get(flowConfig.url, HttpClient.configurations.v1, httpClientGetOptions)
- .then(async (response: HttpClientResponse) => {
+ .then(async (response: HttpClientResponse): Promise => {
return {
statusCode: response?.status,
message: await this._tryGetMessageFromResponseBody(response)
};
});
case 'POST':
- let httpClientPostOptions: IHttpClientOptions = this._createHttpClientPostOptions(context, selectedItems);
+ const httpClientPostOptions: IHttpClientOptions = this._createHttpClientPostOptions(context, selectedItems);
if (!httpClientPostOptions) {
- throw "HTTP client options are invalid.";
+ throw new Error("HTTP client options are invalid.");
}
return await context.httpClient.post(flowConfig.url, HttpClient.configurations.v1, httpClientPostOptions)
- .then(async (response: HttpClientResponse) => {
+ .then(async (response: HttpClientResponse): Promise => {
return {
statusCode: response?.status,
message: await this._tryGetMessageFromResponseBody(response)
@@ -46,27 +46,25 @@ export class FlowService implements IFlowService {
default:
return null;
}
- } catch (ex) {
+ } catch (err) {
return null;
}
}
private _tryGetMessageFromResponseBody = async (response: HttpClientResponse): Promise => {
try {
- return await response?.json()?.then((result: any): Promise => {
- return Promise.resolve(result?.message);
- });
- } catch (ex) {
+ return await response?.json()?.then((result): Promise => Promise.resolve(result?.message));
+ } catch (err) {
return null;
}
}
private _createHttpClientPostOptions = (context: ListViewCommandSetContext, selectedItems: readonly RowAccessor[]): IHttpClientOptions => {
try {
- let processedSelectedItems: ISelectedItem[] = [];
+ const processedSelectedItems: ISelectedItem[] = [];
selectedItems.forEach((selectedItem: RowAccessor): void => {
- let processedSelectedItem: ISelectedItem = {
+ const processedSelectedItem: ISelectedItem = {
id: parseInt(selectedItem?.getValueByName("ID")),
fileRef: selectedItem?.getValueByName("FileRef"),
fileLeafRef: selectedItem?.getValueByName("FileLeafRef"),
@@ -76,11 +74,11 @@ export class FlowService implements IFlowService {
processedSelectedItems.push(processedSelectedItem);
});
- let requestHeaders: Headers = new Headers();
+ const requestHeaders: Headers = new Headers();
requestHeaders.append('Content-type', 'application/json');
requestHeaders.append('Cache-Control', 'no-cache');
- let requestBody: IFlowRequestBody = {
+ const requestBody: IFlowRequestBody = {
site: context.pageContext.site.absoluteUrl,
tenantUrl: context.pageContext.legacyPageContext?.portalUrl,
listId: context.pageContext.list?.id.toString(),
@@ -88,36 +86,36 @@ export class FlowService implements IFlowService {
selectedItems: processedSelectedItems
};
- let httpClientOptions: IHttpClientOptions = {
+ const httpClientOptions: IHttpClientOptions = {
body: JSON.stringify(requestBody),
headers: requestHeaders
};
return httpClientOptions;
- } catch (ex) {
+ } catch (err) {
return null;
}
}
private _createHttpClientGetOptions = (): IHttpClientOptions => {
try {
- let requestHeaders: Headers = new Headers();
+ const requestHeaders: Headers = new Headers();
requestHeaders.append('Content-type', 'application/json');
requestHeaders.append('Cache-Control', 'no-cache');
- let httpClientOptions: IHttpClientOptions = {
+ const httpClientOptions: IHttpClientOptions = {
body: null,
headers: requestHeaders
};
return httpClientOptions;
- } catch (ex) {
+ } catch (err) {
return null;
}
}
}
-export const FlowServiceKey = ServiceKey.create(
+export const FlowServiceKey: ServiceKey = ServiceKey.create(
"FlowService:FlowService",
FlowService
);
diff --git a/src/services/PnPService.ts b/src/services/PnPService.ts
new file mode 100644
index 0000000..0374838
--- /dev/null
+++ b/src/services/PnPService.ts
@@ -0,0 +1,20 @@
+import { ListViewCommandSetContext } from "@microsoft/sp-listview-extensibility";
+
+// import pnp, pnp logging system, and any other selective imports needed
+import { LogLevel, PnPLogging } from "@pnp/logging";
+import { SPFI, spfi, SPFx } from "@pnp/sp";
+import "@pnp/sp/batching";
+import "@pnp/sp/items";
+import "@pnp/sp/lists";
+import "@pnp/sp/webs";
+
+let _sp: SPFI = null;
+
+export const getSP = (context?: ListViewCommandSetContext): SPFI => {
+ if (_sp === null && context !== null) {
+ // You must add the @pnp/logging package to include the PnPLogging behavior it is no longer a peer dependency
+ // The LogLevel set's at what level a message will be written to the console
+ _sp = spfi().using(SPFx(context)).using(PnPLogging(LogLevel.Warning));
+ }
+ return _sp;
+}
diff --git a/src/services/SPOService.ts b/src/services/SPOService.ts
index b40cb52..3013cf1 100644
--- a/src/services/SPOService.ts
+++ b/src/services/SPOService.ts
@@ -1,35 +1,41 @@
+/* eslint-disable @microsoft/spfx/no-async-await */
import { ServiceKey } from "@microsoft/sp-core-library";
-import { Web } from "@pnp/sp";
+import { SPFI } from "@pnp/sp";
+import "@pnp/sp/items/get-all";
import { IFlowConfig, isFlowConfigValid } from "../models";
+import { getSP } from "./PnPService";
export interface ISPOService {
- getFlowConfig(siteUrl: string, listTitle: string): Promise;
+ getFlowConfig(listTitle: string): Promise;
}
export class SPOService implements ISPOService {
- constructor() { }
+ private readonly _sp: SPFI;
+
+ public constructor() {
+ this._sp = getSP();
+ }
public getFlowConfig = async (
- siteUrl: string,
listTitle: string
): Promise => {
try {
- return await new Web(siteUrl).lists
+ return await this._sp.web.lists
.getByTitle(listTitle)
.items.getAll()
- .then((response: any[]): Promise => {
+ .then((response): Promise => {
return new Promise((resolve, reject): void => {
- let flowConfigs: IFlowConfig[] = [];
+ const flowConfigs: IFlowConfig[] = [];
- response.forEach((triggerConfigListItem: any): void => {
- let flowConfig: IFlowConfig = {
+ response.forEach((triggerConfigListItem): void => {
+ const flowConfig: IFlowConfig = {
actionName: triggerConfigListItem?.Title,
url: triggerConfigListItem?.TriggerURL,
method: triggerConfigListItem?.HTTPType
};
if (!isFlowConfigValid(flowConfig)) {
- throw `Flow configuration for '${flowConfig.actionName}' is invalid.`;
+ throw new Error(`Flow configuration for '${flowConfig.actionName}' is invalid.`);
}
flowConfigs.push(flowConfig);
@@ -37,13 +43,13 @@ export class SPOService implements ISPOService {
resolve(flowConfigs);
});
});
- } catch (ex) {
+ } catch (err) {
return null;
}
}
}
-export const SPOServiceKey = ServiceKey.create(
+export const SPOServiceKey: ServiceKey = ServiceKey.create(
"SPOService:SPOService",
SPOService
);
diff --git a/src/services/index.ts b/src/services/index.ts
index abe77a7..a945fff 100644
--- a/src/services/index.ts
+++ b/src/services/index.ts
@@ -1,2 +1,3 @@
export { FlowService, FlowServiceKey, IFlowService } from './FlowService';
+export { getSP } from './PnPService';
export { ISPOService, SPOService, SPOServiceKey } from './SPOService';
diff --git a/src/util/CheckIfStringStartsWith.ts b/src/util/CheckIfStringStartsWith.ts
new file mode 100644
index 0000000..931a440
--- /dev/null
+++ b/src/util/CheckIfStringStartsWith.ts
@@ -0,0 +1,3 @@
+export function checkIfStringStartsWith(str: string, substrs: string[]): boolean {
+ return substrs.some((substr: string): boolean => str.startsWith(substr));
+}
diff --git a/src/util/GetIndexOfNthCharacterInString.ts b/src/util/GetIndexOfNthCharacterInString.ts
new file mode 100644
index 0000000..183d55b
--- /dev/null
+++ b/src/util/GetIndexOfNthCharacterInString.ts
@@ -0,0 +1,17 @@
+export function getIndexOfNthCharacterInString(
+ queryText: string,
+ searchCharacter: string,
+ nthNumber: number
+): number {
+ try {
+ let offset: number = queryText.indexOf(searchCharacter);
+
+ for (let i: number = 1; i < nthNumber; i++) {
+ offset = queryText.indexOf(searchCharacter, offset + 1);
+ }
+
+ return offset;
+ } catch {
+ return 0;
+ }
+}
diff --git a/src/util/GetUrlParameterByName.ts b/src/util/GetUrlParameterByName.ts
new file mode 100644
index 0000000..a710098
--- /dev/null
+++ b/src/util/GetUrlParameterByName.ts
@@ -0,0 +1,11 @@
+export function getUrlParameterByName(name: string): string {
+ const url: string = window.location.href;
+ name = name.replace(/[\[\]]/g, "\\$&");
+ const regexString: string = "[?&]" + name + "(=([^]*)|&|#|$)";
+ // eslint-disable-next-line @rushstack/security/no-unsafe-regexp
+ const regex: RegExp = new RegExp(regexString),
+ results: RegExpExecArray = regex.exec(url);
+ if (!results) return null;
+ if (!results[2]) return "";
+ return decodeURIComponent(results[2].replace(/\+/g, " "));
+}
diff --git a/src/util/StringIsNullOrEmpty.ts b/src/util/StringIsNullOrEmpty.ts
new file mode 100644
index 0000000..ac2a935
--- /dev/null
+++ b/src/util/StringIsNullOrEmpty.ts
@@ -0,0 +1,11 @@
+export function stringIsNullOrEmpty(str: string): boolean {
+ try {
+ if (typeof str === 'string' && str.length > 0) {
+ return false;
+ }
+
+ return true;
+ } catch (err) {
+ return true;
+ }
+}
diff --git a/src/extensions/enhancedPowerAutomateTrigger/components/UseToggle.ts b/src/util/UseToggle.ts
similarity index 52%
rename from src/extensions/enhancedPowerAutomateTrigger/components/UseToggle.ts
rename to src/util/UseToggle.ts
index 3c3a085..47d33a2 100644
--- a/src/extensions/enhancedPowerAutomateTrigger/components/UseToggle.ts
+++ b/src/util/UseToggle.ts
@@ -1,10 +1,10 @@
import * as React from 'react';
-export const useToggle = (defaultValue: boolean) => {
+export const useToggle = (defaultValue: boolean): [boolean, () => void] => {
const [value, setValue] = React.useState(defaultValue);
- const toggleValue = () => {
- setValue((oldState) => !oldState);
+ const toggleValue = (): void => {
+ setValue((oldState: boolean): boolean => !oldState);
};
const result: [boolean, () => void] = [value, toggleValue];
diff --git a/src/util/index.ts b/src/util/index.ts
new file mode 100644
index 0000000..fe19762
--- /dev/null
+++ b/src/util/index.ts
@@ -0,0 +1,5 @@
+export { checkIfStringStartsWith } from "./CheckIfStringStartsWith";
+export { getIndexOfNthCharacterInString } from "./GetIndexOfNthCharacterInString";
+export { getUrlParameterByName } from "./GetUrlParameterByName";
+export { stringIsNullOrEmpty } from "./StringIsNullOrEmpty";
+export { useToggle } from "./UseToggle";
diff --git a/tsconfig.json b/tsconfig.json
index f057162..183d0c6 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,8 +1,7 @@
{
- "extends": "@microsoft/rush-stack-compiler-3.9/includes/tsconfig-web.json",
+ "extends": "./node_modules/@microsoft/rush-stack-compiler-4.5/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es6",
- "noImplicitAny": false,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
diff --git a/tslint.json b/tslint.json
deleted file mode 100644
index fe53c07..0000000
--- a/tslint.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "extends": "./node_modules/@microsoft/sp-tslint-rules/base-tslint.json",
- "rules": {
- "class-name": false,
- "export-name": false,
- "forin": false,
- "label-position": false,
- "member-access": true,
- "no-arg": false,
- "no-console": false,
- "no-construct": false,
- "no-duplicate-variable": true,
- "no-eval": false,
- "no-function-expression": true,
- "no-internal-module": true,
- "no-shadowed-variable": true,
- "no-switch-case-fall-through": true,
- "no-unnecessary-semicolons": true,
- "no-unused-expression": true,
- "no-with-statement": true,
- "semicolon": true,
- "trailing-comma": false,
- "typedef": false,
- "typedef-whitespace": false,
- "use-named-parameter": true,
- "variable-name": false,
- "whitespace": false
- }
-}