Error handling and logging improvments

This commit is contained in:
anoopt 2020-09-03 01:11:14 +00:00
parent df87dd9cfc
commit 2c7161eab4
6 changed files with 155 additions and 66 deletions

View File

@ -32,13 +32,14 @@ This web part shows how to use the Microsoft Graph APIs (beta) for Taxonomy to g
Solution|Author(s) Solution|Author(s)
--------|--------- --------|---------
react-graph-cascading-managed-metadata| Anoop Tatti ([@anooptells](https://twitter.com/anooptells)) react-graph-cascading-managed-metadata| Anoop Tatti ([anoopt](https://github.com/anoopt), [@anooptells](https://twitter.com/anooptells))
## Version history ## Version history
Version|Date|Comments Version|Date|Comments
-------|----|-------- -------|----|--------
1.0.0|Aug 24, 2020|Initial release 1.0.0|Aug 24, 2020|Initial release
1.0.1|Sep 03, 2020|Error handling and logging improvments
## Disclaimer ## Disclaimer

View File

@ -3215,9 +3215,9 @@
} }
}, },
"@pnp/spfx-controls-react": { "@pnp/spfx-controls-react": {
"version": "1.19.0", "version": "1.20.0-beta.52e4e9f",
"resolved": "https://registry.npmjs.org/@pnp/spfx-controls-react/-/spfx-controls-react-1.19.0.tgz", "resolved": "https://registry.npmjs.org/@pnp/spfx-controls-react/-/spfx-controls-react-1.20.0-beta.52e4e9f.tgz",
"integrity": "sha512-W3PS6I8NsdbOZjE9I9djloYutQW17QHd4nT7jFwPyJFoxnt1MDfWyN6nrPhaeGnnPde3t3TlUbWP4HKLXChFiw==", "integrity": "sha512-2lh6EI+0M6H/cgQyDq8av2/LvG/0hCgZG0/XfjWUIFGHcdqpbmcifJbVUeEX46nVt5gHDVeI3vcpqhbaAA5GrA==",
"requires": { "requires": {
"@pnp/common": "1.0.1", "@pnp/common": "1.0.1",
"@pnp/logging": "1.0.1", "@pnp/logging": "1.0.1",

View File

@ -18,7 +18,7 @@
"@microsoft/sp-office-ui-fabric-core": "1.11.0", "@microsoft/sp-office-ui-fabric-core": "1.11.0",
"@microsoft/sp-property-pane": "1.11.0", "@microsoft/sp-property-pane": "1.11.0",
"@microsoft/sp-webpart-base": "1.11.0", "@microsoft/sp-webpart-base": "1.11.0",
"@pnp/spfx-controls-react": "1.19.0", "@pnp/spfx-controls-react": "^1.20.0-beta.52e4e9f",
"office-ui-fabric-react": "6.214.0", "office-ui-fabric-react": "6.214.0",
"react": "16.8.5", "react": "16.8.5",
"react-dom": "16.8.5" "react-dom": "16.8.5"

View File

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import * as ReactDom from 'react-dom'; import * as ReactDom from 'react-dom';
import { Version, DisplayMode } from '@microsoft/sp-core-library'; import { Version, DisplayMode, Guid } from '@microsoft/sp-core-library';
import { import {
BaseClientSideWebPart, BaseClientSideWebPart,
IPropertyPaneConfiguration, IPropertyPaneConfiguration,
@ -22,8 +22,7 @@ export default class CascadingManagedMetadataWebPart extends BaseClientSideWebPa
public async render(): Promise<void> { public async render(): Promise<void> {
await MSGraph.Init(this.context); await MSGraph.Init(this.context);
let renderElement = null; let renderElement = null;
//TODO: Use function to check if GUID? if (this.properties.termSetId) {
if (this.properties.termSetId && this.properties.termSetId.length == 36) {
renderElement = React.createElement( renderElement = React.createElement(
CascadingManagedMetadata, CascadingManagedMetadata,
{ {
@ -69,6 +68,19 @@ export default class CascadingManagedMetadataWebPart extends BaseClientSideWebPa
return Version.parse('1.0'); return Version.parse('1.0');
} }
private validateTermSetId(value: string): string {
if (value === null ||
value.trim().length === 0) {
return 'Provide a term set Id.';
}
if (!Guid.isValid(value.trim())) {
return 'Term set Id must be a valid GUID.';
}
return '';
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return { return {
pages: [ pages: [
@ -81,7 +93,8 @@ export default class CascadingManagedMetadataWebPart extends BaseClientSideWebPa
groupName: strings.BasicGroupName, groupName: strings.BasicGroupName,
groupFields: [ groupFields: [
PropertyPaneTextField('termSetId', { PropertyPaneTextField('termSetId', {
label: strings.TermSetIdFieldLabel label: strings.TermSetIdFieldLabel,
onGetErrorMessage: this.validateTermSetId.bind(this)
}) })
] ]
} }

View File

@ -16,31 +16,74 @@ const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (pro
const [citiesList, setCitiesList] = React.useState<IDropdownOption[]>([]); const [citiesList, setCitiesList] = React.useState<IDropdownOption[]>([]);
const [selectedCityCoordinates, setSelectedCityCoordinates] = React.useState<string>(null); const [selectedCityCoordinates, setSelectedCityCoordinates] = React.useState<string>(null);
const [selectedCity, setSelectedCity] = React.useState<string>(null); const [selectedCity, setSelectedCity] = React.useState<string>(null);
const [showMap, setShowMap] = React.useState<boolean>(false);
const [coordinates, setCoordinates] = React.useState<ICoordinates>({ latitude: null, longitude: null }); const [coordinates, setCoordinates] = React.useState<ICoordinates>({ latitude: null, longitude: null });
const [messageBarStatus, setMessageBarStatus] = React.useState({
type: MessageBarType.info,
message: <span></span>,
show: false
});
const LOG_SOURCE: string = "Cascading MMD -";
React.useEffect(() => { React.useEffect(() => {
_getCountries().then(countries => { _getCountries().then(countries => {
const options: IDropdownOption[] = countries.value.map(c => ({ key: c.id, text: c.labels[0].name })); if (countries) {
setCountriesList(options); const options: IDropdownOption[] = countries.value.map(c => ({ key: c.id, text: c.labels[0].name }));
setCountriesList(options);
} else {
setCountriesList([]);
clearData();
}
}); });
}, []); }, [props.termSetId]); //* Run this also when the property termSetId changes
//* Get the country terms i.e. level 1 children using Graph //* Get the country terms i.e. level 1 children using Graph
const _getCountries = async (): Promise<ITerms> => { const _getCountries = async (): Promise<ITerms> => {
let countries: ITerms = await MSGraph.Get(`/termStore/sets/${props.termSetId}/children`, "beta"); try {
return (countries); let countries: ITerms = await MSGraph.Get(`/termStore/sets/${props.termSetId}/children`, "beta");
setMessageBarStatus(state => ({ ...state, show: false }));
console.debug("%s Retrieved countries. %o", LOG_SOURCE, countries);
return countries;
}
catch (error) {
console.error("%s Error retrieving countries. Details - %o", LOG_SOURCE, error);
setMessageBarStatus({
type: MessageBarType.error,
message: <span>Error in retrieving countries. Please contact admin.</span>,
show: true
});
return null;
}
}; };
//* Get the city terms under a country i.e. level 2 children using Graph //* Get the city terms under a country i.e. level 2 children using Graph
const _onCountryChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => { const _onCountryChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
setCoordinates({ latitude: null, longitude: null }); clearData();
setSelectedCityCoordinates(null);
setSelectedCity(null);
let countryTermId: string = item.key.toString(); let countryTermId: string = item.key.toString();
MMDService.GetTermsAsDropdownOptions(`/termStore/sets/${props.termSetId}/terms/${countryTermId}/children`, countryTermId, true).then(options => { MMDService.GetTermsAsDropdownOptions(`/termStore/sets/${props.termSetId}/terms/${countryTermId}/children`, countryTermId, true)
setCitiesList(options); .then(options => {
}); setCitiesList(options);
//setShowMap(false);
console.debug("%s Retrieved cities. %o", LOG_SOURCE, options);
setMessageBarStatus({
type: MessageBarType.warning,
message: options.length > 0 ?
<span>To see the map, please select a city. </span> :
<span> No city terms in the selected country.</span>,
show: true
});
})
.catch(error => {
console.error("%s Error retrieving cities. Details - %o", LOG_SOURCE, error);
setMessageBarStatus({
type: MessageBarType.error,
message: <span>Error in retrieving cities. Please contact admin.</span>,
show: true
});
setCitiesList([]);
});
}; };
@ -56,41 +99,65 @@ const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (pro
longitude: isNaN(Number(long)) ? null : Number(long) longitude: isNaN(Number(long)) ? null : Number(long)
}; };
setCoordinates(coordinates); setCoordinates(coordinates);
console.debug("%s Retrieved coordinates. %o", LOG_SOURCE, coordinates);
if (coordinates.latitude && coordinates.longitude) {
setShowMap(true);
setMessageBarStatus(state => ({ ...state, show: false }));
} else {
setShowMap(false);
setMessageBarStatus({
type: MessageBarType.error,
message: <span>To see the map, please check if the coordinates have been configured correctly.</span>,
show: true
});
}
}; };
//* Clear the data related to cities and maps
const clearData = () => {
setCoordinates({ latitude: null, longitude: null });
setSelectedCityCoordinates(null);
setSelectedCity(null);
setCitiesList([]);
setShowMap(false);
}
return ( return (
<div> <div>
<Dropdown <Dropdown
label="Country" label="Country"
placeHolder="Select a country" placeHolder="Select a country"
options={countriesList} options={countriesList}
onChange={_onCountryChange} /> defaultSelectedKey=""
onChange={_onCountryChange}
disabled={!(countriesList.length > 0)} />
<Dropdown <Dropdown
label="City" label="City"
selectedKey={selectedCityCoordinates} selectedKey={selectedCityCoordinates}
placeHolder="Select a city" placeHolder="Select a city"
options={citiesList} options={citiesList}
onChange={_onCityChange} /> onChange={_onCityChange}
disabled={!(citiesList.length > 0)} />
{ {
coordinates.latitude && coordinates.longitude ? showMap &&
( <Map
<React.Fragment> titleText={`Map of our office in ${selectedCity}`}
<Map coordinates={coordinates}
titleText={`Map of our office in ${selectedCity}`} zoom={15}
coordinates={coordinates} enableSearch={false} />
zoom={15} }
enableSearch={false} />
</React.Fragment> {
) : messageBarStatus.show &&
( <div style={{ marginTop: "15px" }}>
<div style={{ marginTop: "15px" }}> <MessageBar messageBarType={messageBarStatus.type}>
<MessageBar messageBarType={MessageBarType.warning}> {messageBarStatus.message}
{selectedCity ? "To see the map, please check if the coordinates have been configured correctly." </MessageBar>
: "To see the map, please select a city."} </div>
</MessageBar>
</div>
)
} }
</div> </div>
); );

View File

@ -5,6 +5,7 @@ import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
export class MMDService { export class MMDService {
private static _sessionStorageKey: string = "CMMD_Options"; private static _sessionStorageKey: string = "CMMD_Options";
private static _logSource: string = "Cascading MMD Service -";
public static async GetTermsAsDropdownOptions(apiUrl: string, parent: string, tryFromCache: boolean): Promise<IDropdownOption[]> { public static async GetTermsAsDropdownOptions(apiUrl: string, parent: string, tryFromCache: boolean): Promise<IDropdownOption[]> {
@ -24,27 +25,34 @@ export class MMDService {
} }
private static async _getTermsAsDropdownOptionsUsingGraph(apiUrl: string, parent: string): Promise<IDropdownOption[]> { private static async _getTermsAsDropdownOptionsUsingGraph(apiUrl: string, parent: string): Promise<IDropdownOption[]> {
let terms: ITerms = await MSGraph.Get(apiUrl, "beta"); try {
if (terms.value) { let terms: ITerms = await MSGraph.Get(apiUrl, "beta");
//* Set key as description of the term if (terms.value) {
//* Description will be of the format latitude;longitude //* Set key as description of the term
//* This will be used to render maps //* Description will be of the format latitude;longitude
const options: IDropdownOption[] = terms.value.map(t => ({ //* This will be used to render maps
key: t.descriptions[0] ? t.descriptions[0].description : t.id, const options: IDropdownOption[] = terms.value.map(t => ({
text: t.labels[0].name })); key: t.descriptions[0] ? t.descriptions[0].description : t.id,
let optionsToStoreInCache: IOption[] = options.map(o => ({ text: t.labels[0].name
key: o.key.toString(), }));
text: o.text, let optionsToStoreInCache: IOption[] = options.map(o => ({
parent key: o.key.toString(),
})); text: o.text,
let optionsFromCache: IOption[] = this._fetchFromSessionStorge(); parent
optionsToStoreInCache = [...optionsFromCache, ...optionsToStoreInCache]; }));
window.sessionStorage.setItem(this._sessionStorageKey, JSON.stringify(optionsToStoreInCache)); let optionsFromCache: IOption[] = this._fetchFromSessionStorge();
console.log("Options added in cache"); optionsToStoreInCache = [...optionsFromCache, ...optionsToStoreInCache];
return options; window.sessionStorage.setItem(this._sessionStorageKey, JSON.stringify(optionsToStoreInCache));
} else { console.debug("%s Data added in cache.", this._logSource);
return options;
} else {
return [];
}
} catch (error) {
console.error("%s Error getting data from Graph. Details - %o", this._logSource, error);
return []; return [];
} }
} }
private static _fetchFromSessionStorge(): IOption[] { private static _fetchFromSessionStorge(): IOption[] {
@ -53,11 +61,11 @@ export class MMDService {
if (stringResult) { if (stringResult) {
try { try {
result = JSON.parse(stringResult); result = JSON.parse(stringResult);
if(result.length) { if (result.length) {
console.log("Fetched options from cache"); console.debug("%s Fetched data from cache", this._logSource);
} }
} catch (error) { } catch (error) {
console.error(error); console.error("%s Error getting data from cache. Details - %o", this._logSource, error);
} }
} }
return result; return result;