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)
--------|---------
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|Date|Comments
-------|----|--------
1.0.0|Aug 24, 2020|Initial release
1.0.1|Sep 03, 2020|Error handling and logging improvments
## Disclaimer

View File

@ -3215,9 +3215,9 @@
}
},
"@pnp/spfx-controls-react": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/@pnp/spfx-controls-react/-/spfx-controls-react-1.19.0.tgz",
"integrity": "sha512-W3PS6I8NsdbOZjE9I9djloYutQW17QHd4nT7jFwPyJFoxnt1MDfWyN6nrPhaeGnnPde3t3TlUbWP4HKLXChFiw==",
"version": "1.20.0-beta.52e4e9f",
"resolved": "https://registry.npmjs.org/@pnp/spfx-controls-react/-/spfx-controls-react-1.20.0-beta.52e4e9f.tgz",
"integrity": "sha512-2lh6EI+0M6H/cgQyDq8av2/LvG/0hCgZG0/XfjWUIFGHcdqpbmcifJbVUeEX46nVt5gHDVeI3vcpqhbaAA5GrA==",
"requires": {
"@pnp/common": "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-property-pane": "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",
"react": "16.8.5",
"react-dom": "16.8.5"

View File

@ -1,6 +1,6 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version, DisplayMode } from '@microsoft/sp-core-library';
import { Version, DisplayMode, Guid } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
@ -22,8 +22,7 @@ export default class CascadingManagedMetadataWebPart extends BaseClientSideWebPa
public async render(): Promise<void> {
await MSGraph.Init(this.context);
let renderElement = null;
//TODO: Use function to check if GUID?
if (this.properties.termSetId && this.properties.termSetId.length == 36) {
if (this.properties.termSetId) {
renderElement = React.createElement(
CascadingManagedMetadata,
{
@ -69,6 +68,19 @@ export default class CascadingManagedMetadataWebPart extends BaseClientSideWebPa
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 {
return {
pages: [
@ -81,7 +93,8 @@ export default class CascadingManagedMetadataWebPart extends BaseClientSideWebPa
groupName: strings.BasicGroupName,
groupFields: [
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 [selectedCityCoordinates, setSelectedCityCoordinates] = 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 [messageBarStatus, setMessageBarStatus] = React.useState({
type: MessageBarType.info,
message: <span></span>,
show: false
});
const LOG_SOURCE: string = "Cascading MMD -";
React.useEffect(() => {
_getCountries().then(countries => {
const options: IDropdownOption[] = countries.value.map(c => ({ key: c.id, text: c.labels[0].name }));
setCountriesList(options);
if (countries) {
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
const _getCountries = async (): Promise<ITerms> => {
let countries: ITerms = await MSGraph.Get(`/termStore/sets/${props.termSetId}/children`, "beta");
return (countries);
try {
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
const _onCountryChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
setCoordinates({ latitude: null, longitude: null });
setSelectedCityCoordinates(null);
setSelectedCity(null);
clearData();
let countryTermId: string = item.key.toString();
MMDService.GetTermsAsDropdownOptions(`/termStore/sets/${props.termSetId}/terms/${countryTermId}/children`, countryTermId, true).then(options => {
setCitiesList(options);
});
MMDService.GetTermsAsDropdownOptions(`/termStore/sets/${props.termSetId}/terms/${countryTermId}/children`, countryTermId, true)
.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)
};
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 (
<div>
<Dropdown
label="Country"
placeHolder="Select a country"
options={countriesList}
onChange={_onCountryChange} />
defaultSelectedKey=""
onChange={_onCountryChange}
disabled={!(countriesList.length > 0)} />
<Dropdown
label="City"
selectedKey={selectedCityCoordinates}
placeHolder="Select a city"
options={citiesList}
onChange={_onCityChange} />
onChange={_onCityChange}
disabled={!(citiesList.length > 0)} />
{
coordinates.latitude && coordinates.longitude ?
(
<React.Fragment>
<Map
titleText={`Map of our office in ${selectedCity}`}
coordinates={coordinates}
zoom={15}
enableSearch={false} />
</React.Fragment>
) :
(
<div style={{ marginTop: "15px" }}>
<MessageBar messageBarType={MessageBarType.warning}>
{selectedCity ? "To see the map, please check if the coordinates have been configured correctly."
: "To see the map, please select a city."}
</MessageBar>
</div>
)
showMap &&
<Map
titleText={`Map of our office in ${selectedCity}`}
coordinates={coordinates}
zoom={15}
enableSearch={false} />
}
{
messageBarStatus.show &&
<div style={{ marginTop: "15px" }}>
<MessageBar messageBarType={messageBarStatus.type}>
{messageBarStatus.message}
</MessageBar>
</div>
}
</div>
);

View File

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