New sample SPFx webhooks real time (#351)
* first commit * Implemented GetChanges API, Readme updated, New property "socket server url" * added some scripts in order to automate the provisioning and deployment, readme updated, minor bug fix
This commit is contained in:
parent
99ea9eefde
commit
cbcc2041d4
|
@ -0,0 +1,25 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Build generated files
|
||||||
|
dist
|
||||||
|
lib
|
||||||
|
solution
|
||||||
|
temp
|
||||||
|
*.sppkg
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# OSX
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Visual Studio files
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
.vs
|
||||||
|
bin
|
||||||
|
obj
|
||||||
|
|
||||||
|
# Resx Generated Code
|
||||||
|
*.resx.ts
|
||||||
|
|
||||||
|
# Styles Generated Code
|
||||||
|
*.scss.ts
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Place your settings in this file to overwrite default and user settings.
|
||||||
|
{
|
||||||
|
// Configure glob patterns for excluding files and folders in the file explorer.
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.DS_Store": true,
|
||||||
|
"**/bower_components": true,
|
||||||
|
"**/coverage": true,
|
||||||
|
"**/lib-amd": true,
|
||||||
|
"src/**/*.scss.ts": true
|
||||||
|
},
|
||||||
|
"typescript.tsdk": ".\\node_modules\\typescript\\lib",
|
||||||
|
"json.schemas": [
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"/config/config.json"
|
||||||
|
],
|
||||||
|
"url": "./node_modules/@microsoft/sp-build-core-tasks/lib/configJson/schemas/config-v1.schema.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"/config/copy-assets.json"
|
||||||
|
],
|
||||||
|
"url": "./node_modules/@microsoft/sp-build-core-tasks/lib/copyAssets/copy-assets.schema.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"/config/deploy-azure-storage.json"
|
||||||
|
],
|
||||||
|
"url": "./node_modules/@microsoft/sp-build-core-tasks/lib/deployAzureStorage/deploy-azure-storage.schema.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"/config/package-solution.json"
|
||||||
|
],
|
||||||
|
"url": "./node_modules/@microsoft/sp-build-core-tasks/lib/packageSolution/package-solution.schema.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"/config/serve.json"
|
||||||
|
],
|
||||||
|
"url": "./node_modules/@microsoft/gulp-core-build-serve/lib/serve.schema.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"/config/tslint.json"
|
||||||
|
],
|
||||||
|
"url": "./node_modules/@microsoft/gulp-core-build-typescript/lib/schemas/tslint.schema.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"/config/write-manifests.json"
|
||||||
|
],
|
||||||
|
"url": "./node_modules/@microsoft/sp-build-core-tasks/lib/writeManifests/write-manifests.schema.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"/config/configure-webpack.json"
|
||||||
|
],
|
||||||
|
"url": "./node_modules/@microsoft/sp-build-core-tasks/lib/configureWebpack/configure-webpack.schema.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"/config/configure-external-bundling-webpack.json"
|
||||||
|
],
|
||||||
|
"url": "./node_modules/@microsoft/sp-build-core-tasks/lib/configureWebpack/configure-webpack-external-bundling.schema.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fileMatch": [
|
||||||
|
"/copy-static-assets.json"
|
||||||
|
],
|
||||||
|
"url": "./node_modules/@microsoft/sp-build-core-tasks/lib/copyStaticAssets/copy-static-assets.schema.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"libraryName": "react-webhooks-realtime",
|
||||||
|
"libraryId": "d4eee588-a8e6-45ce-b9e1-c40f0b92ef2d",
|
||||||
|
"environment": "spo"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
#################
|
||||||
|
# Configuration #
|
||||||
|
#################
|
||||||
|
$catalogSite = "https://giuleon.sharepoint.com/sites/apps" # => App Catalog site
|
||||||
|
$catalogRelativePath = "sites/apps/AppCatalog" # => App Catalog relative url
|
||||||
|
#######
|
||||||
|
# End #
|
||||||
|
#######
|
||||||
|
|
||||||
|
# Get Web Request
|
||||||
|
function GetRequest ($apiUrl, $webSession) {
|
||||||
|
return Invoke-WebRequest -Uri $apiUrl -Method Get -WebSession $webSession
|
||||||
|
}
|
||||||
|
|
||||||
|
# Post Web Request
|
||||||
|
function PostRequest($apiUrl, $webSession, $body) {
|
||||||
|
return Invoke-WebRequest -Uri $apiUrl -Body $body -Method Post -WebSession $webSession
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Settting the right parameters value
|
||||||
|
function setXmlMapping($xmlBody, $siteId, $webId, $listId, $fileId, $fileVersion, $skipDeployment) {
|
||||||
|
# Replace the random token with a random guid
|
||||||
|
$randomGuid = [guid]::NewGuid()
|
||||||
|
if($skipDeployment -eq $True){
|
||||||
|
$skipDeployment = "true"
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$skipDeployment = "false"
|
||||||
|
}
|
||||||
|
$xmlBody = [regex]::replace($xmlBody, "{randomId}", $randomGuid)
|
||||||
|
# Replace the site ID token with the actual site ID string
|
||||||
|
$xmlBody = [regex]::replace($xmlBody, "{siteId}", $siteId)
|
||||||
|
# Replace the web ID token with the actual web ID string
|
||||||
|
$xmlBody = [regex]::replace($xmlBody, "{webId}", $webId)
|
||||||
|
# Replace the list ID token with the actual list ID string
|
||||||
|
$xmlBody = [regex]::replace($xmlBody, "{listId}", $listId)
|
||||||
|
# Replace the item ID token with the actual item ID number
|
||||||
|
$xmlBody = [regex]::replace($xmlBody, "{itemId}", $fileId)
|
||||||
|
# Replace the file version token with the actual file version number
|
||||||
|
$xmlBody = [regex]::replace($xmlBody, "{fileVersion}", $fileVersion)
|
||||||
|
# Replace the skipFeatureDeployment token with the skipFeatureDeployment option
|
||||||
|
$xmlBody = [regex]::replace($xmlBody, "{skipFeatureDeployment}", $skipDeployment)
|
||||||
|
return $xmlBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ***************************************** -ForegroundColor Yellow
|
||||||
|
Write-Host * Uploading the sppkg on the AppCatalog * -ForegroundColor Yellow
|
||||||
|
Write-Host ***************************************** -ForegroundColor Yellow
|
||||||
|
$packageConfig = Get-Content -Raw -Path .\config\package-solution.json | ConvertFrom-Json
|
||||||
|
$packagePath = Join-Path "sharepoint/" $packageConfig.paths.zippedPackage -Resolve
|
||||||
|
$skipFeatureDeployment = $packageConfig.solution.skipFeatureDeployment
|
||||||
|
|
||||||
|
# Connect-PnPOnline $catalogSite -Credentials (Get-Credential)
|
||||||
|
Connect-PnPOnline $catalogSite -Credentials giuleon
|
||||||
|
Add-PnPFile -Path $packagePath -Folder "AppCatalog"
|
||||||
|
|
||||||
|
Write-Host *************************************************** -ForegroundColor Yellow
|
||||||
|
Write-Host * The SPFx solution has been succesfully uploaded to the AppCatalog * -ForegroundColor Yellow
|
||||||
|
Write-Host *************************************************** -ForegroundColor Yellow
|
||||||
|
|
||||||
|
# Connect to SharePoint Online
|
||||||
|
$targetSite = "https://giuleon.sharepoint.com/sites/apps"
|
||||||
|
$targetSiteUri = [System.Uri]$targetSite
|
||||||
|
|
||||||
|
# Retrieve the client credentials and the related Authentication Cookies
|
||||||
|
$context = (Get-PnPWeb).Context
|
||||||
|
$credentials = $context.Credentials
|
||||||
|
$authenticationCookies = $credentials.GetAuthenticationCookie($targetSiteUri, $true)
|
||||||
|
|
||||||
|
# Set the Authentication Cookies and the Accept HTTP Header
|
||||||
|
$webSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
|
||||||
|
$webSession.Cookies.SetCookies($targetSiteUri, $authenticationCookies)
|
||||||
|
$webSession.Headers.Add("Accept", "application/json;odata=verbose")
|
||||||
|
|
||||||
|
$apiUrl = $catalogSite + "/_api/contextinfo?$"+"select=FormDigestValue"
|
||||||
|
$result = PostRequest -apiUrl $apiUrl -webSession $webSession
|
||||||
|
$formDigest = $result.Content | ConvertFrom-Json
|
||||||
|
Write-Host "FormDigestValue - " $formDigest.d.GetContextWebInformation.FormDigestValue
|
||||||
|
$formDigest = $formDigest.d.GetContextWebInformation.FormDigestValue
|
||||||
|
$webSession.Headers.Add("X-RequestDigest", $formDigest)
|
||||||
|
|
||||||
|
# Set request variables
|
||||||
|
$apiUrl = "$targetSite" + "/_api/site?$"+"select=Id"
|
||||||
|
|
||||||
|
# Make the REST request
|
||||||
|
$webRequest = GetRequest -apiUrl $apiUrl -webSession $webSession # Invoke-WebRequest -Uri $apiUrl -Method Get -WebSession $webSession
|
||||||
|
$response = $webRequest.Content | ConvertFrom-Json
|
||||||
|
$siteId = $response.d.Id
|
||||||
|
Write-Host "Site Id - " $response.d.Id
|
||||||
|
|
||||||
|
# Retrieving webId and listId
|
||||||
|
$apiUrl = "$targetSite" + "/_api/web/getList('$catalogRelativePath')?$"+"select=Id,ParentWeb/Id`&`$"+"expand=ParentWeb"
|
||||||
|
$webRequest = GetRequest -apiUrl $apiUrl -webSession $webSession # Invoke-WebRequest -Uri $apiUrl -Method Get -WebSession $webSession
|
||||||
|
$response = $webRequest.Content | ConvertFrom-Json
|
||||||
|
$webId = $response.d.ParentWeb.Id
|
||||||
|
$listId = $response.d.Id
|
||||||
|
Write-Host "Web Id - " $webId " / List Id - " + $listId
|
||||||
|
|
||||||
|
# Get the ListItemAllFields Id and Version
|
||||||
|
$fileName = $packageConfig.paths.zippedPackage.Substring($packageConfig.paths.zippedPackage.LastIndexOf("/")+1)
|
||||||
|
$apiUrl = "$targetSite" + "/_api/web/GetFolderByServerRelativeUrl('AppCatalog')/Files('$fileName')?$"+"expand=ListItemAllFields`&`$" + "select=ListItemAllFields/ID,ListItemAllFields/owshiddenversion"
|
||||||
|
$webRequest = GetRequest -apiUrl $apiUrl -webSession $webSession
|
||||||
|
$response = $webRequest.Content -replace '"id":', '"id_":' | ConvertFrom-Json
|
||||||
|
$fileId = $response.d.ListItemAllFields.id_
|
||||||
|
$fileVersion = $response.d.ListItemAllFields.owshiddenversion
|
||||||
|
Write-Host "ListItem Id - " $fileId " / Version - " $fileVersion
|
||||||
|
|
||||||
|
# Read the xml
|
||||||
|
$xmlBody = Get-Content DeploySPFxToAppCatalogRequestBody.xml -Encoding UTF8
|
||||||
|
$xmlBody = setXmlMapping -xmlBody $xmlBody -siteId $siteId -webId $webId -listId $listId -fileId $fileId -fileVersion $fileVersion -skipDeployment $skipFeatureDeployment
|
||||||
|
Write-Host "deployment in progress....."
|
||||||
|
|
||||||
|
# Deploy the sspkg
|
||||||
|
$webSession.Headers.Add("Content-type", "application/xml")
|
||||||
|
$apiUrl = $catalogSite + "/_vti_bin/client.svc/ProcessQuery"
|
||||||
|
$result = PostRequest -apiUrl $apiUrl -webSession $webSession -body $xmlBody
|
||||||
|
Write-Host $result
|
|
@ -0,0 +1,148 @@
|
||||||
|
<Request
|
||||||
|
xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="Javascript Library">
|
||||||
|
<Actions>
|
||||||
|
<Method Name="SetFieldValue" Id="18" ObjectPathId="10" Version="{fileVersion}">
|
||||||
|
<Parameters>
|
||||||
|
<Parameter Type="String">IsClientSideSolutionDeployed</Parameter>
|
||||||
|
<Parameter Type="Boolean">true</Parameter>
|
||||||
|
</Parameters>
|
||||||
|
</Method>
|
||||||
|
<Method Name="SetFieldValue" Id="19" ObjectPathId="10" Version="{fileVersion}">
|
||||||
|
<Parameters>
|
||||||
|
<Parameter Type="String">IsClientSideSolutionCurrentVersionDeployed</Parameter>
|
||||||
|
<Parameter Type="Boolean">true</Parameter>
|
||||||
|
</Parameters>
|
||||||
|
</Method>
|
||||||
|
<Method Name="SetFieldValue" Id="20" ObjectPathId="10" Version="{fileVersion}">
|
||||||
|
<Parameters>
|
||||||
|
<Parameter Type="String">SkipFeatureDeployment</Parameter>
|
||||||
|
<Parameter Type="Boolean">{skipFeatureDeployment}</Parameter>
|
||||||
|
</Parameters>
|
||||||
|
</Method>
|
||||||
|
<Method Name="Update" Id="21" ObjectPathId="10" Version="{fileVersion}" />
|
||||||
|
<Query Id="22" ObjectPathId="10">
|
||||||
|
<Query SelectAllProperties="false">
|
||||||
|
<Properties>
|
||||||
|
<Property Name="FileSystemObjectType" ScalarProperty="true" />
|
||||||
|
<Property Name="Id" ScalarProperty="true" />
|
||||||
|
<Property Name="ServerRedirectedEmbedUri" ScalarProperty="true" />
|
||||||
|
<Property Name="ServerRedirectedEmbedUrl" ScalarProperty="true" />
|
||||||
|
<Property Name="ID" ScalarProperty="true" />
|
||||||
|
<Property Name="ContentTypeId" ScalarProperty="true" />
|
||||||
|
<Property Name="Created" ScalarProperty="true" />
|
||||||
|
<Property Name="Author" ScalarProperty="true" />
|
||||||
|
<Property Name="Modified" ScalarProperty="true" />
|
||||||
|
<Property Name="Editor" ScalarProperty="true" />
|
||||||
|
<Property Name="_HasCopyDestinations" ScalarProperty="true" />
|
||||||
|
<Property Name="_CopySource" ScalarProperty="true" />
|
||||||
|
<Property Name="_ModerationStatus" ScalarProperty="true" />
|
||||||
|
<Property Name="_ModerationComments" ScalarProperty="true" />
|
||||||
|
<Property Name="FileRef" ScalarProperty="true" />
|
||||||
|
<Property Name="FileDirRef" ScalarProperty="true" />
|
||||||
|
<Property Name="Last_x0020_Modified" ScalarProperty="true" />
|
||||||
|
<Property Name="Created_x0020_Date" ScalarProperty="true" />
|
||||||
|
<Property Name="File_x0020_Size" ScalarProperty="true" />
|
||||||
|
<Property Name="FSObjType" ScalarProperty="true" />
|
||||||
|
<Property Name="SortBehavior" ScalarProperty="true" />
|
||||||
|
<Property Name="CheckedOutUserId" ScalarProperty="true" />
|
||||||
|
<Property Name="IsCheckedoutToLocal" ScalarProperty="true" />
|
||||||
|
<Property Name="CheckoutUser" ScalarProperty="true" />
|
||||||
|
<Property Name="FileLeafRef" ScalarProperty="true" />
|
||||||
|
<Property Name="UniqueId" ScalarProperty="true" />
|
||||||
|
<Property Name="SyncClientId" ScalarProperty="true" />
|
||||||
|
<Property Name="ProgId" ScalarProperty="true" />
|
||||||
|
<Property Name="ScopeId" ScalarProperty="true" />
|
||||||
|
<Property Name="VirusStatus" ScalarProperty="true" />
|
||||||
|
<Property Name="CheckedOutTitle" ScalarProperty="true" />
|
||||||
|
<Property Name="_CheckinComment" ScalarProperty="true" />
|
||||||
|
<Property Name="Modified_x0020_By" ScalarProperty="true" />
|
||||||
|
<Property Name="Created_x0020_By" ScalarProperty="true" />
|
||||||
|
<Property Name="File_x0020_Type" ScalarProperty="true" />
|
||||||
|
<Property Name="HTML_x0020_File_x0020_Type" ScalarProperty="true" />
|
||||||
|
<Property Name="_SourceUrl" ScalarProperty="true" />
|
||||||
|
<Property Name="_SharedFileIndex" ScalarProperty="true" />
|
||||||
|
<Property Name="MetaInfo" ScalarProperty="true" />
|
||||||
|
<Property Name="_Level" ScalarProperty="true" />
|
||||||
|
<Property Name="_IsCurrentVersion" ScalarProperty="true" />
|
||||||
|
<Property Name="ItemChildCount" ScalarProperty="true" />
|
||||||
|
<Property Name="FolderChildCount" ScalarProperty="true" />
|
||||||
|
<Property Name="Restricted" ScalarProperty="true" />
|
||||||
|
<Property Name="OriginatorId" ScalarProperty="true" />
|
||||||
|
<Property Name="NoExecute" ScalarProperty="true" />
|
||||||
|
<Property Name="ContentVersion" ScalarProperty="true" />
|
||||||
|
<Property Name="_ComplianceFlags" ScalarProperty="true" />
|
||||||
|
<Property Name="_ComplianceTag" ScalarProperty="true" />
|
||||||
|
<Property Name="_ComplianceTagWrittenTime" ScalarProperty="true" />
|
||||||
|
<Property Name="_ComplianceTagUserId" ScalarProperty="true" />
|
||||||
|
<Property Name="BSN" ScalarProperty="true" />
|
||||||
|
<Property Name="_ListSchemaVersion" ScalarProperty="true" />
|
||||||
|
<Property Name="_Dirty" ScalarProperty="true" />
|
||||||
|
<Property Name="_Parsable" ScalarProperty="true" />
|
||||||
|
<Property Name="AccessPolicy" ScalarProperty="true" />
|
||||||
|
<Property Name="_VirusStatus" ScalarProperty="true" />
|
||||||
|
<Property Name="_VirusVendorID" ScalarProperty="true" />
|
||||||
|
<Property Name="_VirusInfo" ScalarProperty="true" />
|
||||||
|
<Property Name="AppAuthor" ScalarProperty="true" />
|
||||||
|
<Property Name="AppEditor" ScalarProperty="true" />
|
||||||
|
<Property Name="SMTotalSize" ScalarProperty="true" />
|
||||||
|
<Property Name="SMLastModifiedDate" ScalarProperty="true" />
|
||||||
|
<Property Name="SMTotalFileStreamSize" ScalarProperty="true" />
|
||||||
|
<Property Name="SMTotalFileCount" ScalarProperty="true" />
|
||||||
|
<Property Name="owshiddenversion" ScalarProperty="true" />
|
||||||
|
<Property Name="_UIVersion" ScalarProperty="true" />
|
||||||
|
<Property Name="_UIVersionString" ScalarProperty="true" />
|
||||||
|
<Property Name="InstanceID" ScalarProperty="true" />
|
||||||
|
<Property Name="Order" ScalarProperty="true" />
|
||||||
|
<Property Name="GUID" ScalarProperty="true" />
|
||||||
|
<Property Name="WorkflowVersion" ScalarProperty="true" />
|
||||||
|
<Property Name="WorkflowInstanceID" ScalarProperty="true" />
|
||||||
|
<Property Name="ParentVersionString" ScalarProperty="true" />
|
||||||
|
<Property Name="ParentLeafName" ScalarProperty="true" />
|
||||||
|
<Property Name="DocConcurrencyNumber" ScalarProperty="true" />
|
||||||
|
<Property Name="ParentUniqueId" ScalarProperty="true" />
|
||||||
|
<Property Name="StreamHash" ScalarProperty="true" />
|
||||||
|
<Property Name="ComplianceAssetId" ScalarProperty="true" />
|
||||||
|
<Property Name="Title" ScalarProperty="true" />
|
||||||
|
<Property Name="AppProductID" ScalarProperty="true" />
|
||||||
|
<Property Name="AssetID" ScalarProperty="true" />
|
||||||
|
<Property Name="AppPublisher" ScalarProperty="true" />
|
||||||
|
<Property Name="AppThumbnailURL" ScalarProperty="true" />
|
||||||
|
<Property Name="AppDescription" ScalarProperty="true" />
|
||||||
|
<Property Name="AppShortDescription" ScalarProperty="true" />
|
||||||
|
<Property Name="AppVersion" ScalarProperty="true" />
|
||||||
|
<Property Name="AppSupportURL" ScalarProperty="true" />
|
||||||
|
<Property Name="AppVideoURL" ScalarProperty="true" />
|
||||||
|
<Property Name="IsAppPackageEnabled" ScalarProperty="true" />
|
||||||
|
<Property Name="SharePointAppCategory" ScalarProperty="true" />
|
||||||
|
<Property Name="AppPrerequisitesXML" ScalarProperty="true" />
|
||||||
|
<Property Name="IsFeaturedApp" ScalarProperty="true" />
|
||||||
|
<Property Name="AppMetadataLocale" ScalarProperty="true" />
|
||||||
|
<Property Name="IsDefaultAppMetadataLocale" ScalarProperty="true" />
|
||||||
|
<Property Name="AppImageURL1" ScalarProperty="true" />
|
||||||
|
<Property Name="AppImageURL2" ScalarProperty="true" />
|
||||||
|
<Property Name="AppImageURL3" ScalarProperty="true" />
|
||||||
|
<Property Name="AppImageURL4" ScalarProperty="true" />
|
||||||
|
<Property Name="AppImageURL5" ScalarProperty="true" />
|
||||||
|
<Property Name="AppPackageHash" ScalarProperty="true" />
|
||||||
|
<Property Name="IsValidAppPackage" ScalarProperty="true" />
|
||||||
|
<Property Name="AppPackageErrorMessage" ScalarProperty="true" />
|
||||||
|
<Property Name="AppPermissionXML" ScalarProperty="true" />
|
||||||
|
<Property Name="AppSubtypeID" ScalarProperty="true" />
|
||||||
|
<Property Name="IsAutoHostedApp" ScalarProperty="true" />
|
||||||
|
<Property Name="AppTitleInfo" ScalarProperty="true" />
|
||||||
|
<Property Name="AppSupportedLocales" ScalarProperty="true" />
|
||||||
|
<Property Name="IsClientSideSolution" ScalarProperty="true" />
|
||||||
|
<Property Name="IsClientSideSolutionDeployed" ScalarProperty="true" />
|
||||||
|
<Property Name="ExternalContentDomains" ScalarProperty="true" />
|
||||||
|
<Property Name="IsClientSideSolutionCurrentVersionDeployed" ScalarProperty="true" />
|
||||||
|
<Property Name="SkipFeatureDeployment" ScalarProperty="true" />
|
||||||
|
<Property Name="PackageDefaultSkipFeatureDeployment" ScalarProperty="true" />
|
||||||
|
<Property Name="UniqueSolutionId" ScalarProperty="true" />
|
||||||
|
</Properties>
|
||||||
|
</Query>
|
||||||
|
</Query>
|
||||||
|
</Actions>
|
||||||
|
<ObjectPaths>
|
||||||
|
<Identity Id="10" Name="{randomId}|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:{siteId}:web:{webId}:list:{listId}:item:{itemId},1" />
|
||||||
|
</ObjectPaths>
|
||||||
|
</Request>
|
|
@ -0,0 +1,24 @@
|
||||||
|
#################
|
||||||
|
# Configuration #
|
||||||
|
#################
|
||||||
|
$cdnSite = "https://giuleon.sharepoint.com/" # => CDN SharePoint site
|
||||||
|
$cdnLib = "cdn/SPFx-react-webhooks-realtime" # => Document library and eventual folders
|
||||||
|
#######
|
||||||
|
# End #
|
||||||
|
#######
|
||||||
|
|
||||||
|
Write-Host ************************************************************************************** -ForegroundColor Yellow
|
||||||
|
Write-Host * Reading the cdnBasePath from write-manifests.json and collectiong the bundle files * -ForegroundColor Yellow
|
||||||
|
Write-Host ************************************************************************************** -ForegroundColor Yellow
|
||||||
|
$cdnConfig = Get-Content -Raw -Path .\config\copy-assets.json | ConvertFrom-Json
|
||||||
|
$bundlePath = Convert-Path $cdnConfig.deployCdnPath
|
||||||
|
$files = Get-ChildItem $bundlePath\*.*
|
||||||
|
|
||||||
|
Write-Host **************************************** -ForegroundColor Yellow
|
||||||
|
Write-Host Uploading the bundle on Office 365 CDN * -ForegroundColor Yellow
|
||||||
|
Write-Host **************************************** -ForegroundColor Yellow
|
||||||
|
Connect-PnPOnline $cdnSite -Credentials giuleon
|
||||||
|
foreach ($file in $files) {
|
||||||
|
$fullPath = $file.DirectoryName + "\" + $file.Name
|
||||||
|
Add-PnPFile -Path $fullPath -Folder $cdnLib
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
$spSite = "https://giuleon.sharepoint.com/sites/demo" # => SharePoint site
|
||||||
|
$spListTitle = "Events" # => List name
|
||||||
|
|
||||||
|
Connect-PnPOnline $spSite -Credentials (Get-Credential)
|
||||||
|
|
||||||
|
New-PnPList -Title $spListTitle -Template GenericList
|
||||||
|
|
||||||
|
Add-PnPField -List $spListTitle -DisplayName "Description" -InternalName "SPFxDescription" -Type Text -Group "SPFx Group" -AddToDefaultView
|
||||||
|
Add-PnPField -List $spListTitle -DisplayName "Thumbnail" -InternalName "SPFxThumbnail" -Type Url -Group "SPFx Group" -AddToDefaultView
|
|
@ -0,0 +1,58 @@
|
||||||
|
# Webhooks Realtime
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
This web part demonstrates how to leverage the capabilities of SharePoint Webhooks.
|
||||||
|
The libraries used by this web part are Socket.io, sp pnp js, moment.
|
||||||
|
|
||||||
|
![Preview](./assets/spfx-react-webhooks-realtime.gif)
|
||||||
|
|
||||||
|
### Solution architecture
|
||||||
|
![Architecture](./assets/Architecture.png)
|
||||||
|
|
||||||
|
## Used SharePoint Framework Version
|
||||||
|
![drop](https://img.shields.io/badge/version-GA-green.svg)
|
||||||
|
|
||||||
|
## Applies to
|
||||||
|
|
||||||
|
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||||
|
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
> In order to use properly this web part is necessary follow these steps:
|
||||||
|
> * Istall a webserver that will receive the webhooks, for this PoC I created a NodeJs Application hosted on Azure take a look on my solution [https://github.com/giuleon/SharePoint-Webhooks-Broadcaster](https://github.com/giuleon/SharePoint-Webhooks-Broadcaster)
|
||||||
|
> * run the Powershell script **ProvisioningArtifacts.ps1** in order to provision the list Events which is required for this web part
|
||||||
|
> * Create a new webhooks subscription for the SharePoint List **Events** (that will be installed by running the script **ProvisioningArtifacts.ps1**), as you prefer, across your solution or Postman, please read the following guideline to achieve this goal [https://docs.microsoft.com/en-us/sharepoint/dev/apis/webhooks/overview-sharepoint-webhooks](https://docs.microsoft.com/en-us/sharepoint/dev/apis/webhooks/overview-sharepoint-webhooks)
|
||||||
|
> * The web part has been developed (GetChanges API) to notify new items added in the **Events** list
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
react-webhooks-realtime|Giuliano De Luca ([@giuleon](https://twitter.com/giuleon) , [www.delucagiuliano.com](delucagiuliano.com))
|
||||||
|
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0|October 29, 2017|Initial release
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal Path to Awesome
|
||||||
|
|
||||||
|
- Clone this repository
|
||||||
|
- in the command line run:
|
||||||
|
- `npm install`
|
||||||
|
- `gulp serve`
|
||||||
|
|
||||||
|
## Features
|
||||||
|
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||||
|
|
||||||
|
- How to leverage the capabilities of SharePoint webhooks.
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-webhooks-realtime" />
|
Binary file not shown.
After Width: | Height: | Size: 88 KiB |
Binary file not shown.
After Width: | Height: | Size: 687 KiB |
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"real-time-list-web-part": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/webparts/realTimeList/RealTimeListWebPart.js",
|
||||||
|
"manifest": "./src/webparts/realTimeList/RealTimeListWebPart.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {
|
||||||
|
"sp-pnp-js": "https://cdnjs.cloudflare.com/ajax/libs/sp-pnp-js/3.0.1/pnp.min.js"
|
||||||
|
},
|
||||||
|
"localizedResources": {
|
||||||
|
"RealTimeListWebPartStrings": "lib/webparts/realTimeList/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||||
|
"workingDir": "./temp/deploy/",
|
||||||
|
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||||
|
"container": "react-webhooks-realtime",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "react-webhooks-realtime-client-side-solution",
|
||||||
|
"id": "d4eee588-a8e6-45ce-b9e1-c40f0b92ef2d",
|
||||||
|
"version": "1.0.0.0",
|
||||||
|
"skipFeatureDeployment": true
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/react-webhooks-realtime.sppkg"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.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/"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
|
||||||
|
// Display errors as warnings
|
||||||
|
"displayAsWarning": true,
|
||||||
|
// The TSLint task may have been configured with several custom lint rules
|
||||||
|
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
|
||||||
|
// project). If true, this flag will deactivate any of these rules.
|
||||||
|
"removeExistingRules": true,
|
||||||
|
// When true, the TSLint task is configured with some default TSLint "rules.":
|
||||||
|
"useDefaultConfigAsBase": false,
|
||||||
|
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
|
||||||
|
// which are active, other than the list of rules below.
|
||||||
|
"lintConfig": {
|
||||||
|
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
|
||||||
|
"rules": {
|
||||||
|
"class-name": false,
|
||||||
|
"export-name": false,
|
||||||
|
"forin": false,
|
||||||
|
"label-position": false,
|
||||||
|
"member-access": true,
|
||||||
|
"no-arg": false,
|
||||||
|
"no-console": false,
|
||||||
|
"no-construct": false,
|
||||||
|
"no-duplicate-case": true,
|
||||||
|
"no-duplicate-variable": true,
|
||||||
|
"no-eval": false,
|
||||||
|
"no-function-expression": true,
|
||||||
|
"no-internal-module": true,
|
||||||
|
"no-shadowed-variable": true,
|
||||||
|
"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,
|
||||||
|
"typedef": false,
|
||||||
|
"typedef-whitespace": false,
|
||||||
|
"use-named-parameter": true,
|
||||||
|
"valid-typeof": true,
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||||
|
"cdnBasePath": "https://publiccdn.sharepointonline.com/giuleon.sharepoint.com/cdn/SPFx-react-webhooks-realtime"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gulp = require('gulp');
|
||||||
|
const build = require('@microsoft/sp-build-web');
|
||||||
|
|
||||||
|
build.initialize(gulp);
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"name": "react-webhooks-realtime",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp bundle",
|
||||||
|
"clean": "gulp clean",
|
||||||
|
"test": "gulp test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/sp-core-library": "~1.3.0",
|
||||||
|
"@microsoft/sp-lodash-subset": "~1.3.0",
|
||||||
|
"@microsoft/sp-webpart-base": "~1.3.0",
|
||||||
|
"@types/react": "15.0.38",
|
||||||
|
"@types/react-addons-shallow-compare": "0.14.17",
|
||||||
|
"@types/react-addons-test-utils": "0.14.15",
|
||||||
|
"@types/react-addons-update": "0.14.14",
|
||||||
|
"@types/react-dom": "0.14.18",
|
||||||
|
"@types/webpack-env": ">=1.12.1 <1.14.0",
|
||||||
|
"@uifabric/example-app-base": "^5.1.2",
|
||||||
|
"moment": "^2.19.1",
|
||||||
|
"react": "15.4.2",
|
||||||
|
"react-dom": "15.4.2",
|
||||||
|
"socket.io-client": "^2.0.4",
|
||||||
|
"sp-pnp-js": "^3.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@microsoft/sp-build-web": "~1.3.0",
|
||||||
|
"@microsoft/sp-module-interfaces": "~1.3.0",
|
||||||
|
"@microsoft/sp-webpart-workbench": "~1.3.0",
|
||||||
|
"gulp": "~3.9.1",
|
||||||
|
"@types/chai": ">=3.4.34 <3.6.0",
|
||||||
|
"@types/mocha": ">=2.2.33 <2.6.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
|
"id": "874bb168-abe1-4176-92c2-24251077c23e",
|
||||||
|
"alias": "RealTimeListWebPart",
|
||||||
|
"componentType": "WebPart",
|
||||||
|
|
||||||
|
// The "*" signifies that the version should be taken from the package.json
|
||||||
|
"version": "*",
|
||||||
|
"manifestVersion": 2,
|
||||||
|
|
||||||
|
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||||
|
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||||
|
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||||
|
"requiresCustomScript": false,
|
||||||
|
|
||||||
|
"preconfiguredEntries": [{
|
||||||
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||||
|
"group": { "default": "Other" },
|
||||||
|
"title": { "default": "Real Time List" },
|
||||||
|
"description": { "default": "get the last changes in real time" },
|
||||||
|
"officeFabricIconFontName": "News",
|
||||||
|
"properties": {
|
||||||
|
"description": "RealTimeList"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDom from 'react-dom';
|
||||||
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
|
import {
|
||||||
|
BaseClientSideWebPart,
|
||||||
|
IPropertyPaneConfiguration,
|
||||||
|
PropertyPaneTextField
|
||||||
|
} from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
|
import * as strings from 'RealTimeListWebPartStrings';
|
||||||
|
import RealTimeList from './components/RealTimeList';
|
||||||
|
import { IRealTimeListProps } from './components/IRealTimeListProps';
|
||||||
|
import pnp from "sp-pnp-js";
|
||||||
|
|
||||||
|
export interface IRealTimeListWebPartProps {
|
||||||
|
socketserverurl: string;
|
||||||
|
siteUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class RealTimeListWebPart extends BaseClientSideWebPart<IRealTimeListWebPartProps> {
|
||||||
|
|
||||||
|
// Ovverriding onInit in order to set up the sp pnp js with the web part context
|
||||||
|
public onInit(): Promise<void> {
|
||||||
|
return super.onInit().then(_ => {
|
||||||
|
// establish SPFx context
|
||||||
|
pnp.setup({
|
||||||
|
spfxContext: this.context
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
const element: React.ReactElement<IRealTimeListProps > = React.createElement(
|
||||||
|
RealTimeList,
|
||||||
|
{
|
||||||
|
socketserverurl: this.properties.socketserverurl,
|
||||||
|
siteUrl: this.context.pageContext.web.absoluteUrl
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ReactDom.render(element, this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
header: {
|
||||||
|
description: strings.PropertyPaneDescription
|
||||||
|
},
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
groupName: strings.BasicGroupName,
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('socketserverurl', {
|
||||||
|
label: strings.SocketserverurlFieldLabel
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
protected get disableReactivePropertyChanges(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface IRealTimeListProps {
|
||||||
|
socketserverurl: string;
|
||||||
|
siteUrl: string;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import {
|
||||||
|
IColumn
|
||||||
|
} from 'office-ui-fabric-react/lib/DetailsList';
|
||||||
|
|
||||||
|
export interface IRealTimeListState {
|
||||||
|
sortedItems?: any[];
|
||||||
|
columns?: IColumn[];
|
||||||
|
loading?: boolean;
|
||||||
|
newsFeed?: string;
|
||||||
|
newsFeedVisible?: boolean;
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
.realTimeList {
|
||||||
|
.container {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0px auto;
|
||||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
padding: 0;//20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listItem {
|
||||||
|
max-width: 715px;
|
||||||
|
margin: 5px auto 5px auto;
|
||||||
|
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
// Our button
|
||||||
|
text-decoration: none;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
// Primary Button
|
||||||
|
min-width: 80px;
|
||||||
|
background-color: "[theme:themePrimary, default:#0078d7]";
|
||||||
|
border-color: "[theme:themePrimary, default:#0078d7]";
|
||||||
|
color: #ffffff;
|
||||||
|
|
||||||
|
// Basic Button
|
||||||
|
outline: transparent;
|
||||||
|
position: relative;
|
||||||
|
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
border-width: 0;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
margin: 0 4px;
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,270 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './RealTimeList.module.scss';
|
||||||
|
import { IRealTimeListProps } from './IRealTimeListProps';
|
||||||
|
import { IRealTimeListState } from './IRealTimeListState';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import pnp from "sp-pnp-js";
|
||||||
|
import { Web } from "sp-pnp-js";
|
||||||
|
import * as io from 'socket.io-client';
|
||||||
|
import { createListItems } from '@uifabric/example-app-base';
|
||||||
|
import { autobind } from 'office-ui-fabric-react/lib/Utilities';
|
||||||
|
import { Link } from 'office-ui-fabric-react/lib/Link';
|
||||||
|
import { Image, ImageFit } from 'office-ui-fabric-react/lib/Image';
|
||||||
|
import { DefaultButton, IButtonProps } from 'office-ui-fabric-react/lib/Button';
|
||||||
|
import { Label } from 'office-ui-fabric-react/lib/Label';
|
||||||
|
import {
|
||||||
|
DetailsList,
|
||||||
|
buildColumns,
|
||||||
|
IColumn
|
||||||
|
} from 'office-ui-fabric-react/lib/DetailsList';
|
||||||
|
import {
|
||||||
|
Spinner,
|
||||||
|
SpinnerSize
|
||||||
|
} from 'office-ui-fabric-react/lib/Spinner';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
let _items: any[];
|
||||||
|
let _lastQueryDate: moment.Moment;
|
||||||
|
|
||||||
|
export interface IList {
|
||||||
|
Id: number;
|
||||||
|
Title: string;
|
||||||
|
SPFxDescription: string;
|
||||||
|
SPFxThumbnail: ITumbnailUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITumbnailUrl {
|
||||||
|
Url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class RealTimeList extends React.Component<IRealTimeListProps, IRealTimeListState> {
|
||||||
|
public componentDidMount(): void {
|
||||||
|
if (this.props.socketserverurl != null && this.props.socketserverurl != "" && this.props.socketserverurl !== undefined) {
|
||||||
|
this._connectSocket(this.props.socketserverurl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public componentWillReceiveProps(nextProps: IRealTimeListProps): void {
|
||||||
|
if (nextProps.socketserverurl != null && nextProps.socketserverurl != "" && nextProps.socketserverurl !== undefined) {
|
||||||
|
this._connectSocket(nextProps.socketserverurl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
constructor(props: IRealTimeListProps, state: IRealTimeListState) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
_items = [];
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
sortedItems: _items,
|
||||||
|
columns: _buildColumns(),
|
||||||
|
loading: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): React.ReactElement<IRealTimeListProps> {
|
||||||
|
if (this.props.siteUrl.toLowerCase().indexOf("wwww.contoso.com") >= 0
|
||||||
|
|| this.props.socketserverurl === undefined || this.props.socketserverurl === "") {
|
||||||
|
return (
|
||||||
|
<div className={styles.realTimeList}>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={`ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}`}>
|
||||||
|
<div className="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
|
||||||
|
<span className="ms-font-xl ms-fontColor-white">Connect the web part with SharePoint and configuring it before to begin</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let { sortedItems, columns } = this.state;
|
||||||
|
const loading: JSX.Element = (this.state.loading == true) ? <Spinner size={SpinnerSize.large} /> : null;
|
||||||
|
const list: JSX.Element = (this.state.loading == false) ?
|
||||||
|
<DetailsList
|
||||||
|
items={sortedItems as any[]}
|
||||||
|
setKey='set'
|
||||||
|
columns={columns}
|
||||||
|
onRenderItemColumn={_renderItemColumn}
|
||||||
|
onColumnHeaderClick={this._onColumnClick}
|
||||||
|
onItemInvoked={this._onItemInvoked}
|
||||||
|
onColumnHeaderContextMenu={this._onColumnHeaderContextMenu}
|
||||||
|
/>
|
||||||
|
: null;
|
||||||
|
const newsFeed: JSX.Element =
|
||||||
|
this.state.newsFeedVisible == true ?
|
||||||
|
<DefaultButton
|
||||||
|
text={this.state.newsFeed}
|
||||||
|
onClick={() => this._loadList()}
|
||||||
|
/>
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.realTimeList}>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={`ms-Grid-row ms-bgColor-white ms-fontColor-white ${styles.row}`}>
|
||||||
|
<div className="ms-Grid-col ms-lg6 ms-xl6 ms-xlPush5 ms-lgPush5">
|
||||||
|
{newsFeed}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`ms-Grid-row ms-bgColor-white ms-fontColor-white ${styles.row}`}>
|
||||||
|
<div className="ms-Grid-col ms-lg12 ms-xl12">
|
||||||
|
{loading}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`ms-Grid-row ms-bgColor-white ms-fontColor-white ${styles.row}`}>
|
||||||
|
<div className="ms-Grid-col ms-lg12 ms-xl12">
|
||||||
|
{list}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private toTicks(date: moment.Moment): number {
|
||||||
|
return (date.valueOf() * 10000) + 621355968000000000;
|
||||||
|
}
|
||||||
|
private async _connectSocket(socketServerUrl: string) {
|
||||||
|
// Connect to the server
|
||||||
|
const socket = io(socketServerUrl);
|
||||||
|
// Add the socket io listeners
|
||||||
|
socket.on('list:changes', (data) => {
|
||||||
|
this._getListChanges(data);
|
||||||
|
console.log(JSON.stringify(data));
|
||||||
|
});
|
||||||
|
await this._loadList();
|
||||||
|
}
|
||||||
|
private async _getListChanges(dataWebhooks: any): Promise<void> {
|
||||||
|
let dataParsed = JSON.parse(dataWebhooks);
|
||||||
|
let resource = dataParsed[0].resource;
|
||||||
|
let changeToken = `1;3;${resource};${this.toTicks(_lastQueryDate)};-1`;
|
||||||
|
let changes = await pnp.sp.web.lists.getByTitle("Events").getChanges(
|
||||||
|
{
|
||||||
|
Add: true,
|
||||||
|
Item: true,
|
||||||
|
ChangeTokenStart: { StringValue: changeToken }
|
||||||
|
});
|
||||||
|
console.log(changes);
|
||||||
|
console.log(_lastQueryDate);
|
||||||
|
console.log(_items.length);
|
||||||
|
if (changes.length > 0) {
|
||||||
|
let newsFeedText = (changes.length == 1) ? changes.length + " new item" : changes.length + " new items";
|
||||||
|
this.setState({
|
||||||
|
newsFeedVisible: true,
|
||||||
|
newsFeed: newsFeedText
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private async _loadList(): Promise<void> {
|
||||||
|
this.setState({
|
||||||
|
loading: true
|
||||||
|
});
|
||||||
|
let items = await pnp.sp.web.lists.getByTitle("Events").items.select("Id", "Title", "SPFxDescription", "SPFxThumbnail")
|
||||||
|
.orderBy("Modified", false).get();
|
||||||
|
_items = items.map((item: IList, index: number) => {
|
||||||
|
return {
|
||||||
|
thumbnail: item.SPFxThumbnail != null ? item.SPFxThumbnail.Url : "",
|
||||||
|
key: item.Id,
|
||||||
|
name: item.Title,
|
||||||
|
description: item.SPFxDescription
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
sortedItems: _items,
|
||||||
|
columns: _buildColumns(),
|
||||||
|
loading: false,
|
||||||
|
newsFeedVisible: false
|
||||||
|
});
|
||||||
|
_lastQueryDate = moment();
|
||||||
|
}
|
||||||
|
private _getListItems(count: number, startIndex: number = 0): any {
|
||||||
|
// get all the items from a list
|
||||||
|
pnp.sp.web.lists.getByTitle("Events").items.get().then((items: any[]) => {
|
||||||
|
console.log(items);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private _onColumnClick(event: React.MouseEvent<HTMLElement>, column: IColumn) {
|
||||||
|
let { sortedItems, columns } = this.state;
|
||||||
|
let isSortedDescending = column.isSortedDescending;
|
||||||
|
|
||||||
|
// If we've sorted this column, flip it.
|
||||||
|
if (column.isSorted) {
|
||||||
|
isSortedDescending = !isSortedDescending;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the items.
|
||||||
|
sortedItems = sortedItems!.concat([]).sort((a, b) => {
|
||||||
|
let firstValue = a[column.fieldName];
|
||||||
|
let secondValue = b[column.fieldName];
|
||||||
|
|
||||||
|
if (isSortedDescending) {
|
||||||
|
return firstValue > secondValue ? -1 : 1;
|
||||||
|
} else {
|
||||||
|
return firstValue > secondValue ? 1 : -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset the items and columns to match the state.
|
||||||
|
this.setState({
|
||||||
|
sortedItems: sortedItems,
|
||||||
|
columns: columns!.map(col => {
|
||||||
|
col.isSorted = (col.key === column.key);
|
||||||
|
|
||||||
|
if (col.isSorted) {
|
||||||
|
col.isSortedDescending = isSortedDescending;
|
||||||
|
}
|
||||||
|
|
||||||
|
return col;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onColumnHeaderContextMenu(column: IColumn | undefined, ev: React.MouseEvent<HTMLElement> | undefined): void {
|
||||||
|
console.log(`column ${column!.key} contextmenu opened.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onItemInvoked(item: any, index: number | undefined): void {
|
||||||
|
alert(`Item ${item.name} at index ${index} has been invoked.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _buildColumns() {
|
||||||
|
if (_items.length == 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let columns = buildColumns(_items);
|
||||||
|
|
||||||
|
let thumbnailColumn = columns.filter(column => column.name === 'thumbnail')[0];
|
||||||
|
// Special case one column's definition.
|
||||||
|
thumbnailColumn.name = '';
|
||||||
|
thumbnailColumn.maxWidth = 100;
|
||||||
|
|
||||||
|
let keyColumn = columns.filter(column => column.name === 'key')[0];
|
||||||
|
keyColumn.maxWidth = 100;
|
||||||
|
|
||||||
|
let nameColumn = columns.filter(column => column.name === 'name')[0];
|
||||||
|
nameColumn.maxWidth = 200;
|
||||||
|
|
||||||
|
let descriptionColumn = columns.filter(column => column.name === 'description')[0];
|
||||||
|
descriptionColumn.maxWidth = 300;
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _renderItemColumn(item: any, index: number, column: IColumn) {
|
||||||
|
let fieldContent = item[column.fieldName];
|
||||||
|
|
||||||
|
switch (column.key) {
|
||||||
|
case 'thumbnail':
|
||||||
|
return <Image src={fieldContent} width={50} height={50} imageFit={ImageFit.cover} />;
|
||||||
|
|
||||||
|
case 'name':
|
||||||
|
return <Link href='#'>{fieldContent}</Link>;
|
||||||
|
|
||||||
|
case 'color':
|
||||||
|
return <span data-selection-disabled={true} style={{ color: fieldContent }}>{fieldContent}</span>;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return <span>{fieldContent}</span>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
define([], function() {
|
||||||
|
return {
|
||||||
|
"PropertyPaneDescription": "Description",
|
||||||
|
"BasicGroupName": "Configuration Panel",
|
||||||
|
"SocketserverurlFieldLabel": "Insert the Socket.IO server URL"
|
||||||
|
}
|
||||||
|
});
|
10
samples/react-webhooks-realtime/src/webparts/realTimeList/loc/mystrings.d.ts
vendored
Normal file
10
samples/react-webhooks-realtime/src/webparts/realTimeList/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
declare interface IRealTimeListWebPartStrings {
|
||||||
|
PropertyPaneDescription: string;
|
||||||
|
BasicGroupName: string;
|
||||||
|
SocketserverurlFieldLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'RealTimeListWebPartStrings' {
|
||||||
|
const strings: IRealTimeListWebPartStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
|
describe('RealTimeListWebPart', () => {
|
||||||
|
it('should do something', () => {
|
||||||
|
assert.ok(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"types": [
|
||||||
|
"es6-promise",
|
||||||
|
"es6-collections",
|
||||||
|
"webpack-env"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Type definitions for Microsoft ODSP projects
|
||||||
|
// Project: ODSP
|
||||||
|
|
||||||
|
/* Global definition for UNIT_TEST builds
|
||||||
|
Code that is wrapped inside an if(UNIT_TEST) {...}
|
||||||
|
block will not be included in the final bundle when the
|
||||||
|
--ship flag is specified */
|
||||||
|
declare const UNIT_TEST: boolean;
|
||||||
|
|
||||||
|
/* Global defintion for SPO builds */
|
||||||
|
declare const DATACENTER: boolean;
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference path="@ms/odsp.d.ts" />
|
Loading…
Reference in New Issue