Added container and sample.json
This commit is contained in:
parent
7850700800
commit
2d8b12f1fe
|
@ -0,0 +1,39 @@
|
|||
// For more information on how to run this SPFx project in a VS Code Remote Container, please visit https://aka.ms/spfx-devcontainer
|
||||
{
|
||||
"name": "SPFx 1.15.2",
|
||||
"image": "docker.io/m365pnp/spfx:1.15.2",
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {},
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"editorconfig.editorconfig",
|
||||
"dbaeumer.vscode-eslint"
|
||||
],
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [
|
||||
4321,
|
||||
35729
|
||||
],
|
||||
"portsAttributes": {
|
||||
"4321": {
|
||||
"protocol": "https",
|
||||
"label": "Manifest",
|
||||
"onAutoForward": "silent",
|
||||
"requireLocalPort": true
|
||||
},
|
||||
// Not needed for SPFx>= 1.12.1
|
||||
// "5432": {
|
||||
// "protocol": "https",
|
||||
// "label": "Workbench",
|
||||
// "onAutoForward": "silent"
|
||||
// },
|
||||
"35729": {
|
||||
"protocol": "https",
|
||||
"label": "LiveReload",
|
||||
"onAutoForward": "silent",
|
||||
"requireLocalPort": true
|
||||
}
|
||||
},
|
||||
"postCreateCommand": "bash .devcontainer/spfx-startup.sh",
|
||||
"remoteUser": "node"
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
echo
|
||||
echo -e "\e[1;94mInstalling Node dependencies\e[0m"
|
||||
npm install
|
||||
|
||||
## commands to create dev certificate and copy it to the root folder of the project
|
||||
echo
|
||||
echo -e "\e[1;94mGenerating dev certificate\e[0m"
|
||||
gulp trust-dev-cert
|
||||
|
||||
# Convert the generated PEM certificate to a CER certificate
|
||||
openssl x509 -inform PEM -in ~/.rushstack/rushstack-serve.pem -outform DER -out ./spfx-dev-cert.cer
|
||||
|
||||
# Copy the PEM ecrtificate for non-Windows hosts
|
||||
cp ~/.rushstack/rushstack-serve.pem ./spfx-dev-cert.pem
|
||||
|
||||
## add *.cer to .gitignore to prevent certificates from being saved in repo
|
||||
if ! grep -Fxq '*.cer' ./.gitignore
|
||||
then
|
||||
echo "# .CER Certificates" >> .gitignore
|
||||
echo "*.cer" >> .gitignore
|
||||
fi
|
||||
|
||||
## add *.pem to .gitignore to prevent certificates from being saved in repo
|
||||
if ! grep -Fxq '*.pem' ./.gitignore
|
||||
then
|
||||
echo "# .PEM Certificates" >> .gitignore
|
||||
echo "*.pem" >> .gitignore
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -e "\e[1;92mReady!\e[0m"
|
||||
|
||||
echo -e "\n\e[1;94m**********\nOptional: if you plan on using gulp serve, don't forget to add the container certificate to your local machine. Please visit https://aka.ms/spfx-devcontainer for more information\n**********"
|
|
@ -1,4 +1,5 @@
|
|||
# SP Site ER Diagram
|
||||
|
||||
## Summary
|
||||
|
||||
This web part loads all lists on a site and display it in a Entity Relationship Diagram (ERD) using [GoJS](https://www.npmjs.com/package/gojs).
|
||||
|
@ -7,6 +8,7 @@ This web part loads all lists on a site and display it in a Entity Relationship
|
|||
![ER Webpart in fullscreen](assets/SPERasAppPageFullScreen.png)
|
||||
|
||||
## Compatibility
|
||||
|
||||
![SPFx 1.14](https://img.shields.io/badge/SPFx-1.14-green.svg)
|
||||
![Node.js v14 | v12](https://img.shields.io/badge/Node.js-v14%20%7C%20v12-green.svg)
|
||||
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
|
||||
|
@ -23,15 +25,7 @@ This web part loads all lists on a site and display it in a Entity Relationship
|
|||
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram)
|
||||
|
||||
## Solution
|
||||
<!--
|
||||
We use this section to recognize and promote your contributions. Please provide one author per line -- even if you worked together on it.
|
||||
|
||||
We'll only use the info you provided here. Make sure to include your full name, not just your GitHub username.
|
||||
|
||||
Provide a link to your GitHub profile to help others find more cool things you have done.
|
||||
|
||||
If you provide a link to your Twitter profile, we'll promote your contribution on social media.
|
||||
-->
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
|
@ -44,6 +38,7 @@ Version|Date|Comments
|
|||
1.0|October 07, 2022|Initial release
|
||||
|
||||
## Minimal path to awesome
|
||||
|
||||
* Clone this repository (or [download this solution as a .ZIP file](https://pnp.github.io/download-partial/?url=https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-pnpjs-spsite-er-diagram) then unzip it)
|
||||
* From your command line, change your current directory to the directory containing this sample (`react-pnpjs-spsite-er-diagram`, located under `samples`)
|
||||
* in the command line run:
|
||||
|
@ -54,7 +49,7 @@ Version|Date|Comments
|
|||
|
||||
## Features
|
||||
|
||||
This project can be used as a starting point for any visualisation of SharePoint Data. Currently it's using GoJS as dependency for the ER Diagram (in productive enviroment you would need to get a license for it). The data layer is abstract so it's possible to use a different library (like three.js) as presentation layer.
|
||||
This project can be used as a starting point for any visualization of SharePoint Data. Currently it's using GoJS as dependency for the ER Diagram (in productive environment you would need to get a license for it). The data layer is abstract so it's possible to use a different library (like three.js) as presentation layer.
|
||||
|
||||
* the Data gets cached, to see changes made to the lists/lookups a "refresh" is needed
|
||||
* alerts/warnings for the lists are displayed as well (Versioning/ItemCount/Threshholdlimit/..)
|
||||
|
@ -62,41 +57,9 @@ This project can be used as a starting point for any visualisation of SharePoint
|
|||
* easy switch between Internal-/DisplayName
|
||||
* Download current canvas as image
|
||||
|
||||
<!--
|
||||
Note that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions in advance! You rock ❤.
|
||||
-->
|
||||
|
||||
<!--
|
||||
RESERVED FOR REPO MAINTAINERS
|
||||
|
||||
We'll add the video from the community call recording here
|
||||
|
||||
## Video
|
||||
|
||||
[![YouTube video title](./assets/video-thumbnail.jpg)](https://www.youtube.com/watch?v=XXXXX "YouTube video title")
|
||||
-->
|
||||
|
||||
## Help
|
||||
|
||||
<!--
|
||||
You can just search and replace this page with the following values:
|
||||
|
||||
Search for:
|
||||
react-pnpjs-spsite-er-diagram
|
||||
|
||||
Replace with your sample folder name. E.g.: react-my-cool-sample
|
||||
|
||||
Search for:
|
||||
@YOURGITHUBUSERNAME
|
||||
|
||||
Replace with your GitHub username, prefixed with an "@". If you have more than one author, use %20 to separate them, making sure to prefix everyone's username individually with an "@".
|
||||
|
||||
Example:
|
||||
@hugoabernier
|
||||
|
||||
Or:
|
||||
@hugoabernier%20@VesaJuvonen%20@PopWarner
|
||||
-->
|
||||
|
||||
We do not support samples, but 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.
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
[
|
||||
{
|
||||
"name": "pnp-sp-dev-spfx-web-parts-react-pnpjs-spsite-er-diagram",
|
||||
"source": "pnp",
|
||||
"title": "SP Site ER Diagram",
|
||||
"shortDescription": "This web part loads all lists on a site and display it in a Entity Relationship Diagram (ERD)",
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-pnpjs-spsite-er-diagram",
|
||||
"downloadUrl": "https://pnp.github.io/download-partial/?url=https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-pnpjs-spsite-er-diagram",
|
||||
"longDescription": [
|
||||
"This web part loads all lists on a site and display it in a Entity Relationship Diagram (ERD)"
|
||||
],
|
||||
"creationDateTime": "2022-11-07",
|
||||
"updateDateTime": "2022-11-07",
|
||||
"products": [
|
||||
"SharePoint"
|
||||
],
|
||||
"metadata": [
|
||||
{
|
||||
"key": "CLIENT-SIDE-DEV",
|
||||
"value": "React"
|
||||
},
|
||||
{
|
||||
"key": "SPFX-VERSION",
|
||||
"value": "1.14"
|
||||
}
|
||||
],
|
||||
"thumbnails": [
|
||||
{
|
||||
"type": "image",
|
||||
"order": 100,
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-pnpjs-spsite-er-diagram/assets/YOUR-IMAGE-NAME-HERE",
|
||||
"alt": "Web Part Preview"
|
||||
}
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"gitHubAccount": "ICTNiklasWilhelm",
|
||||
"pictureUrl": "https://github.com/ICTNiklasWilhelm.png",
|
||||
"name": "Niklas Wilhelm"
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"name": "Build your first SharePoint client-side web part",
|
||||
"description": "Client-side web parts are client-side components that run in the context of a SharePoint page. Client-side web parts can be deployed to SharePoint environments that support the SharePoint Framework. You can also use modern JavaScript web frameworks, tools, and libraries to build them.",
|
||||
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -23,16 +23,16 @@ const SpSiteErDiagram: React.FC<ISpSiteDiagramProps> = (props: ISpSiteDiagramPro
|
|||
const [alertsActive, setAlertsActive] = React.useState(true);
|
||||
const [fieldsActive, setFieldsActive] = React.useState(true);
|
||||
|
||||
const loadDiagram = async (refresh: boolean) => {
|
||||
const loadDiagram = async (refresh: boolean):Promise<void> => {
|
||||
if(refresh) { setLoadingProgress(0); setNodeDataArray([]); }
|
||||
// Get SP SiteData for ER Diagram
|
||||
let spSiteData = await getSPSiteData(props.context, refresh, (progress) => {setLoadingProgress(progress);});
|
||||
const spSiteData = await getSPSiteData(props.context, refresh, (progress) => {setLoadingProgress(progress);});
|
||||
console.log("SPSiteData", spSiteData);
|
||||
// Transform to GoJS Model
|
||||
let goJSNodes = getGoJSNodesFromSPSiteData(spSiteData, useInternalName ? "name" : "displayName", alertsActive, fieldsActive);
|
||||
const goJSNodes = getGoJSNodesFromSPSiteData(spSiteData, useInternalName ? "name" : "displayName", alertsActive, fieldsActive);
|
||||
// Set State
|
||||
setNodeDataArray(goJSNodes.nodeDataArray.filter((n) =>
|
||||
optionRelationOnly && goJSNodes.linkDataArray.some(l => l.from == n.key || l.to == n.key) || !optionRelationOnly // Filter optionRelationOnly
|
||||
optionRelationOnly && goJSNodes.linkDataArray.some(l => l.from === n.key || l.to === n.key) || !optionRelationOnly // Filter optionRelationOnly
|
||||
));
|
||||
setLinkDataArray(goJSNodes.linkDataArray);
|
||||
}
|
||||
|
@ -42,11 +42,11 @@ const SpSiteErDiagram: React.FC<ISpSiteDiagramProps> = (props: ISpSiteDiagramPro
|
|||
loadDiagram(false);
|
||||
}, [optionRelationOnly, useInternalName, alertsActive, fieldsActive]);
|
||||
|
||||
const downloadAsImage = () => {
|
||||
const downloadAsImage = ():void => {
|
||||
if(diagramRef && diagramRef.current) {
|
||||
let canvas = (diagramRef.current as any).divRef.current.firstChild;
|
||||
const canvas = (diagramRef.current as any).divRef.current.firstChild;
|
||||
console.log((diagramRef.current as any).divRef.current);
|
||||
var link = document.createElement('a');
|
||||
const link = document.createElement('a');
|
||||
link.download = props.context.pageContext.web.title + '_ERDiagram.png';
|
||||
link.href = canvas.toDataURL()
|
||||
link.click();
|
||||
|
@ -64,7 +64,7 @@ const SpSiteErDiagram: React.FC<ISpSiteDiagramProps> = (props: ISpSiteDiagramPro
|
|||
{key: '4', text: "Download as image", iconProps: { iconName: 'Share' }, onClick: () => { downloadAsImage() }}
|
||||
]} />
|
||||
<div className={styles.spSiteErDiagram} style={{height: "calc(100% - 44px)", padding: "0px"}}>
|
||||
{ loadingProgress != 100 && nodeDataArray.length == 0 ?
|
||||
{ loadingProgress !== 100 && nodeDataArray.length === 0 ?
|
||||
<div style={{ padding: "8%" }}>
|
||||
<ProgressIndicator label={`Loading Lists and Columns ${loadingProgress.toFixed(0)}%`} percentComplete={loadingProgress/100} />
|
||||
</div> :
|
||||
|
|
|
@ -44,19 +44,19 @@ const getSPSiteData = async (spfxContext: any, force?: boolean, progress?: (numb
|
|||
}
|
||||
|
||||
// Load from site
|
||||
let spSiteData: SPSiteData = {
|
||||
const spSiteData: SPSiteData = {
|
||||
relations: [],
|
||||
tables: []
|
||||
}
|
||||
let tmp_listNames: any = {};
|
||||
const tmp_listNames: any = {};
|
||||
|
||||
const sp = spfi().using(SPFx(spfxContext));
|
||||
let lists = await sp.web.lists.filter("Hidden ne 1")();
|
||||
const lists = await sp.web.lists.filter("Hidden ne 1")();
|
||||
|
||||
const totalCount = lists.filter(l => !l.Hidden).length;
|
||||
let loadedCount = 0;
|
||||
|
||||
for(let list of lists) {
|
||||
for(const list of lists) {
|
||||
if(!list.Hidden) {
|
||||
loadedCount++;
|
||||
progress && progress(loadedCount/totalCount * 100);
|
||||
|
@ -65,11 +65,11 @@ const getSPSiteData = async (spfxContext: any, force?: boolean, progress?: (numb
|
|||
tmp_listNames[`{${list.Id.toLocaleLowerCase()}}`] = list.Title;
|
||||
|
||||
// Tables/Lists
|
||||
let table: SPTable = { id: list.Id, title: list.Title, fields: [], alerts: [] };
|
||||
const table: SPTable = { id: list.Id, title: list.Title, fields: [], alerts: [] };
|
||||
// Fields
|
||||
let fields = (await sp.web.lists.getById(list.Id).fields.filter("Hidden ne 1")())
|
||||
.filter(f => !f.Hidden && (f as any).LookupList != "AppPrincipals" &&
|
||||
((f as any).CanBeDeleted || (f as any).InternalName == "Title" || (f as any).InternalName == "Id")
|
||||
const fields = (await sp.web.lists.getById(list.Id).fields.filter("Hidden ne 1")())
|
||||
.filter(f => !f.Hidden && (f as any).LookupList !== "AppPrincipals" &&
|
||||
((f as any).CanBeDeleted || (f as any).InternalName === "Title" || (f as any).InternalName === "Id")
|
||||
)
|
||||
//.sort((a,b) => a.InternalName.charCodeAt(0) - b.InternalName.charCodeAt(0) );
|
||||
table.fields = fields.map(f => {
|
||||
|
@ -77,7 +77,7 @@ const getSPSiteData = async (spfxContext: any, force?: boolean, progress?: (numb
|
|||
return {
|
||||
name: f.InternalName,
|
||||
displayName: f.Title,
|
||||
iskey: (f as any).TypeDisplayName == "Lookup" && (f as any).IsRelationship && (f as any).LookupList != '' && (f as any).LookupList != "AppPrincipals",
|
||||
iskey: (f as any).TypeDisplayName === "Lookup" && (f as any).IsRelationship && (f as any).LookupList !== '' && (f as any).LookupList !== "AppPrincipals",
|
||||
isunique: f.EnforceUniqueValues,
|
||||
type: f.TypeDisplayName
|
||||
}
|
||||
|
@ -97,9 +97,9 @@ const getSPSiteData = async (spfxContext: any, force?: boolean, progress?: (numb
|
|||
spSiteData.tables.push(table);
|
||||
|
||||
// Links/Lookups
|
||||
let relations: SPRelation[] = fields.filter(f => f.TypeDisplayName == "Lookup" &&
|
||||
const relations: SPRelation[] = fields.filter(f => f.TypeDisplayName === "Lookup" &&
|
||||
(f as any).IsRelationship &&
|
||||
(f as any).LookupList != '' && (f as any).LookupList != "AppPrincipals"
|
||||
(f as any).LookupList !== '' && (f as any).LookupList !== "AppPrincipals"
|
||||
).map<SPRelation>(f =>
|
||||
{
|
||||
return {
|
||||
|
|
|
@ -30,26 +30,26 @@ const configByFieldType: any = {
|
|||
"Hyperlink or Picture": { color: colors.blue, figure: "Circle" }
|
||||
}
|
||||
const getNodeItemFromField = (f: SPTableField, fieldNameProperty: string = "name") : GoJSNodeItem => {
|
||||
let c = configByFieldType[f.type] || configByFieldType['default'];
|
||||
let prefix = f.type == "Counter" ? "PK | " : (f.iskey && f.type == "Lookup" ? "FK | " : "");
|
||||
const c = configByFieldType[f.type] || configByFieldType['default'];
|
||||
const prefix = f.type === "Counter" ? "PK | " : (f.iskey && f.type === "Lookup" ? "FK | " : "");
|
||||
return {
|
||||
name: prefix + (f as any)[fieldNameProperty] + ` (${f.type})`,
|
||||
iskey: f.iskey,
|
||||
figure: c.figure,
|
||||
color: f.iskey ? colors.keycolor : c.color,
|
||||
order: f.type == "Counter" ? "1" :
|
||||
f.type == "Lookup" && f.iskey ? "2" :
|
||||
f.type == "Lookup" ? "3" :
|
||||
order: f.type === "Counter" ? "1" :
|
||||
f.type === "Lookup" && f.iskey ? "2" :
|
||||
f.type === "Lookup" ? "3" :
|
||||
f.type
|
||||
};
|
||||
}
|
||||
const configByAlert: any = {
|
||||
const configByAlert = {
|
||||
'Info': { color: colors.lightblue, figure: "Rectangle" },
|
||||
'Warning': { color: colors.orange, figure: "Rectangle" },
|
||||
'Error': { color: colors.red, figure: "Rectangle" },
|
||||
}
|
||||
const getNodeItemFromAlert = (a: SPTableAlert) : GoJSNodeItem => {
|
||||
let c = configByAlert[a.type];
|
||||
const c = configByAlert[a.type];
|
||||
return {
|
||||
name: "#" + a.type + " | " + a.title,
|
||||
iskey: false,
|
||||
|
|
Loading…
Reference in New Issue