SPFx v1.14, custom props of terms, other updates

This commit is contained in:
Anoop 2022-03-05 00:49:48 +00:00
parent 51eebc82de
commit 823f273e95
28 changed files with 16819 additions and 9123 deletions

View File

@ -1,25 +0,0 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@ -9,6 +9,7 @@ node_modules
# Build generated files
dist
lib
release
solution
temp
*.sppkg

View File

@ -0,0 +1,16 @@
!dist
config
gulpfile.js
release
src
temp
tsconfig.json
tslint.json
*.log
.yo-rc.json
.vscode

View File

@ -2,7 +2,7 @@
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.9.1",
"version": "1.14.0",
"libraryName": "react-graph-cascading-managed-metadata",
"libraryId": "cdc626ca-e9a7-4d1d-bded-a574bc5e61d0",
"packageManager": "npm",

View File

@ -8,21 +8,13 @@ This web part shows how to use the Microsoft Graph APIs (beta) for Taxonomy to g
![Cascading managed metadata](./assets/cmmd.gif)
### Termstore
![Term store](./assets/termstore.png)
## Compatibility
![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
![Node.js v10](https://img.shields.io/badge/Node.js-v10-green.svg)
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
![Does not work with SharePoint 2019](https://img.shields.io/badge/SharePoint%20Server%202019-Incompatible-red.svg "SharePoint Server 2019 requires SPFx 1.4.1 or lower")
![Does not work with SharePoint 2016 (Feature Pack 2)](https://img.shields.io/badge/SharePoint%20Server%202016%20(Feature%20Pack%202)-Incompatible-red.svg "SharePoint Server 2016 Feature Pack 2 requires SPFx 1.1")
![Teams Incompatible](https://img.shields.io/badge/Teams-Incompatible-lightgrey.svg)
![Local Workbench Incompatible](https://img.shields.io/badge/Local%20Workbench-Incompatible-yellow.svg "This solution requires access to data terms using Microsoft Graph API")
![Hosted Workbench Compatible](https://img.shields.io/badge/Hosted%20Workbench-Compatible-green.svg)
![Compatible with Remote Containers](https://img.shields.io/badge/Remote%20Containers-Compatible-green.svg)
![SPFx 1.14.0](https://img.shields.io/badge/SPFx-1.14.0-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)
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
![Workbench Hosted](https://img.shields.io/badge/Workbench-Hosted-green.svg)
## Applies to
@ -31,16 +23,18 @@ This web part shows how to use the Microsoft Graph APIs (beta) for Taxonomy to g
## Pre-requisites
* Set up the termset structure as shown in the image above.
* Set up the termset structure as shown in the image below - .
* To the termset, add a custom property called `UsedForShowingMaps` and set it's value to `true` as shown in the image below
![Term store properties](./assets/termsetproperties.png)
* For the cities, get the required latitude and longitude.
* Set the description of the city term as `latitude;longitude` (as highlighted for the term `London` in the image above).
* Add 2 custom properties for the city terms `latitude` and `longitude` (as highlighted for the term `London` in the image below).
![Term store](./assets/termstore.png)
## Solution
Solution|Author(s)
--------|---------
react-graph-cascading-managed-metadata| Anoop Tatti ([anoopt](https://github.com/anoopt), [@anooptells](https://twitter.com/anooptells))
react-graph-cascading-managed-metadata| Anoop Tatti ([anoopt](https://github.com/anoopt), ([https://linktr.ee/anoopt](https://linktr.ee/anoopt))
## Version history
@ -48,6 +42,7 @@ Version|Date|Comments
-------|----|--------
1.0.0|Aug 24, 2020|Initial release
1.0.1|Sep 03, 2020|Error handling and logging improvements
2.0.0|Mar 04, 2022|Updated to SPFx 1.14, used term `custom properties` to get co-ordinates (as Graph API provides that capability now), usage of `PropertyFieldGuid` and several other improvements
## Minimal Path to Awesome
@ -55,6 +50,9 @@ Version|Date|Comments
* in the command line run:
* `npm install`
* `gulp serve`
* Make sure you have completed the [pre-requisites](#Pre-requisites)
* Add the web part to the workbench page of a site
* Edit the web part and add the termset id in the properties
> This sample can also be opened with [VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview). Visit https://aka.ms/spfx-devcontainer for further instructions.
@ -62,7 +60,7 @@ Version|Date|Comments
This sample illustrates the following concepts on top of the SharePoint Framework:
* Get data terms using Microsoft Graph API (beta).
* Get termset, terms and their custom properties using Microsoft Graph API (beta).
* React Hooks
* Using async / await for the async calls
* Caching the data in session storage
@ -72,12 +70,15 @@ This sample illustrates the following concepts on top of the SharePoint Framewor
### Enhancements
* Currently, this web part supports 2-level cascading. So there is scope to enhance this such that it supports more levels of cascading dynamically.
* Currently, this web part reads latitude and longitude from description of the city terms. If there is a way of getting these from the custom properties of the city terms, then that needs to be implemented.
## Video
[![Cascading managed metadata using Microsoft Graph and SharePoint Framework](./assets/video-thumbnail.jpg)](https://www.youtube.com/watch?v=lk47ijo_H6Y "Cascading managed metadata using Microsoft Graph and SharePoint Framework")
## Need to show more details?
An Adaptive Card Extension (ACE) which performs similar operations and provides more data like the local time of the office, weather data of the office location and address of the office location along with it's map can be found in the `Office locations` sample of [pnp/sp-dev-fx-aces repostory](https://github.com/pnp/sp-dev-fx-aces/tree/main/samples/ImageCard-OfficeLocations).
## Help
We do not support samples, but we this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues.

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -14,6 +14,7 @@
"externals": {},
"localizedResources": {
"CascadingManagedMetadataWebPartStrings": "lib/webparts/cascadingManagedMetadata/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
}
}

View File

@ -1,4 +0,0 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -1,6 +1,6 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"workingDir": "./release/assets/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "react-graph-cascading-managed-metadata",
"accessKey": "<!-- ACCESS KEY -->"

View File

@ -2,6 +2,21 @@
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-graph-cascading-managed-metadata-client-side-solution",
"developer": {
"name": "",
"privacyUrl": "",
"termsOfUseUrl": "",
"websiteUrl": "",
"mpnId": "Undefined-1.14.0"
},
"metadata": {
"shortDescription": {
"default": "react-graph-cascading-managed-metadata description"
},
"longDescription": {
"default": "react-graph-cascading-managed-metadata description"
}
},
"id": "cdc626ca-e9a7-4d1d-bded-a574bc5e61d0",
"version": "1.0.0.0",
"includeClientSideAssets": true,
@ -11,4 +26,4 @@
"paths": {
"zippedPackage": "solution/react-graph-cascading-managed-metadata.sppkg"
}
}
}

View File

@ -2,9 +2,5 @@
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
"initialPage": "https://enter-your-SharePoint-site/_layouts/workbench.aspx"
}

View File

@ -0,0 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/s-KaiNet/spfx-fast-serve/master/schema/config.latest.schema.json",
"cli": {
"isLibraryComponent": false
}
}

View File

@ -0,0 +1,24 @@
/*
* User webpack settings file. You can add your own settings here.
* Changes from this file will be merged into the base webpack configuration file.
* This file will not be overwritten by the subsequent spfx-fast-serve calls.
*/
// you can add your project related webpack configuration here, it will be merged using webpack-merge module
// i.e. plugins: [new webpack.Plugin()]
const webpackConfig = {
}
// for even more fine-grained control, you can apply custom webpack settings using below function
const transformConfig = function (initialWebpackConfig) {
// transform the initial webpack config here, i.e.
// initialWebpackConfig.plugins.push(new webpack.Plugin()); etc.
return initialWebpackConfig;
}
module.exports = {
webpackConfig,
transformConfig
}

View File

@ -1,36 +1,21 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
const argv = build.rig.getYargs().argv;
const useCustomServe = argv['custom-serve'];
const fs = require("fs");
const workbenchApi = require("@microsoft/sp-webpart-workbench/lib/api");
var getTasks = build.rig.getTasks;
build.rig.getTasks = function () {
var result = getTasks.call(build.rig);
if (useCustomServe) {
build.tslintCmd.enabled = false;
const ensureWorkbenchSubtask = build.subTask('ensure-workbench-task', function (gulp, buildOptions, done) {
this.log('Creating workbench.html file...');
try {
workbenchApi.default["/workbench"]();
} catch (e) { }
result.set('serve', result.get('serve-deprecated'));
done();
});
return result;
};
build.rig.addPostBuildTask(build.task('ensure-workbench', ensureWorkbenchSubtask));
build.configureWebpack.mergeConfig({
additionalConfiguration: (generatedConfiguration) => {
fs.writeFileSync("./temp/_webpack_config.json", JSON.stringify(generatedConfiguration, null, 2));
return generatedConfiguration;
}
});
}
/* fast-serve */
const { addFastServe } = require("spfx-fast-serve-helpers");
addFastServe(build);
/* end of fast-serve */
build.initialize(require('gulp'));

File diff suppressed because it is too large Load Diff

View File

@ -3,51 +3,34 @@
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test",
"serve": "cross-env NODE_OPTIONS=--max_old_space_size=4096 gulp bundle --custom-serve && cross-env NODE_OPTIONS=--max_old_space_size=4096 webpack-dev-server --mode development --config ./webpack.js --env.env=dev"
"serve": "gulp bundle --custom-serve --max_old_space_size=4096 && fast-serve"
},
"dependencies": {
"@microsoft/sp-core-library": "1.11.0",
"@microsoft/sp-lodash-subset": "1.11.0",
"@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.20.0-beta.52e4e9f",
"office-ui-fabric-react": "6.214.0",
"react": "16.8.5",
"react-dom": "16.8.5"
},
"resolutions": {
"@types/react": "16.8.8"
"@microsoft/sp-core-library": "1.14.0",
"@microsoft/sp-lodash-subset": "1.14.0",
"@microsoft/sp-office-ui-fabric-core": "1.14.0",
"@microsoft/sp-property-pane": "1.14.0",
"@microsoft/sp-webpart-base": "1.14.0",
"@pnp/spfx-controls-react": "3.5.0",
"@pnp/spfx-property-controls": "3.5.0",
"office-ui-fabric-react": "7.174.1",
"react": "16.13.1",
"react-dom": "16.13.1"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-build-web": "1.11.0",
"@microsoft/sp-module-interfaces": "1.11.0",
"@microsoft/sp-tslint-rules": "1.11.0",
"@microsoft/sp-webpart-workbench": "1.11.0",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"@microsoft/rush-stack-compiler-3.9": "0.4.47",
"@microsoft/sp-build-web": "1.14.0",
"@microsoft/sp-module-interfaces": "1.14.0",
"@microsoft/sp-tslint-rules": "1.14.0",
"@types/react": "16.9.51",
"@types/react-dom": "16.9.8",
"@types/webpack-env": "1.13.1",
"ajv": "~5.2.2",
"cross-env": "7.0.2",
"css-loader": "3.4.2",
"css-modules-typescript-loader": "4.0.0",
"del": "5.1.0",
"fork-ts-checker-webpack-plugin": "4.1.0",
"gulp": "~3.9.1",
"node-sass": "4.13.1",
"sass-loader": "8.0.2",
"style-loader": "1.1.3",
"ts-loader": "6.2.1",
"webpack": "4.42.0",
"webpack-cli": "3.3.11",
"webpack-dev-server": "3.10.3"
"gulp": "~4.0.2",
"spfx-fast-serve-helpers": "~1.14.0"
}
}

View File

@ -1,12 +1,9 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version, DisplayMode, Guid } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { Version, DisplayMode } from '@microsoft/sp-core-library';
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
import { IPropertyPaneConfiguration } from "@microsoft/sp-property-pane";
import { PropertyFieldGuid } from '@pnp/spfx-property-controls/lib/PropertyFieldGuid';
import * as strings from 'CascadingManagedMetadataWebPartStrings';
import CascadingManagedMetadata from './components/CascadingManagedMetadata';
import { MSGraph } from './services/MSGraph';
@ -68,18 +65,6 @@ 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 {
@ -92,9 +77,11 @@ export default class CascadingManagedMetadataWebPart extends BaseClientSideWebPa
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('termSetId', {
PropertyFieldGuid('termSetId', {
key: 'termSetId',
label: strings.TermSetIdFieldLabel,
onGetErrorMessage: this.validateTermSetId.bind(this)
value: this.properties.termSetId,
errorMessage: "Term set Id must be a valid GUID"
})
]
}

View File

@ -1,20 +1,20 @@
import * as React from 'react';
import styles from './CascadingManagedMetadata.module.scss';
import { ICascadingManagedMetadataProps } from './ICascadingManagedMetadataProps';
import { MSGraph } from '../services/MSGraph';
import { ITerms } from '../../interfaces';
import { ICMMDDropdownOption, IProperty, ITerms } from '../../interfaces';
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import { Map, ICoordinates } from "@pnp/spfx-controls-react/lib/Map";
import { MMDService } from '../services/MMDService';
import { find, isEmpty } from '@microsoft/sp-lodash-subset';
const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (props) => {
const [countriesList, setCountriesList] = React.useState<IDropdownOption[]>([]);
const [citiesList, setCitiesList] = React.useState<IDropdownOption[]>([]);
const [selectedCityCoordinates, setSelectedCityCoordinates] = React.useState<string>(null);
const [citiesList, setCitiesList] = React.useState<ICMMDDropdownOption[]>([]);
const [selectedCityKey, setselectedCityKey] = 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 });
@ -26,17 +26,29 @@ const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (pro
const LOG_SOURCE: string = "Cascading MMD -";
React.useEffect(() => {
_getCountries().then(countries => {
if (countries) {
const options: IDropdownOption[] = countries.value.map(c => ({ key: c.id, text: c.labels[0].name }));
setCountriesList(options);
} else {
setCountriesList([]);
clearData();
//* Check if the term set has a property called UsedForShowingMaps
const _checkIfTermsetIsUsedForShowingMaps = async (): Promise<boolean> => {
try {
const termsetData = await MSGraph.Get(`/termStore/sets/${props.termSetId}`, "beta", ["properties"]);
const termsetProperties: IProperty[] = termsetData.properties;
console.debug("%s Retrieved termset properties. %o", LOG_SOURCE, termsetProperties);
if (isEmpty(termsetProperties)) {
return false;
}
});
}, [props.termSetId]); //* Run this also when the property termSetId changes
return find(termsetProperties, (p: IProperty) => p.key === "UsedForShowingMaps")?.value === "true";
}
catch (error) {
console.error("%s Error retrieving termset properties. Details - %o", LOG_SOURCE, error);
setMessageBarStatus({
type: MessageBarType.error,
message: <span>Error retrieving termset properties. Please contact admin.</span>,
show: true
});
return null;
}
};
//* Get the country terms i.e. level 1 children using Graph
const _getCountries = async (): Promise<ITerms> => {
@ -58,51 +70,52 @@ const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (pro
};
//* Get the city terms under a country i.e. level 2 children using Graph
const _onCountryChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
clearData();
let countryTermId: string = item.key.toString();
const _onCountryChange = async (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): Promise<void> => {
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([]);
try {
clearData();
let countryTermId: string = item.key.toString();
let cities: ICMMDDropdownOption[] = await MMDService
.GetTermsAsDropdownOptions(
`/termStore/sets/${props.termSetId}/terms/${countryTermId}/children`,
["id", "labels", "properties"],
countryTermId,
true);
setCitiesList(cities);
setMessageBarStatus({
type: MessageBarType.warning,
message: cities.length > 0 ?
<span>To see the map, please select a city. </span> :
<span> No city terms in the selected country.</span>,
show: true
});
console.debug("%s Retrieved cities. %o", LOG_SOURCE, cities);
}
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([]);
}
};
//* Extract co-ordinates from key of the dropdown option
//* The key will contain the description of the term
//* The description of the term will be of the format latitude;longitude
const _onCityChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
setSelectedCity(item.text);
setSelectedCityCoordinates(item.key.toString());
const [lat, long] = item.key.toString().split(';');
const coordinates: ICoordinates = {
latitude: isNaN(Number(lat)) ? null : Number(lat),
longitude: isNaN(Number(long)) ? null : Number(long)
};
setCoordinates(coordinates);
console.debug("%s Retrieved coordinates. %o", LOG_SOURCE, coordinates);
const _onCityChange = (event: React.FormEvent<HTMLDivElement>, item: ICMMDDropdownOption): void => {
if (coordinates.latitude && coordinates.longitude) {
const { text, key, data } = item;
setSelectedCity(text);
setselectedCityKey(key.toString());
setCoordinates(data);
console.debug("%s Retrieved coordinates. %o", LOG_SOURCE, data);
if (data.latitude && data.longitude) {
setShowMap(true);
setMessageBarStatus(state => ({ ...state, show: false }));
} else {
@ -118,11 +131,58 @@ const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (pro
//* Clear the data related to cities and maps
const clearData = () => {
setCoordinates({ latitude: null, longitude: null });
setSelectedCityCoordinates(null);
setselectedCityKey(null);
setSelectedCity(null);
setCitiesList([]);
setShowMap(false);
}
};
const _start = async (): Promise<void> => {
let isTermsetUsedForShowingMaps: boolean = await _checkIfTermsetIsUsedForShowingMaps();
if(isTermsetUsedForShowingMaps === null) {
return;
}
if (!isTermsetUsedForShowingMaps) {
setCountriesList([]);
clearData();
setMessageBarStatus({
type: MessageBarType.warning,
message: <span>The selected term set is not used for showing maps.</span>,
show: true
});
return;
}
let countries: ITerms = await _getCountries();
if(countries === null) {
return;
}
if (isEmpty(countries.value)) {
setCountriesList([]);
clearData();
setMessageBarStatus({
type: MessageBarType.warning,
message: <span>No country terms in the selected termset. Please contact admin.</span>,
show: true
});
return;
}
let countriesList: IDropdownOption[] = countries.value.map(c => ({ key: c.id, text: c.labels[0].name }));
setCountriesList(countriesList);
};
React.useEffect(() => {
_start();
}, [props.termSetId]); //* Run this also when the property termSetId changes
return (
<div>
@ -136,7 +196,7 @@ const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (pro
<Dropdown
label="City"
selectedKey={selectedCityCoordinates}
selectedKey={selectedCityKey}
placeHolder="Select a city"
options={citiesList}
onChange={_onCityChange}

View File

@ -1,47 +1,44 @@
import { ITerms, IOption } from "../../interfaces";
import { ITerms, ICMMDDropdownOption } from "../../interfaces";
import { MSGraph } from "./MSGraph";
import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { find } from '@microsoft/sp-lodash-subset';
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[] = [];
public static async GetTermsAsDropdownOptions(apiUrl: string, selectProperties: string[], parent: string, tryFromCache: boolean): Promise<ICMMDDropdownOption[]> {
if (tryFromCache) {
let optionsFromCache: IOption[] = this._fetchFromSessionStorge();
let optionsFromCache: ICMMDDropdownOption[] = this._fetchFromSessionStorge();
if (optionsFromCache.length) {
let requiredOptionsFromCache = optionsFromCache.filter(o => o.parent == parent);
if (requiredOptionsFromCache.length) {
options = requiredOptionsFromCache.map(r => ({ key: r.key, text: r.text }));
return options;
return requiredOptionsFromCache;
}
}
}
//Get data using Graph
return await this._getTermsAsDropdownOptionsUsingGraph(apiUrl, parent);
return await this._getTermsAsDropdownOptionsUsingGraph(apiUrl, selectProperties, parent);
}
private static async _getTermsAsDropdownOptionsUsingGraph(apiUrl: string, parent: string): Promise<IDropdownOption[]> {
private static async _getTermsAsDropdownOptionsUsingGraph(apiUrl: string, selectProperties: string[], parent: string): Promise<ICMMDDropdownOption[]> {
try {
let terms: ITerms = await MSGraph.Get(apiUrl, "beta");
let terms: ITerms = await MSGraph.Get(apiUrl, "beta", selectProperties);
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,
const options: ICMMDDropdownOption[] = terms.value.map(t => ({
key: t.id,
text: t.labels[0].name,
data: {
latitude: Number(find(t.properties, p => p.key === "latitude")?.value) ?? null,
longitude: Number(find(t.properties, p => p.key === "longitude")?.value) ?? null,
},
parent
}));
let optionsFromCache: IOption[] = this._fetchFromSessionStorge();
optionsToStoreInCache = [...optionsFromCache, ...optionsToStoreInCache];
let optionsFromCache: ICMMDDropdownOption[] = this._fetchFromSessionStorge();
let optionsToStoreInCache: ICMMDDropdownOption[] = [...optionsFromCache, ...options];
window.sessionStorage.setItem(this._sessionStorageKey, JSON.stringify(optionsToStoreInCache));
console.debug("%s Data added in cache.", this._logSource);
return options;
@ -55,8 +52,8 @@ export class MMDService {
}
private static _fetchFromSessionStorge(): IOption[] {
let result: IOption[] = [];
private static _fetchFromSessionStorge(): ICMMDDropdownOption[] {
let result: ICMMDDropdownOption[] = [];
let stringResult: string = window.sessionStorage.getItem(this._sessionStorageKey);
if (stringResult) {
try {

View File

@ -10,7 +10,7 @@ export class MSGraph {
this._graphClient = await context.msGraphClientFactory.getClient();
}
public static async Get(apiUrl: string, version: string = "v1.0", selectProperties?: string[], expandProperties?: string[], filter?: string): Promise<any> {
public static async Get(apiUrl: string, version: string = "v1.0", selectProperties?: string[], expandProperties?: string[], filter?: string, count?: boolean): Promise<any> {
var p = new Promise<string>(async (resolve, reject) => {
let query = this._graphClient.api(apiUrl).version(version);
if (selectProperties && selectProperties.length > 0) {
@ -22,6 +22,9 @@ export class MSGraph {
if (expandProperties && expandProperties.length > 0) {
query = query.expand(expandProperties);
}
if (count) {
query = query.count(true);
}
let callback = (error: GraphError, response: any, rawResponse?: any) => {
if (error) {
@ -46,7 +49,7 @@ export class MSGraph {
if (error) {
reject(error);
} else {
resolve();
resolve(_response);
}
};
await query.update(content, callback);

View File

@ -0,0 +1,7 @@
import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { ICoordinates } from "@pnp/spfx-controls-react/lib/Map";
export interface ICMMDDropdownOption extends IDropdownOption {
data: ICoordinates;
parent?: string;
}

View File

@ -1,5 +0,0 @@
export interface IOption {
key: string;
text: string;
parent: string;
}

View File

@ -0,0 +1,4 @@
export interface IProperty {
key: string;
value: string;
}

View File

@ -1,5 +1,4 @@
import { IDescription } from "./IDescription";
import { ILabel } from "./ILabel";
import { IProperty, ILabel, IDescription } from ".";
export interface ITerm {
id: string;
@ -7,4 +6,5 @@ export interface ITerm {
lastModifiedDateTime: Date;
labels: ILabel[];
descriptions: IDescription[];
properties: IProperty[];
}

View File

@ -2,4 +2,5 @@ export { ILabel } from './ILabel';
export { IDescription } from './IDescription';
export { ITerm } from './ITerm';
export { ITerms } from './ITerms';
export { IOption } from './IOption';
export { IProperty } from './IProperty';
export { ICMMDDropdownOption } from './ICMMDDropdownOption';

View File

@ -1,5 +1,5 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/includes/tsconfig-web.json",
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.9/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
@ -19,20 +19,17 @@
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
"es2015.collection",
"es2015.promise"
]
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
"src/**/*.ts",
"src/**/*.tsx"
]
}

View File

@ -1,5 +1,5 @@
{
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"extends": "./node_modules/@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
@ -17,7 +17,6 @@
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,