Merge pull request #2085 from a1mery/app-secret-expiration-pagination

[react-graph-secret-expiration] - Add pagination
This commit is contained in:
Hugo Bernier 2021-10-31 14:59:05 -04:00 committed by GitHub
commit d16f505759
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 105 additions and 42 deletions

View File

@ -10,7 +10,7 @@ I got the idea from this great article [Use Power Automate to Notify of Upcoming
## Compatibility
![SPFx 1.12.1](https://img.shields.io/badge/SPFx-1.12.1-green.svg)
![Node.js LTS v14 | LTS v12 | LTS v10](https://img.shields.io/badge/Node.js-LTS%20v14%20%7C%20LTS%20v12%20%7C%20LTS%20v10-green.svg)
![Node.js LTS v12 | LTS v10](https://img.shields.io/badge/Node.js-LTS%20v12%20%7C%20LTS%20v10-green.svg)
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
@ -32,6 +32,7 @@ react-graph-app-secret-expiration | [Aimery Thomas](https://github.com/a1mery) (
Version|Date|Comments
-------|----|--------
1.0|September 17, 2021|Initial release
1.1|October 10, 2021|Add pagination
## Minimal Path to Awesome
@ -39,14 +40,14 @@ Version|Date|Comments
- Clone this repository
- Ensure that you are at the solution folder
- In the command-line run:
- **npm install**
- **gulp bundle**
- **gulp package-solution**
- `npm install`
- `gulp bundle`
- `gulp package-solution`
- Deploy the package to your app catalog
- Approve the API permission request from the SharePoint admin
- Add the web part to a page
- In the command-line run:
- **gulp serve --nobrowser**
- `gulp serve --nobrowser`
## Features

View File

@ -9,7 +9,7 @@
"This sample web part shows the list of your applications registered in Azure AD along with their associated client secret/certificate expiration date."
],
"creationDateTime": "2021-09-17",
"updateDateTime": "2021-09-17",
"updateDateTime": "2021-10-10",
"products": [
"SharePoint",
"Office"
@ -22,6 +22,10 @@
{
"key": "SPFX-VERSION",
"value": "1.12.1"
},
{
"key": "PNPCONTROLS",
"value": "Pagination"
}
],
"thumbnails": [

View File

@ -3,7 +3,7 @@
"solution": {
"name": "react-graph-app-secret-expiration-client-side-solution",
"id": "b25d85a4-7310-408a-a263-25959b0a5b1b",
"version": "1.0.0.0",
"version": "1.1.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false,
"skipFeatureDeployment": true,

View File

@ -1,6 +1,6 @@
{
"name": "react-graph-app-secret-expiration",
"version": "0.0.1",
"version": "1.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "react-graph-app-secret-expiration",
"version": "0.0.1",
"version": "1.1.0",
"private": true,
"main": "lib/index.js",
"scripts": {

View File

@ -5,7 +5,9 @@ import { ListView, IViewField, SelectionMode, GroupOrder, IGrouping } from "@pnp
import { IApplications, IApplication, IFormattedApplication } from '../../../models/IApplication';
import { IGraphAppSecretExpirationState } from './GraphAppSecretExpirationState';
import * as moment from 'moment';
import { DefaultButton, Spinner, mergeStyles } from '@fluentui/react';
import { DefaultButton, Spinner, mergeStyles, SearchBox } from '@fluentui/react';
import { Pagination } from "@pnp/spfx-controls-react/lib/pagination";
import * as strings from 'GraphAppSecretExpirationWebPartStrings';
const stackItemHidden = mergeStyles({
display: 'none',
@ -57,9 +59,13 @@ export default class GraphAppSecretExpiration extends React.Component<IGraphAppS
super(props);
this.state = {
applications: [],
filteredApplications: [],
filterValue: "",
searchFilter: "",
error: undefined,
loading: true,
groupByFields: []
groupByFields: [],
page: 1
};
}
@ -113,42 +119,80 @@ export default class GraphAppSecretExpiration extends React.Component<IGraphAppS
var today = (moment(Date.now())).format('DD-MMM-YYYY');
try {
applications.forEach(app => {
if (app.passwordCredentials.length > 0) {
app.passwordCredentials.forEach(pswd => {
let daysBeforeExpiration = moment.duration((moment(pswd.endDateTime)).diff(today, 'days'), 'days').asDays();
let formatedApp: IFormattedApplication = {
applicationId: app.appId,
displayName: app.displayName,
type: "Secret",
expirationDate: (moment(pswd.endDateTime)).format('DD-MMM-YYYY'),
daysLeft: daysBeforeExpiration > 0 ? daysBeforeExpiration : 0
};
displayedApplication.push(formatedApp);
});
}
if (app.keyCredentials.length > 0) {
app.keyCredentials.forEach(keyCred => {
let daysBeforeExpiration = moment.duration((moment(keyCred.endDateTime)).diff(today, 'days'), 'days').asDays();
let formatedApp: IFormattedApplication = {
applicationId: app.appId,
displayName: app.displayName,
type: "Certificate",
expirationDate: (moment(keyCred.endDateTime)).format('DD-MMM-YYYY'),
daysLeft: daysBeforeExpiration > 0 ? daysBeforeExpiration : 0
};
displayedApplication.push(formatedApp);
});
}
app.passwordCredentials.forEach(pswd => {
let daysBeforeExpiration = moment.duration((moment(pswd.endDateTime)).diff(today, 'days'), 'days').asDays();
let formatedApp: IFormattedApplication = {
applicationId: app.appId,
displayName: app.displayName,
type: "Secret",
expirationDate: (moment(pswd.endDateTime)).format('DD-MMM-YYYY'),
daysLeft: daysBeforeExpiration > 0 ? daysBeforeExpiration : 0
};
displayedApplication.push(formatedApp);
});
app.keyCredentials.forEach(keyCred => {
let daysBeforeExpiration = moment.duration((moment(keyCred.endDateTime)).diff(today, 'days'), 'days').asDays();
let formatedApp: IFormattedApplication = {
applicationId: app.appId,
displayName: app.displayName,
type: "Certificate",
expirationDate: (moment(keyCred.endDateTime)).format('DD-MMM-YYYY'),
daysLeft: daysBeforeExpiration > 0 ? daysBeforeExpiration : 0
};
displayedApplication.push(formatedApp);
});
});
this.setState({
applications: displayedApplication
applications: displayedApplication,
filteredApplications: displayedApplication
});
} catch (error) {
console.log(error);
}
}
private _getPage(selectedPage: number) {
this.setState({
page: selectedPage
});
}
private _filterApplication = (value, clear: boolean) => {
let searchResult: IFormattedApplication[] = [];
if (clear) {
this.state.applications.forEach(app => {
if (this._filterByProperties(app, value)) {
searchResult.push(app);
}
});
this.setState({
filteredApplications: searchResult,
filterValue: value
});
} else {
this.setState({
filteredApplications: this.state.applications,
filterValue: value,
page: 1
});
}
}
private _filterByProperties(application: IFormattedApplication, filterValue) {
if (application.applicationId.toLowerCase().indexOf(filterValue.toLowerCase()) >= 0) {
return true;
} else if (application.displayName.toLowerCase().indexOf(filterValue.toLowerCase()) >= 0) {
return true;
} else if (application.expirationDate.toLowerCase().indexOf(filterValue.toLowerCase()) >= 0) {
return true;
} else if (application.type.toLowerCase().indexOf(filterValue.toLowerCase()) >= 0) {
return true;
} else {
return false;
}
}
private _groupView = () => {
if (this.state.groupByFields.length === 0) {
let groupByFields: IGrouping[] = [
@ -177,21 +221,31 @@ export default class GraphAppSecretExpiration extends React.Component<IGraphAppS
<p className={styles.title}>Application list :</p>
<div className={this.state.loading ? "" : stackItemHidden}>
<Spinner label="Loading..." ariaLive="assertive" labelPosition="left" />
<br/>
<br />
</div>
<div className={this.state.loading ? stackItemHidden : ""}>
<DefaultButton text={this.state.groupByFields.length === 0 ? "Group by App ID" : "Ungroup"} onClick={this._groupView} />
<SearchBox placeholder="Search" onChange={(e, text) => this._filterApplication(text, false)} onClear={() => this._filterApplication("", true)} value={this.state.filterValue} />
<ListView
items={this.state.applications}
items={this.state.filteredApplications.slice(this.state.page === 1 || this.state.filterValue !== "" ? 0 : this.state.page * 10 - 10, this.state.page * 10)}
viewFields={_viewFields}
iconFieldName="ServerRelativeUrl"
compact={true}
selectionMode={SelectionMode.none}
selection={this._getSelection}
groupByFields={this.state.groupByFields}
showFilter={true}
showFilter={false}
filterPlaceHolder="Search..." />
</div>
<Pagination
currentPage={1}
totalPages={Math.floor(this.state.filteredApplications.length / 10) + 1}
onChange={(page) => this._getPage(page)}
limiter={3} // Optional - default value 3
hideFirstPageJump // Optional
hideLastPageJump // Optional
limiterIcon={"Emoji12"} // Optional
/>
</div>
</div>
);

View File

@ -3,7 +3,11 @@ import { IApplications, IApplication,IPasswordCredential,IKeyCredential,IFormatt
export interface IGraphAppSecretExpirationState {
applications: IFormattedApplication[];
filteredApplications: IFormattedApplication[];
filterValue: string;
searchFilter: string;
groupByFields: IGrouping[];
page: number;
error: string;
loading: boolean;
}