diff --git a/CHANGELOG.md b/CHANGELOG.md index f691609dd..4b3f4b8ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - ISite now supports `async rebase()` to ensure any ISite is represented by the url pattern /sites/{site id} regardless of how it was first loaded - ISites.getAllSites() - support for operations for ISite and IList + - completed support for Files - sp - explict error thrown if SPFx context is null or undefined when needed @@ -26,6 +27,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - graph - paged method removed from IGraphQueryableCollection - ./operations.ts methods moved to ./graphqueryable.ts + - deprecated DriveItem move method. + - deprecated DriveItem setContent method. - sp - getPaged method removed from _Items/IItems diff --git a/debug/launch/graph.ts b/debug/launch/graph.ts index 9fe04eec9..8c46eb739 100644 --- a/debug/launch/graph.ts +++ b/debug/launch/graph.ts @@ -1,20 +1,92 @@ import { Logger, LogLevel } from "@pnp/logging"; import { graphSetup } from "./setup.js"; import "@pnp/graph/users"; +import "@pnp/graph/groups"; +import "@pnp/graph/sites"; +import "@pnp/graph/files"; +import { IDriveItemAdd, IDriveItemAddFolder } from "@pnp/graph/files"; +import * as fs from "fs"; +import { IResumableUploadOptions } from "@pnp/graph/files"; +import { graphPut } from "@pnp/graph"; +import { InjectHeaders } from "@pnp/queryable/index.js"; +import { DriveItemUploadableProperties } from "@microsoft/microsoft-graph-types"; +import { ISensitivityLabel } from "@pnp/graph/files"; declare var process: { exit(code?: number): void }; export async function Example(settings: any) { + const userId = "julie@sympjulie.onmicrosoft.com"; + const graph = graphSetup(settings); - const graph = graphSetup(settings); + const folderInfo: IDriveItemAddFolder = { + name: "Sub Folder", + conflictBehavior: "replace", + }; - const users = await graph.users(); + const fileInfo: IDriveItemAdd = { + filename: "Test File.txt", + content: "Contents of test file", + contentType: "text/plain", + conflictBehavior: "replace", + }; - Logger.log({ - data: users, - level: LogLevel.Info, - message: "List of Users Data", - }); + //const users = await graph.users.getById(userId).drive.root.children.addFolder(folderInfo); + //const folder = await graph.users.getById(userId).drive.getItemByPath("/Test Folder")(); + //const folder = await graph.users.getById(userId).drive.root.getItemByPath("/Test Folder")(); - process.exit(0); + //const file = await graph.users.getById(userId).drive.root.children.add(fileInfo); + + // const moveItem = { + // parentReference: { + // id: folder.id, + // }, + // name: "Moved File.txt", + // } + // const move = await graph.users.getById(userId).drive.getItemById(file.id).moveItem(moveItem); + //const thumbnails = await graph.users.getById(userId).drive.getItemById(folder.id).thumbnails(); + //const versions = await graph.users.getById(userId).drive.getItemById(folder.id).versions(); + //const users = await graph.sites.getById(settings.testing.graph.id).drive.list(); + + const fileBuff = fs.readFileSync("C:\\Users\\jturner.BMA\\Desktop\\TestDocument.docx"); + + const fileUploadOptions: IResumableUploadOptions = { + item: { + name: "TestDocument2.docx", + fileSize: fileBuff.byteLength, + }, + }; + + const label: ISensitivityLabel = { + sensitivityLabelId: "b7a3c3d5-7b6d-4e6c-8e0c-3f5c7b1d0e3d", + assignmentMethod: "standard", + justificationText: "Just because", + }; + + const driveRoot = await graph.sites.getById(settings.testing.graph.id).drive.root(); + const driveItems = await graph.sites.getById(settings.testing.graph.id).drive.root.children(); + const driveItem = await graph.sites.getById(settings.testing.graph.id).drive.getItemById(driveItems[1].id)(); + const retentionLabelStatusUrl = await graph.sites.getById(settings.testing.graph.id).drive.getItemById(driveItems[1].id).assignSensitivityLabel(label); + //const retentionLabel = await graph.users.getById(userId).drive.getItemById(driveItems[0].id).extractSensitivityLabels(); + const uploadSession = await graph.users.getById(userId).drive.getItemById(driveRoot.id).createUploadSession(fileUploadOptions); + const status = await uploadSession.resumableUpload.status(); + + const upload = await uploadSession.resumableUpload.upload(fileBuff.length, fileBuff); + + // Upload a chunk of the file to the upload session + // Using a fragment size that doesn't divide evenly by 320 KiB results in errors committing some files. + const chunkSize = 327680; + let startFrom = 0; + while (startFrom < fileBuff.length) { + const fileChunk = fileBuff.slice(startFrom, startFrom + chunkSize); + const contentLength = `bytes ${startFrom}-${startFrom + chunkSize}/${fileBuff.length}` + const uploadChunk = await uploadSession.resumableUpload.upload(chunkSize, fileChunk, contentLength); + startFrom += chunkSize; + } + Logger.log({ + data: retentionLabelStatusUrl, + level: LogLevel.Info, + message: "List of Users Data", + }); + + process.exit(0); } diff --git a/debug/launch/main.ts b/debug/launch/main.ts index 230ee47d3..5345d2d94 100644 --- a/debug/launch/main.ts +++ b/debug/launch/main.ts @@ -8,8 +8,8 @@ import { ITestingSettings } from "../../test/load-settings.js"; // add your debugging imports here and prior to submitting a PR git checkout debug/debug.ts // will allow you to keep all your debugging files locally // comment out the example -import { Example } from "./sp.js"; -// import { Example } from "./graph.js"; +// import { Example } from "./sp.js"; +import { Example } from "./graph.js"; // setup the connection to SharePoint using the settings file, you can // override any of the values as you want here, just be sure not to commit diff --git a/docs/graph/files-labels.md b/docs/graph/files-labels.md new file mode 100644 index 000000000..d87de5e86 --- /dev/null +++ b/docs/graph/files-labels.md @@ -0,0 +1,103 @@ +# @pnp/graph/files - Sensitivity and Retention Labels (Premium Endpoint) + +The ability to manage sensitivity and retention labels on drive items in SharePoint. + +More information can be found in the official Graph documentation: + +- [Drives/Files Resource Type](https://learn.microsoft.com/en-us/graph/api/resources/drive?view=graph-rest-1.0) + +## IInvitations + +[![Invokable Banner](https://img.shields.io/badge/Invokable-informational.svg)](../concepts/invokable.md) [![Selective Imports Banner](https://img.shields.io/badge/Selective%20Imports-informational.svg)](../concepts/selective-imports.md) + +## Assign Sensitivity Label to Drive Item + +Using the assignSensitivityLabel() you can add a sensitivity label to a DriveItem + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/users"; +import "@pnp/graph/sites"; +import "@pnp/graph/groups"; +import "@pnp/graph/files"; +import { ISensitivityLabel } from "@pnp/graph/files"; + +const graph = graphfi(...); + +const label: ISensitivityLabel = { + sensitivityLabelId: "b7a3c3d5-7b6d-4e6c-8e0c-3f5c7b1d0e3d", + assignmentMethod: "standard", + justificationText: "Just because", +}; + +// This is a long running operation and returns a url to check the status. +const retentionLabelStatusUrl = await graph.sites.getById({site id}).drive.getItemById({item id}).assignSensitivityLabel(label); +const retentionLabelStatusUrl = await graph.users.getById({user id}).drive.getItemById({item id}).assignSensitivityLabel(label); +const retentionLabelStatusUrl = await graph.group.getById({group id}).drive.getItemById({item id}).assignSensitivityLabel(label); +``` + +## Extract Sensitivity Labels from a Drive Item + +Using extractSensitivityLabels() extract one or more sensitivity labels assigned to a drive item + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/users"; +import "@pnp/graph/sites"; +import "@pnp/graph/groups"; +import "@pnp/graph/files"; + +const graph = graphfi(...); + +const sensitivityLabels = await graph.sites.getById({site id}).drive.getItemById({item id}).extractSensitivityLabels(); +const sensitivityLabels = await graph.users.getById({user id}).drive.getItemById({item id}).extractSensitivityLabels(); +const sensitivityLabels = await graph.group.getById({group id}).drive.getItemById({item id}).extractSensitivityLabels(); +``` + +## Retrieve/Update/Delete Retention Label of the Drive Item + +Method for retrieving, updating, and removing the retention label of the drive item. + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/users"; +import "@pnp/graph/sites"; +import "@pnp/graph/groups"; +import "@pnp/graph/files"; + +const graph = graphfi(...); + +// Get retention label +const retentionLabel = await graph.sites.getById({site id}).drive.getItemById({item id}).retentionLabel(); +const retentionLabel = await graph.users.getById({user id}).drive.getItemById({item id}).retentionLabel(); +const retentionLabel = await graph.group.getById({group id}).drive.getItemById({item id}).retentionLabel(); + +// Update retention label +const retentionLabel = await graph.sites.getById({site id}).drive.getItemById({item id}).updateRetentionLabel("New Name"); +const retentionLabel = await graph.users.getById({user id}).drive.getItemById({item id}).updateRetentionLabel("New Name"); +const retentionLabel = await graph.group.getById({group id}).drive.getItemById({item id}).updateRetentionLabel("New Name"); + +// Delete retention label +await graph.sites.getById({site id}).drive.getItemById({item id}).removeRetentionLabel(); +await graph.users.getById({user id}).drive.getItemById({item id}).removeRetentionLabel(); +await graph.group.getById({group id}).drive.getItemById({item id}).removeRetentionLabel(); +``` + +## Lock/Unlock Record of the Drive Item + +Method for locking/unlocking a record of the drive item. + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/users"; +import "@pnp/graph/sites"; +import "@pnp/graph/groups"; +import "@pnp/graph/files"; + +const graph = graphfi(...); + +// Send 'true' to lock the record, and 'false' to unlock the record. +const retentionLabel = await graph.sites.getById({site id}).drive.getItemById({item id}).recordLocked(true); +const retentionLabel = await graph.users.getById({user id}).drive.getItemById({item id}).recordLocked(true); +const retentionLabel = await graph.group.getById({group id}).drive.getItemById({item id}).recordLocked(true); +``` diff --git a/docs/graph/files.md b/docs/graph/files.md index 87fba26d2..793ac6aef 100644 --- a/docs/graph/files.md +++ b/docs/graph/files.md @@ -1,11 +1,11 @@ # @pnp/graph/files -The ability to manage drives and drive items in Onedrive is a capability introduced in version 1.2.4 of @pnp/graph. Through the methods described -you can manage drives and drive items in Onedrive. +The ability to manage drives and drive items in OneDrive is a capability introduced in version 1.2.4 of @pnp/graph. Through the methods described +you can manage drives and drive items in OneDrive. More information can be found in the official Graph documentation: -- [Drive/Files Resource Type](https://docs.microsoft.com/en-us/graph/api/resources/onedrive?view=graph-rest-1.0) +- [Drives/Files Resource Type](https://learn.microsoft.com/en-us/graph/api/resources/drive?view=graph-rest-1.0) ## IInvitations @@ -13,7 +13,7 @@ More information can be found in the official Graph documentation: ## Get the default drive -Using the drive you can get the users default drive from Onedrive, or the groups or sites default document library. +Using the drive you can get the users default drive from OneDrive, or the groups or sites default document library. ```TypeScript import { graphfi } from "@pnp/graph"; @@ -24,7 +24,7 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const otherUserDrive = await graph.users.getById("user@tenant.onmicrosoft.com").drive(); +const otherUserDrive = await graph.users.getById({user id}).drive(); const currentUserDrive = await graph.me.drive(); @@ -35,7 +35,7 @@ const siteDrive = await graph.sites.getById("{site identifier}").drive(); ## Get all of the drives -Using the drives() you can get the users available drives from Onedrive +Using the drives() you can get the users available drives from OneDrive ```TypeScript import { graphfi } from "@pnp/graph"; @@ -46,7 +46,7 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const otherUserDrive = await graph.users.getById("user@tenant.onmicrosoft.com").drives(); +const otherUserDrive = await graph.users.getById({user id}).drives(); const currentUserDrive = await graph.me.drives(); @@ -67,15 +67,15 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const drive = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}")(); +const drive = await graph.users.getById({user id}).drives.getById({drive id})(); -const drive = await graph.me.drives.getById("{drive id}")(); +const drive = await graph.me.drives.getById({drive id})(); -const drive = await graph.drives.getById("{drive id}")(); +const drive = await graph.drives.getById({drive id})(); ``` -## Get the associated list of a drive +## Get the associated list of a SharePoint drive Using the list() you get the associated list information @@ -86,33 +86,30 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const list = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").list(); - -const list = await graph.me.drives.getById("{drive id}").list(); +const list = await graph.sites.getById("{site identifier}").getById({drive id}).list(); ``` -Using the getList(), from the lists implementation, you get the associated IList object. -Form more infomration about acting on the IList object see [@pnpjs/graph/lists](./lists.md) +## Get the recent files + +Using the recent() you get the recent files ```TypeScript import { graphfi } from "@pnp/graph"; import "@pnp/graph/users"; import "@pnp/graph/files"; -import "@pnp/graph/lists"; const graph = graphfi(...); -const listObject: IList = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").getList(); +const files = await graph.users.getById({user id}).drives.getById({drive id}).recent(); -const listOBject: IList = await graph.me.drives.getById("{drive id}").getList(); +const files = await graph.me.drives.getById({drive id}).recent(); -const list = await listObject(); ``` -## Get the recent files +## Get the files shared with me -Using the recent() you get the recent files +Using the sharedWithMe() you get the files shared with the user ```TypeScript import { graphfi } from "@pnp/graph"; @@ -121,15 +118,20 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const files = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").recent(); +const shared = await graph.users.getById({user id}).drives.getById({drive id}).sharedWithMe(); + +const shared = await graph.me.drives.getById({drive id}).sharedWithMe(); -const files = await graph.me.drives.getById("{drive id}").recent(); +// By default, sharedWithMe return items shared within your own tenant. To include items shared from external tenants include the options object. + +const options: ISharingWithMeOptions = {allowExternal: true}; +const shared = await graph.me.drives.getById({drive id}).sharedWithMe(options); ``` -## Get the files shared with me +## Get the following drive item -Using the sharedWithMe() you get the files shared with the user +List the items that have been followed by the signed in user. ```TypeScript import { graphfi } from "@pnp/graph"; @@ -138,20 +140,13 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const shared = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").sharedWithMe(); - -const shared = await graph.me.drives.getById("{drive id}").sharedWithMe(); - -// By default, sharedWithMe return items shared within your own tenant. To include items shared from external tenants include the options object. - -const options: ISharingWithMeOptions = {allowExternal: true}; -const shared = await graph.me.drives.getById("{drive id}").sharedWithMe(options); +const files = await graph.me.drives.getById({drive id}).following(); ``` -## Get the following files +## Follow/Unfollow a drive item -List the items that have been followed by the signed in user. +Follow/Unfollow a drive item ```TypeScript import { graphfi } from "@pnp/graph"; @@ -160,8 +155,11 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const files = await graph.me.drives.getById("{drive id}").following(); +const driveItem = await graph.me.drives.getById({drive id}).getItemById({item id}).follow(); +const driveItem = await graph.me.drives.getById({drive id}).getItemById({item id}).unfollow(); +const driveItem = await graph.users.getById({user id}).drives.getById({drive id}).getItemById({item id}).follow(); +const driveItem = await graph.users.getById({user id}).drives.getById({drive id}).getItemById({item id}).unfollow(); ``` ## Get the Root folder @@ -177,16 +175,16 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const root = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").root(); -const root = await graph.users.getById("user@tenant.onmicrosoft.com").drive.root(); +const root = await graph.users.getById({user id}).drives.getById({drive id}).root(); +const root = await graph.users.getById({user id}).drive.root(); -const root = await graph.me.drives.getById("{drive id}").root(); +const root = await graph.me.drives.getById({drive id}).root(); const root = await graph.me.drive.root(); -const root = await graph.sites.getById("{site id}").drives.getById("{drive id}").root(); +const root = await graph.sites.getById("{site id}").drives.getById({drive id}).root(); const root = await graph.sites.getById("{site id}").drive.root(); -const root = await graph.groups.getById("{site id}").drives.getById("{drive id}").root(); +const root = await graph.groups.getById("{site id}").drives.getById({drive id}).root(); const root = await graph.groups.getById("{site id}").drive.root(); ``` @@ -202,13 +200,13 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const rootChildren = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").root.children(); +const rootChildren = await graph.users.getById({user id}).drives.getById({drive id}).root.children(); -const rootChildren = await graph.me.drives.getById("{drive id}").root.children(); +const rootChildren = await graph.me.drives.getById({drive id}).root.children(); -const itemChildren = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").items.getById("{item id}").children(); +const itemChildren = await graph.users.getById({user id}).drives.getById({drive id}).items.getById("{item id}").children(); -const itemChildren = await graph.me.drives.getById("{drive id}").root.items.getById("{item id}").children(); +const itemChildren = await graph.me.drives.getById({drive id}).root.items.getById("{item id}").children(); ``` @@ -223,13 +221,13 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const item = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getItemsByPath("MyFolder/MySubFolder")(); +const item = await graph.users.getById({user id}).drives.getItemsByPath("MyFolder/MySubFolder")(); const item = await graph.me.drives.getItemsByPath("MyFolder/MySubFolder")(); ``` -## Add Item +## Add Drive Item (File and Folder) Using the add you can add an item, for more options please user the upload method instead. @@ -237,12 +235,28 @@ Using the add you can add an item, for more options please user the upload metho import { graphfi } from "@pnp/graph"; import "@pnp/graph/files"; import "@pnp/graph/users"; -import {IDriveItemAddResult} from "@pnp/graph/files"; +import {IDriveItemAdd} from "@pnp/graph/files"; const graph = graphfi(...); -const add1: IDriveItemAddResult = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").root.children.add("test.txt", "My File Content String"); -const add2: IDriveItemAddResult = await graph.me.drives.getById("{drive id}").root.children.add("filename.txt", "My File Content String"); +const fileInfo: IDriveItemAdd = { + filename: "Test File.txt", + content: "Contents of test file", + contentType: "text/plain", + conflictBehavior: "replace", + driveItem: {}, +}; + +const folderInfo: IDriveItemAddFolder = { + name: "Sub Folder", + conflictBehavior: "replace", +}; + +const driveRootFile = await graph.users.getById({user Id}).drive.root.children.add(fileInfo); +const driveRootFolder = await graph.users.getById({user Id}).drive.root.children.addFolder(folderInfo); + +const subFolderFile = await graph.users.getById({user Id}).drive.getItemById({folder id}).children.add(fileInfo); +const subFolderFile = await graph.users.getById({user Id}).drive.getItemById({folder id}).children.addFolder(folderInfo); ``` ## Upload/Replace Drive Item Content @@ -253,22 +267,20 @@ Using the .upload method you can add or update the content of an item. import { graphfi } from "@pnp/graph"; import "@pnp/graph/files"; import "@pnp/graph/users"; -import {IFileOptions, IDriveItemAddResult} from "@pnp/graph/files"; +import {IFileUploadOptions} from "@pnp/graph/files"; const graph = graphfi(...); // file path is only file name -const fileOptions: IFileOptions = { +const fileOptions: IFileUploadOptions = { content: "This is some test content", filePathName: "pnpTest.txt", - contentType: "text/plain;charset=utf-8" + contentType: "text/plain;charset=utf-8", } -const uDriveRoot: IDriveItemAddResult = await graph.users.getById("user@tenant.onmicrosoft.com").drive.root.upload(fileOptions); - -const uFolder: IDriveItemAddResult = await graph.users.getById("user@tenant.onmicrosoft.com").drive.getItemById("{folder id}").upload(fileOptions); - -const uDriveIdRoot: IDriveItemAddResult = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").root.upload(fileOptions); +const driveItem = await graph.users.getById({user id}).drive.root.upload(fileOptions); +const driveItem = await graph.users.getById({user id}).drive.getItemById({folder id}).upload(fileOptions); +const driveItem = await graph.users.getById({user id}).drives.getById({drive id}).root.upload(fileOptions); // file path includes folders const fileOptions2: IFileOptions = { @@ -277,24 +289,48 @@ const fileOptions2: IFileOptions = { contentType: "text/plain;charset=utf-8" } -const uFileOptions: IDriveItemAddResult = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").root.upload(fileOptions2); +const driveItem = await graph.users.getById({user id}).drives.getById({drive id}).root.upload(fileOptions2); ``` -## Add folder +## Resumable Upload for Drive Item Content -Using addFolder you can add a folder +Create an upload session to allow your app to upload files up to the maximum file size. An upload session allows your app to upload ranges of the file in sequential API requests. Upload sessions also allow the transfer to resume if a connection is dropped while the upload is in progress. ```TypeScript -import { graph } from "@pnp/graph"; +import * as fs from "fs"; +import { graphfi } from "@pnp/graph"; import "@pnp/graph/files"; -import "@pnp/graph/users" -import {IDriveItemAddResult} from "@pnp/graph/ondrive"; +import "@pnp/graph/users"; +import {IFileUploadOptions} from "@pnp/graph/files"; const graph = graphfi(...); -const addFolder1: IDriveItemAddResult = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").root.children.addFolder('New Folder'); -const addFolder2: IDriveItemAddResult = await graph.me.drives.getById("{drive id}").root.children.addFolder('New Folder'); +const fileBuff = fs.readFileSync("C:\\MyDocs\\TestDocument.docx"); +const fileUploadOptions: IResumableUploadOptions = { + item: { + name: "TestDocument2.docx", + fileSize: fileBuff.byteLength, + }, +}; +// Create the upload session +const uploadSession = await graph.users.getById(userId).drive.getItemById(driveRoot.id).createUploadSession(fileUploadOptions); +// Get the status of the upload session +const status = await uploadSession.resumableUpload.status(); + +// Upload the entire file to the upload session +const upload = await uploadSession.resumableUpload.upload(fileBuff.length, fileBuff); + +// Upload a chunk of the file to the upload session +// Using a fragment size that doesn't divide evenly by 320 KiB results in errors committing some files. +const chunkSize = 327680; +let startFrom = 0; +while (startFrom < fileBuff.length) { + const fileChunk = fileBuff.slice(startFrom, startFrom + chunkSize); + const contentLength = `bytes ${startFrom}-${startFrom + chunkSize}/${fileBuff.length}` + const uploadChunk = await uploadSession.resumableUpload.upload(chunkSize, fileChunk, contentLength); + startFrom += chunkSize; +} ``` ## Search items @@ -311,9 +347,9 @@ const graph = graphfi(...); // Where searchTerm is the query text used to search for items. // Values may be matched across several fields including filename, metadata, and file content. -const search = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").root.search(searchTerm)(); +const search = await graph.users.getById({user id}).drives.getById({drive id}).root.search(searchTerm)(); -const search = await graph.me.drives.getById("{drive id}").root.search(searchTerm)(); +const search = await graph.me.drives.getById({drive id}).root.search(searchTerm)(); ``` @@ -328,9 +364,9 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const item = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").items.getById("{item id}")(); +const item = await graph.users.getById({user id}).drives.getById({drive id}).items.getById("{item id}")(); -const item = await graph.me.drives.getById("{drive id}").items.getById("{item id}")(); +const item = await graph.me.drives.getById({drive id}).items.getById("{item id}")(); ``` @@ -345,7 +381,7 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const item = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getItemByPath("MyFolder/MySubFolder/myFile.docx")(); +const item = await graph.users.getById({user id}).drives.getItemByPath("MyFolder/MySubFolder/myFile.docx")(); const item = await graph.me.drives.getItemByPath("MyFolder/MySubFolder/myFile.docx")(); @@ -422,15 +458,15 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const thumbs = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").items.getById("{item id}").thumbnails(); +const thumbs = await graph.users.getById({user id}).drives.getById({drive id}).items.getById("{item id}").thumbnails(); -const thumbs = await graph.me.drives.getById("{drive id}").items.getById("{item id}").thumbnails(); +const thumbs = await graph.me.drives.getById({drive id}).items.getById("{item id}").thumbnails(); ``` -## Delete drive item +## Delete/Permenently Delete drive item -Using the delete() you delete the current item +Using the delete() you delete the current item. Using .permanentDelete you can permenently delete the current item. ```TypeScript import { graphfi } from "@pnp/graph"; @@ -439,10 +475,10 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const thumbs = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").items.getById("{item id}").delete(); - -const thumbs = await graph.me.drives.getById("{drive id}").items.getById("{item id}").delete(); - +await graph.me.drives.getById({drive id}).items.getById({item id}).delete(); +await graph.me.drives.getById({drive id}).items.getById({item id}).permanentDelete(); +await graph.users.getById({user id}).drives.getById({drive id}).items.getById({item id}).delete(); +await graph.users.getById({user id}).drives.getById({drive id}).items.getById({item id}).permanentDelete(); ``` ## Update drive item metadata @@ -456,9 +492,9 @@ import "@pnp/graph/files"; const graph = graphfi(...); -const update = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").items.getById("{item id}").update({name: "New Name"}); +const update = await graph.users.getById({user id}).drives.getById({drive id}).items.getById("{item id}").update({name: "New Name"}); -const update = await graph.me.drives.getById("{drive id}").items.getById("{item id}").update({name: "New Name"}); +const update = await graph.me.drives.getById({drive id}).items.getById("{item id}").update({name: "New Name"}); ``` @@ -482,9 +518,9 @@ const moveOptions: IItemOptions = { name?: {newName}; }; -const move = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").items.getById("{item id}").move(moveOptions); +const move = await graph.users.getById({user id}).drives.getById({drive id}).items.getById("{item id}").move(moveOptions); -const move = await graph.me.drives.getById("{drive id}").items.getById("{item id}").move(moveOptions); +const move = await graph.me.drives.getById({drive id}).items.getById("{item id}").move(moveOptions); ``` @@ -508,9 +544,9 @@ const copyOptions: IItemOptions = { name?: {newName}; }; -const copy = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").items.getById("{item id}").copy(copyOptions); +const copy = await graph.users.getById({user id}).drives.getById({drive id}).items.getById("{item id}").copy(copyOptions); -const copy = await graph.me.drives.getById("{drive id}").items.getById("{item id}").copy(copyOptions); +const copy = await graph.me.drives.getById({drive id}).items.getById("{item id}").copy(copyOptions); ``` @@ -550,16 +586,16 @@ import { ItemPreviewInfo } from "@microsoft/microsoft-graph-types" const graph = graphfi(...); -const preview: ItemPreviewInfo = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").items.getById("{item id}").preview(); +const preview: ItemPreviewInfo = await graph.users.getById({user id}).drives.getById({drive id}).items.getById("{item id}").preview(); -const preview: ItemPreviewInfo = await graph.me.drives.getById("{drive id}").items.getById("{item id}").preview(); +const preview: ItemPreviewInfo = await graph.me.drives.getById({drive id}).items.getById("{item id}").preview(); const previewOptions: IPreviewOptions = { page: 1, zoom: 90 } -const preview2: ItemPreviewInfo = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").items.getById("{item id}").preview(previewOptions); +const preview2: ItemPreviewInfo = await graph.users.getById({user id}).drives.getById({drive id}).items.getById("{item id}").preview(previewOptions); ``` @@ -576,7 +612,7 @@ const graph = graphfi(...); // Get the changes for the drive items from inception const delta = await graph.me.drive.root.delta()(); -const delta = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").root.delta()(); +const delta = await graph.users.getById({user id}).drives.getById({drive id}).root.delta()(); //You can also loop through the delta changes using the async iterator const driveItems = graph.me.drive.root.delta(); @@ -600,13 +636,16 @@ import { IAnalyticsOptions } from "@pnp/graph/files"; const graph = graphfi(...); // Defaults to lastSevenDays -const analytics = await graph.users.getById("user@tenant.onmicrosoft.com").drives.getById("{drive id}").items.getById("{item id}").analytics()(); +const analytics = await graph.users.getById({user id}).drives.getById({drive id}).items.getById("{item id}").analytics()(); -const analytics = await graph.me.drives.getById("{drive id}").items.getById("{item id}").analytics()(); +const analytics = await graph.me.drives.getById({drive id}).items.getById("{item id}").analytics()(); const analyticOptions: IAnalyticsOptions = { timeRange: "allTime" }; -const analyticsAllTime = await graph.me.drives.getById("{drive id}").items.getById("{item id}").analytics(analyticOptions)(); +const analyticsAllTime = await graph.me.drives.getById({drive id}).items.getById("{item id}").analytics(analyticOptions)(); ``` + +For more information on: +[Sensitivity and Retention Labels (Premium Endpoint)](./files-labels.md) diff --git a/mkdocs.yml b/mkdocs.yml index 3b28d5e07..c56dd035d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -52,6 +52,7 @@ nav: - calendars: 'graph/calendars.md' - 'directory objects': 'graph/directoryobjects.md' - files: 'graph/files.md' + - 'Retention/Sensitivity Labels': 'graph/files-labels.md' - invitations: 'graph/invitations.md' - items: 'graph/items.md' - lists: 'graph/lists.md' diff --git a/packages/graph/files/funcs.ts b/packages/graph/files/funcs.ts index 34c46f260..eed1ac7e4 100644 --- a/packages/graph/files/funcs.ts +++ b/packages/graph/files/funcs.ts @@ -1,7 +1,8 @@ -import { combine } from "@pnp/core"; import { body, InjectHeaders } from "@pnp/queryable"; import { graphPost, graphPut } from "../graphqueryable.js"; -import { DriveItem, IDriveItemAddResult, IFileOptions } from "./types.js"; +import { DriveItem, IFileUploadOptions } from "./types.js"; +import { DriveItem as IDriveItemType } from "@microsoft/microsoft-graph-types"; + export interface ICheckInOptions { checkInAs?: string; @@ -20,7 +21,7 @@ export function encodeSharingUrl(url: string): string { return "u!" + Buffer.from(url, "utf8").toString("base64").replace(/=$/i, "").replace("/", "_").replace("+", "-"); } -export async function driveItemUpload(fileOptions: IFileOptions): Promise { +export async function driveItemUpload(fileOptions: IFileUploadOptions): Promise { let path = "/content"; if (fileOptions.filePathName) { path = `:/${fileOptions.filePathName}:/content`; @@ -34,11 +35,12 @@ export async function driveItemUpload(fileOptions: IFileOptions): Promise { + /** Get the status of teh Resumable Upload URL */ + public get status(): IGraphQueryable { + return GraphQueryable(this); + } + + /** Upload a chunk of the file + * @param byteLength - number - the length of the byte array + * @param buffer - any - the buffer to upload + * @param contentRange - string (Optional) - the content range to upload e.g. `bytes 0-311/312` + */ + public async upload(byteLength: number, buffer: any, contentRange?: string): Promise { + const range = contentRange || `bytes 0-${byteLength - 1}/${byteLength}`; + return graphPut(this, { body: buffer, headers: { "Content-Length": byteLength.toString(), "Content-Range": range } }); + } + + /** Cancel the Resumable Upload */ + public async cancel(): Promise { + return graphDelete(this, body(null)); + } +} +export interface IResumableUpload extends _ResumableUpload { } +export const ResumableUpload = graphInvokableFactory(_ResumableUpload); + + +export async function getUploadSession(resuableUploadOptions: any): Promise<{session: IUploadSessionType; resumableUpload: IResumableUpload}> { + const create = resuableUploadOptions.create != null ? resuableUploadOptions.create : true; + const url = this.toRequestUrl(); + const q = GraphQueryable(`${url}${(create)?`:/${resuableUploadOptions.item.name}:/`:""}createUploadSession`).using(AssignFrom(this)); + + if(resuableUploadOptions.eTag) { + const header = {}; + header[resuableUploadOptions.eTagMatch || "If-Match"] = resuableUploadOptions.eTag; + q.using(InjectHeaders(header)); + } + const postBody: any = {}; + if(resuableUploadOptions.conflictBehavior || resuableUploadOptions.item) { + Object.defineProperty(postBody, "item", {value: {}, writable: true}); + if(resuableUploadOptions.item){ + postBody.item = resuableUploadOptions.item; + } + postBody.item["@microsoft.graph.conflictBehavior"] = resuableUploadOptions.conflictBehavior || "rename"; + } + if(resuableUploadOptions.deferCommit){ + Object.defineProperty(postBody, "deferCommit", { value: resuableUploadOptions.deferCommit }); + } + // Create the upload session + const session = await graphPost(q, body(postBody)); + + // Create a new queryable for the upload session + const uploadQueryable = GraphQueryable(session.uploadUrl).using(CopyFrom(this, "replace", (k) => /(pre|init|send|parse|post|data)/i.test(k))); + + const resumableUpload = ResumableUpload(uploadQueryable); + + return {session, resumableUpload}; +} + +/** + * IResumableUploadOptions for creating a resumable upload for uploading a file. + * @param item - Microsoft Graph - IDriveItemUploadablePropertiesType (Optional), must specify the name property. + * @param create - boolean (Optional) - default true for new files; false for updates + * @param deferCommit - boolean (Optional) + * @param eTag - string (Optional) + * @param eTagMatch - string (Optional) - eTag header "If-Match" or "If-None-Match" + * @param conflictBehavior - string (Optional) - "rename" | "replace" | "fail" rename is default + */ +export interface IResumableUploadOptions { + item?: T; + create?: boolean; + deferCommit?: boolean; + eTag?: string; + eTagMatch?: "If-Match" | "If-None-Match"; + conflictBehavior?: "rename" | "replace" | "fail"; +} diff --git a/packages/graph/files/sites.ts b/packages/graph/files/sites.ts index 7399f8647..499e991ee 100644 --- a/packages/graph/files/sites.ts +++ b/packages/graph/files/sites.ts @@ -1,8 +1,8 @@ import { addProp } from "@pnp/queryable"; import { _Site } from "../sites/types.js"; -import { IDrive, Drive, IDrives, Drives, _DriveItem } from "./types.js"; +import { IDrive, Drive, IDrives, Drives, _DriveItem, _Drive } from "./types.js"; import { checkIn, ICheckInOptions, checkOut } from "./funcs.js"; - +import { IList, List } from "../lists/types.js"; declare module "../sites/types" { interface _Site { @@ -17,9 +17,17 @@ declare module "../sites/types" { addProp(_Site, "drive", Drive); addProp(_Site, "drives", Drives); +addProp(_Drive, "list", List); declare module "./types" { + interface _Drive { + list: IList; + } + interface IDrive { + list: IList; + } + interface _DriveItem { checkIn(checkInOptions?: ICheckInOptions): Promise; checkOut(): Promise; diff --git a/packages/graph/files/types.ts b/packages/graph/files/types.ts index 221b34d6c..d65f79aeb 100644 --- a/packages/graph/files/types.ts +++ b/packages/graph/files/types.ts @@ -1,21 +1,35 @@ import { - GraphInstance, GraphCollection, _GraphInstance, - IGraphInstance, IGraphCollection, _GraphCollection, + IGraphQueryable, graphInvokableFactory, GraphQueryable, graphPatch, graphPost, graphPut, + graphDelete, + GraphInstance, + IGraphInstance, } from "../graphqueryable.js"; -import { Drive as IDriveType, DriveItem as IDriveItemType, ItemPreviewInfo as IDriveItemPreviewInfo } from "@microsoft/microsoft-graph-types"; +import { + Drive as IDriveType, + DriveItem as IDriveItemType, + ItemPreviewInfo as IDriveItemPreviewType, + ThumbnailSet as IThumbnailSetType, + DriveItemVersion as IDriveItemVersionType, + UploadSession as IUploadSessionType, + DriveItemUploadableProperties as IDriveItemUploadablePropertiesType, + SensitivityLabelAssignmentMethod as ISensitivityLabelAssignmentMethodType, + ExtractSensitivityLabelsResult as IExtractSensitivityLabelsResultType, + ItemRetentionLabel as IItemRetentionLabelType, +} from "@microsoft/microsoft-graph-types"; import { combine } from "@pnp/core"; import { defaultPath, getById, IGetById, deleteable, IDeleteable, updateable, IUpdateable, hasDelta, IHasDelta, IDeltaProps } from "../decorators.js"; import { body, BlobParse, CacheNever, InjectHeaders } from "@pnp/queryable"; import { driveItemUpload } from "./funcs.js"; +import { IResumableUpload, IResumableUploadOptions, getUploadSession } from "./resumableUpload.js"; /** * Describes a Drive instance @@ -32,14 +46,6 @@ export class _Drive extends _GraphInstance { return Root(this); } - /** - * Method for retrieving the related list resource, for use with SharePoint drives. - * @returns IGraphInstance - */ - public get list(): IGraphInstance { - return GraphInstance(this, "list"); - } - /** * Method for retrieving recently accessed drive items by the user. * @returns IDriveItems @@ -139,21 +145,16 @@ export class _Root extends _GraphInstance { * Method for retrieving thumbnails of the drive items. * @returns IGraphCollection */ - public get thumbnails(): IGraphCollection { - return GraphCollection(this, "thumbnails"); + public get thumbnails(): IGraphInstance { + return GraphInstance(this, "thumbnails"); } /** * Method for uploading a new file, or updating the contents of an existing file. - * @param fileOptions - IFileOptions - * @param content - any - * @param filePathName - string (Optional) - * e.g. myfile.txt or myfolder/myfile.txt, unneeded for updates - * @param contentType - string (Optional) - * e.g. "application/json; charset=utf-8" for JSON files + * @param fileOptions - IFileOptions object * @returns IDriveItem */ - public async upload(fileOptions: IFileOptions): Promise { + public async upload(fileOptions: IFileUploadOptions): Promise { return Reflect.apply(driveItemUpload, this, [fileOptions]); } } @@ -182,30 +183,20 @@ export class _DriveItem extends _GraphInstance { /** * Method for retrieving thumbnails of the drive items. - * @returns IGraphCollection + * @returns Microsoft Graph - ThumbnailSet */ - public get thumbnails(): IGraphCollection { - return GraphCollection(this, "thumbnails"); + public get thumbnails(): IGraphCollection { + return GraphCollection(this, "thumbnails"); } /** * Method for retrieving the versions of a drive item. * @returns IDriveItemVersionInfo */ - public get versions(): IGraphCollection { + public get versions(): IGraphCollection { return GraphCollection(this, "versions"); } - /** - * Method for moving a drive item - * @param parentReference - { id: string} - reference to destination folder drive item - * @param name - string - name of the file in the destination - * @deprecated (v3.11.0) use `moveItem` - */ - public move(parentReference: { id: "string" }, name: string): Promise { - return graphPatch(this, body({ name, ...parentReference })); - } - /** * Method for moving a file to a new location and/or name. * @param moveOptions - IItemOptions object @@ -235,18 +226,6 @@ export class _DriveItem extends _GraphInstance { return query(); } - /** - * Method for setting the contents of a IDriveItem - * @param content - any - content to upload to the drive item - * @returns - { id: string; name: string; size: number } - * @deprecated (v3.11.0) use `upload` - */ - public setContent(content: any): Promise<{ id: string; name: string; size: number }> { - return graphPut(DriveItem(this, "content"), { - body: content, - }); - } - /** * Method for copying a file to a new location and/or name. * @param copyOptions - IItemOptions @@ -294,39 +273,123 @@ export class _DriveItem extends _GraphInstance { return query(); } + /** + * Method for getting a temporary preview image of a drive item. + * @returns Microsoft Graph - DriveItem + */ + public async follow(): Promise { + return await graphPost(DriveItem(this, "follow"), body(null)); + } + + /** + * Method for getting a temporary preview image of a drive item. + * @returns void + */ + public async unfollow(): Promise { + return await graphPost(DriveItem(this, "unfollow"), body(null)); + } + /** * Method for uploading a new file, or updating the contents of an existing file. - * @param fileOptions - IFileOptions object - * @param content - any - * @param filePathName - string (Optional) - * e.g. myfile.txt or myfolder/myfile.txt, unneeded for updates - * @param contentType - string (Optional) - * e.g. "application/json; charset=utf-8" for JSON files - * @returns IDriveItem + * @param fileOptions - IFileUploadOptions object + * @returns Microsoft Graph - DriveItem */ - public async upload(fileOptions: IFileOptions): Promise { + public async upload(fileOptions: IFileUploadOptions): Promise { return Reflect.apply(driveItemUpload, this, [fileOptions]); } - // TODO: Upload Session for large files - // public uploadSession(fileOptions: IFileOptions): Promise { - - // } + /** + * Method for uploading a new file, or updating the contents of an existing file. + * @param resuableUploadOptions - IResumableUploadOptions object + * @returns session: Microsoft Graph - UploadSession, resumableUpload: IResumableUpload + */ + public async createUploadSession(resuableUploadOptions: IResumableUploadOptions): + Promise<{ session: IUploadSessionType; resumableUpload: IResumableUpload }> { + return Reflect.apply(getUploadSession, this, [resuableUploadOptions]); + } /** * Method for getting a temporary preview image of a drive item. * @param previewOptions - IPreviewOptions (Optional) - * @returns IDriveItemPreviewInfo + * @returns Microsoft Graph - DriveItemPreview */ - public async preview(previewOptions?: IPreviewOptions): Promise { + public async preview(previewOptions?: IPreviewOptions): Promise { return graphPost(DriveItem(this, "preview"), body(previewOptions)); } + + /** + * Method for permanently deleting a driveItem by using its ID. + * @returns void + */ + public async permanentDelete(): Promise { + return graphPost(DriveItem(this, "permanentDelete"), body(null)); + } + + /** + * Method for permanently deleting a driveItem by using its ID. + * @param label: ISensitivityLabel + * @returns string - long running operation status URL + */ + public async assignSensitivityLabel(label: ISensitivityLabel): Promise { + const data: Headers = await graphPost(DriveItem(this, "assignSensitivityLabel"), body(label)); + let result: string = null; + if (data.has("location")) { + result = data.get("location"); + } + + return result; + } + + /** + * Method for permanently deleting a driveItem by using its ID. + * @returns Microsoft Graph - ExtractSensitivityLabelsResult + */ + public async extractSensitivityLabels(): Promise { + return graphPost(DriveItem(this, "extractSensitivityLabels"), body(null)); + } + + /** + * Method for retrieving the retention label of the drive item. + * @returns Microsoft Graph - ItemRetentionLabel + */ + public retentionLabel(): IGraphQueryable { + return GraphQueryable(this, "retentionLabel"); + } + + /** + * Method for locking/unlocking a record of the drive item. + * @returns Microsoft Graph - ItemRetentionLabel + */ + public async recordLocked(locked: boolean): Promise { + const postBody = { + retentionSettings: { + "isRecordLocked": locked, + }, + }; + return graphPatch(DriveItem(this, "retentionLabel"), body(postBody)); + } + + /** + * Method for deleting a retention label from a driveItem. + * @returns void + */ + public async removeRetentionLabel(): Promise { + return graphDelete(DriveItem(this, "retentionLabel")); + } + + /** + * Method for updating a retention label on a driveItem. + * @returns Microsoft Graph - ItemRetentionLabel + */ + public async updateRetentionLabel(name: string): Promise { + const postBody = { name }; + return graphPatch(DriveItem(this, "retentionLabel"), body(postBody)); + } } export interface IDriveItem extends _DriveItem, IDeleteable, IUpdateable { } export const DriveItem = graphInvokableFactory(_DriveItem); - /** * Describes a collection of Drive Item objects * @@ -335,91 +398,87 @@ export const DriveItem = graphInvokableFactory(_DriveItem); export class _DriveItems extends _GraphCollection { /** * Adds a file to this collection of drive items. - * For more upload options please see the .upload method on DriveItem and Root. - * @param filename - string - name of new file - * @param content - string - contents of file - * @param contentType - string - content type for header - default to "application/json" - * @returns IDriveItemAddResult - result with file data and chainable drive item object + * This method allows more control for conflict behavior and affecting other properties of the DriveItem than the .upload method. + * For more upload options please see the .upload method on DriveItem. + * @param fileInfo - IDriveItemAdd + * @returns Microsoft Graph - DriveItem */ - public async add(filename: string, content: string, contentType = "application/json"): Promise { + public async add(fileInfo: IDriveItemAdd): Promise { const postBody = { - name: filename, - file: {}, - "@microsoft.graph.conflictBehavior": "rename", + name: fileInfo.filename, + file: fileInfo.driveItem || {}, + "@microsoft.graph.conflictBehavior": fileInfo.conflictBehavior || "rename", }; const driveItem = await graphPost(this, body(postBody)); const q = DriveItem([this, `${combine("drives", driveItem.parentReference.driveId, "items", driveItem.id)}`], "content"); q.using(InjectHeaders({ - "Content-Type": contentType, + "Content-Type": fileInfo.contentType || "application/json", })); - const data = await graphPut(q, { body: content }); - - return { - data, - driveItem: DriveItem([this, `${combine("drives", driveItem.parentReference.driveId, "items", driveItem.id)}`]), - }; + return await graphPut(q, { body: fileInfo.content }); } /** * Adds a folder to this collection of drive items. - * @param name - string, name of new folder - * @param driveItem - DriveItem (Optional) - override default drive item properties - * @returns IDriveItemAddResult - result with folder data and chainable drive item object - */ - public async addFolder(name: string, driveItem?: any): Promise { - let postBody = { - name, - folder: {}, - "@microsoft.graph.conflictBehavior": "rename", + * @param folderInfo - an object of type IDriveItemAddFolder specifying the properties of the new folder + * @returns Microsoft Graph - DriveItem + */ + public async addFolder(folderInfo: IDriveItemAddFolder): Promise { + const postBody = { + name: folderInfo.name, + folder: folderInfo.driveItem || {}, + "@microsoft.graph.conflictBehavior": folderInfo.conflictBehavior || "rename", }; - if (driveItem) { - if (driveItem.name == null) { - driveItem.name = name; - } - if (driveItem["@microsoft.graph.conflictBehavior"] == null) { - driveItem["@microsoft.graph.conflictBehavior"] = "rename"; - } - postBody = driveItem; - } - const data = await graphPost(this, body(postBody)); - - return { - data, - driveItem: DriveItem([this, `${combine("drives", data.parentReference.driveId, "items", data.id)}`]), - }; + return await graphPost(this, body(postBody)); } } -export interface IDriveItems extends _DriveItems, IGetById { } +export interface IDriveItems extends _DriveItems, IGetById { } export const DriveItems = graphInvokableFactory(_DriveItems); /** - * IDriveItemAddResult + * IDriveItemAdd - for adding a drive item and the corresponding contents + * @param filename - string - file name. + * @param content - any - file content. + * @param contentType - string (Optional) - e.g. "application/json; charset=utf-8" for JSON files + * @param driveItem - DriveItem (Optional). + * @param conflictBehavior - string (Optional) - "rename" | "replace" | "fail" rename is default */ -export interface IDriveItemAddResult { - data: any; - driveItem: IDriveItem; +export interface IDriveItemAdd { + filename: string; + content: string; + contentType?: string; + driveItem?: IDriveItem; + conflictBehavior?: "rename" | "replace" | "fail"; } -export interface IDriveItemVersionInfo { - id: string; - lastModifiedBy: { - user: { - id: string; - displayName: string; - }; - }; - lastModifiedDateTime: string; - size: number; +/** + * IDriveItemAddFolder - for adding a folder drive item + * @param name - string - folder name. + * @param driveItem - DriveItem (Optional). + * @param conflictBehavior - string (Optional) - "rename" | "replace" | "fail" rename is default + */ +export interface IDriveItemAddFolder { + name: string; + driveItem?: IDriveItem; + conflictBehavior?: "rename" | "replace" | "fail"; } +/** + * ISharingWithMeOptions - Sharing file with me options + * @param allowExternal - boolean - To include items shared from external tenants set to true - default false + */ export interface ISharingWithMeOptions { allowExternal: boolean; } +/** + * IItemOptions - for copy/move operations + * @param name - string (Optional) - destination file name. + * @param parentReference - Parent DriveItem Info (Optional). id of Drive Item and driveId of Drive. + */ export interface IItemOptions { parentReference?: { id?: string; @@ -428,13 +487,42 @@ export interface IItemOptions { name?: string; } -export interface IFileOptions { +/** + * IFileUploadOptions for uploading a file. + * @param content - any + * @param filePathName - string (Optional) + * e.g. myfile.txt or myfolder/myfile.txt, unneeded for updates + * @param contentType - string (Optional) + * e.g. "application/json; charset=utf-8" for JSON files + * @param eTag - string (Optional) + * @param eTagMatch - string (Optional) - eTag header "If-Match" or "If-None-Match" + */ +export interface IFileUploadOptions { content: any; filePathName?: string; contentType?: string; + eTag?: string; + eTagMatch?: "If-Match" | "If-None-Match"; } +/** + * IPreviewOptions for getting a file preview image. + * @param page - string/number (Optional) - Page number of document to start at, if applicable. + * @param zoom - number (Optional) - Zoom level to start at, if applicable. + */ export interface IPreviewOptions { page?: string | number; zoom?: number; } + +/** + * ISensitivityLabel - for assigning a sensitivity label to a drive item + * @param sensitivityLabelId - string - the id of the sensitivity label + * @param assignmentMethod - Microsoft Graph SensitivityLabelAssignmentMethod - "standard" | "privileged" | "auto" | "none" + * @param justificationText - string - the justification for the sensitivity label + */ +export interface ISensitivityLabel { + sensitivityLabelId: string; + assignmentMethod: ISensitivityLabelAssignmentMethodType; + justificationText: string; +} diff --git a/packages/graph/files/users.ts b/packages/graph/files/users.ts index a88fb4bbc..1f7d71fb4 100644 --- a/packages/graph/files/users.ts +++ b/packages/graph/files/users.ts @@ -1,7 +1,7 @@ import { addProp, body } from "@pnp/queryable"; import { graphPost } from "../graphqueryable.js"; import { _User } from "../users/types.js"; -import { IDrive, Drive, IDrives, Drives, _Drive, DriveItem, IDriveItem, _DriveItem, IItemOptions } from "./types.js"; +import { IDrive, Drive, IDrives, Drives, _Drive, DriveItem, IDriveItem, _DriveItem, IItemOptions, DriveItems } from "./types.js"; declare module "../users/types" { interface _User { @@ -16,23 +16,23 @@ declare module "../users/types" { addProp(_User, "drive", Drive); addProp(_User, "drives", Drives); +addProp(_Drive, "bundles", DriveItems); +addProp(_Drive, "following", DriveItems); declare module "./types" { interface _Drive { + bundles: IDriveItems; special(specialFolder: SpecialFolder): IDriveItem; } interface IDrive { + bundles: IDriveItems; special(specialFolder: SpecialFolder): IDriveItem; } interface _DriveItem { restore(restoreOptions: IItemOptions): Promise; - follow(): Promise; - unfollow(): void; } interface IDriveItem { restore(restoreOptions: IItemOptions): Promise; - follow(): Promise; - unfollow(): void; } } @@ -54,9 +54,3 @@ export enum SpecialFolder { _DriveItem.prototype.restore = function restore(restoreOptions: IItemOptions): Promise { return graphPost(DriveItem(this, "restore"), body(restoreOptions)); }; -_DriveItem.prototype.follow = function follow(): Promise { - return graphPost(DriveItem(this, "follow"), null); -}; -_DriveItem.prototype.unfollow = function unfollow(): Promise { - return graphPost(DriveItem(this, "unfollow"), null); -}; diff --git a/test/graph/analytics.ts b/test/graph/analytics.ts index 74b8f1757..5e9b5343f 100644 --- a/test/graph/analytics.ts +++ b/test/graph/analytics.ts @@ -67,7 +67,7 @@ describe("Analytics", function () { const fo = JSON.parse(JSON.stringify(fileOptions)); fo.filePathName = testFileName; const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.upload(fo); - const analytics = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.data.id).analytics(); + const analytics = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).analytics(); return expect(analytics).to.haveOwnProperty("@odata.context").eq("https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.itemActivityStat"); }); @@ -98,7 +98,7 @@ describe("Analytics", function () { fo.filePathName = testFileName; const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.upload(fo); const options: IAnalyticsOptions = { timeRange: "allTime" }; - const analytics = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.data.id).analytics(options); + const analytics = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).analytics(options); return expect(analytics).to.haveOwnProperty("@odata.context").eq("https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.itemActivityStat"); }); diff --git a/test/graph/files.ts b/test/graph/files.ts index 4b4c267a4..16a3b73b0 100644 --- a/test/graph/files.ts +++ b/test/graph/files.ts @@ -4,8 +4,11 @@ import * as fs from "fs"; import findupSync from "findup-sync"; import "@pnp/graph/users"; import "@pnp/graph/files"; +import "@pnp/graph/files/sites"; import { getRandomString, stringIsNullOrEmpty } from "@pnp/core"; -import { IItemOptions } from "@pnp/graph/files/types"; +import { IDriveItemAdd, IDriveItemAddFolder, IFileUploadOptions, IItemOptions } from "@pnp/graph/files/types"; +import { IResumableUploadOptions } from "@pnp/graph/files"; +import { DriveItemUploadableProperties } from "@microsoft/microsoft-graph-types"; // give ourselves a single reference to the projectRoot const projectRoot = path.resolve(path.dirname(findupSync("package.json"))); @@ -13,11 +16,19 @@ const projectRoot = path.resolve(path.dirname(findupSync("package.json"))); describe("Drive", function () { let testUserName = ""; let driveId = null; - const fileOptions = { + const fileOptions: IFileUploadOptions = { content: "This is some test content", filePathName: "pnpTest.txt", contentType: "text/plain;charset=utf-8", }; + + const fileROOptions: IResumableUploadOptions = { + item: { + name: "TestDocument2.docx", + fileSize: null, + }, + }; + const testConvert = path.join(projectRoot, "test/graph/assets", "testconvert.docx"); // Ensure we have the data to test against @@ -53,10 +64,13 @@ describe("Drive", function () { }); it("Get Drive List", async function () { + if (stringIsNullOrEmpty(this.pnp.settings.graph.id)) { + this.skip(); + } if (stringIsNullOrEmpty(driveId)) { this.skip(); } - const list = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).list(); + const list = await this.pnp.graph.sites.getById(this.pnp.settings.graph.id).drive.list(); return expect(list).is.not.null; }); @@ -95,9 +109,9 @@ describe("Drive", function () { const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.upload(fo); if (children != null) { // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); } - return expect(children.data.id).length.greaterThan(0); + return expect(children.id).length.greaterThan(0); }); it("Add Drive Root Folder Item (Add)", async function () { @@ -107,12 +121,17 @@ describe("Drive", function () { const testFileName = `TestFile_${getRandomString(4)}.json`; const fo = JSON.parse(JSON.stringify(fileOptions)); fo.filePathName = testFileName; - const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.children.add(testFileName, fileOptions.content, fileOptions.contentType); + const driveItemAdd: IDriveItemAdd = { + filename: testFileName, + content: fileOptions.content, + contentType: fileOptions.contentType, + }; + const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.children.add(driveItemAdd); if (children != null) { // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); } - return expect(children.data.id).length.greaterThan(0); + return expect(children.id).length.greaterThan(0); }); it("Add New Drive Folder", async function () { @@ -120,12 +139,15 @@ describe("Drive", function () { this.skip(); } const testFolderName = `TestFolder_${getRandomString(4)}`; - const folder = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.children.addFolder(testFolderName); + const driveItemAdd: IDriveItemAddFolder = { + name: testFolderName, + }; + const folder = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.children.addFolder(driveItemAdd); if (folder != null) { // Clean up test file - await folder.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(folder.id).delete(); } - return expect(folder.data.id).length.greaterThan(0); + return expect(folder.id).length.greaterThan(0); }); it("Search Drive Item", async function () { @@ -141,7 +163,7 @@ describe("Drive", function () { if (children != null) { searchResults = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.search(searchString)(); // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); } return expect(searchResults).to.not.be.null; }); @@ -156,11 +178,11 @@ describe("Drive", function () { const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.upload(fo); let driveItemId; if (children != null) { - driveItemId = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.data.id)(); + driveItemId = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id)(); // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); } - return expect(driveItemId.id).to.be.eq(children.data.id); + return expect(driveItemId.id).to.be.eq(children.id); }); it("Get Drive Item By Path", async function () { @@ -175,9 +197,9 @@ describe("Drive", function () { if (children != null) { driveItemId = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemByPath(testFileName)(); // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); } - return expect(driveItemId.id).to.be.eq(children.data.id); + return expect(driveItemId.id).to.be.eq(children.id); }); it("Get Drive Items By Path", async function () { @@ -186,17 +208,18 @@ describe("Drive", function () { } let driveItems; const testFolderName = `TestFolder_${getRandomString(4)}`; - const folder = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.children.addFolder(testFolderName); + const folder = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.children.addFolder({name: testFolderName}); if (folder != null) { const testFileName = `${getRandomString(4)}.txt`; - const children = await folder.driveItem.upload({ filePathName: testFileName, content: "My File Content String" }); + const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(folder.id) + .upload({ filePathName: testFileName, content: "My File Content String" }); if (children != null) { driveItems = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemsByPath(testFolderName)(); // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); } // Clean up test folder - await folder.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); } return expect(driveItems.length).to.be.gt(0); }); @@ -229,9 +252,30 @@ describe("Drive", function () { let driveItemId = null; if (children != null) { // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); + try { + driveItemId = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id)(); + } catch (err) { + // Do nothing as this is the expected outcome + } + } + return expect(driveItemId).to.be.null; + }); + + it("Permanently Delete Drive Item", async function () { + if (stringIsNullOrEmpty(driveId)) { + this.skip(); + } + const testFileName = `${getRandomString(4)}.txt`; + const fo = JSON.parse(JSON.stringify(fileOptions)); + fo.filePathName = testFileName; + const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.upload(fo); + let driveItemId = null; + if (children != null) { + // Clean up test file + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).permanentDelete(); try { - driveItemId = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.data.id)(); + driveItemId = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id)(); } catch (err) { // Do nothing as this is the expected outcome } @@ -250,10 +294,10 @@ describe("Drive", function () { const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.upload(fo); let driveItemUpdate; if (children != null) { - await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.data.id).update({ name: testFileName2 }); - driveItemUpdate = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.data.id)(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).update({ name: testFileName2 }); + driveItemUpdate = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id)(); // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); } return expect(driveItemUpdate.name).to.eq(testFileName2); }); @@ -274,9 +318,9 @@ describe("Drive", function () { parentReference: { driveId: r.parentReference.driveId, id: r.id }, name: testFileName2, }; - fileCopy = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.data.id).copyItem(copyOptions); + fileCopy = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).copyItem(copyOptions); // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); if (fileCopy.length > 0) { await await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemByPath(testFileName2).delete(); } @@ -290,24 +334,24 @@ describe("Drive", function () { } const testFileName = `${getRandomString(4)}.txt`; const testFileName2 = `${getRandomString(4)}.txt`; - const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.children.add(testFileName, "My File Content String"); + const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.children.add({filename: testFileName, content: "My File Content String"}); let driveItemUpdate; if (children != null) { const testFolderName = `TestFolder_${getRandomString(4)}`; - const folder = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.children.addFolder(testFolderName); + const folder = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.children.addFolder({name: testFolderName}); if (folder != null) { const moveOptions: IItemOptions = { - parentReference: { driveId: folder.data.parentReference.driveId, id: folder.data.id }, + parentReference: { driveId: folder.parentReference.driveId, id: folder.id }, name: testFileName2, }; - driveItemUpdate = await children.driveItem.moveItem(moveOptions); + driveItemUpdate = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).moveItem(moveOptions); // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); // Clean up test folder - await folder.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(folder.id).delete(); } else { // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); } } return expect(driveItemUpdate.name).to.eq(testFileName2); @@ -327,9 +371,9 @@ describe("Drive", function () { const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.upload(fo); let convertDriveItem = null; if (children != null) { - convertDriveItem = await children.driveItem.convertContent("pdf"); + convertDriveItem = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).convertContent("pdf"); // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); } return expect(convertDriveItem).is.not.null; }); @@ -344,10 +388,52 @@ describe("Drive", function () { const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.upload(fo); let previewDriveItem = null; if (children != null) { - previewDriveItem = await children.driveItem.preview(); + previewDriveItem = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).preview(); // Clean up test file - await children.driveItem.delete(); + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); } return expect(previewDriveItem).to.haveOwnProperty("getUrl"); }); + + it("Follow Drive Item", async function () { + if (stringIsNullOrEmpty(driveId)) { + this.skip(); + } + const testFileName = `${getRandomString(4)}.txt`; + const fo = JSON.parse(JSON.stringify(fileOptions)); + fo.filePathName = testFileName; + const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.upload(fo); + let followDriveItem = null; + if (children != null) { + // Clean up test file + followDriveItem = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).follow(); + // Clean up test file + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); + } + return expect(followDriveItem).to.be.null; + }); + + it("UnFollow Drive Item", async function () { + if (stringIsNullOrEmpty(driveId)) { + this.skip(); + } + const testFileName = `${getRandomString(4)}.txt`; + const fo = JSON.parse(JSON.stringify(fileOptions)); + fo.filePathName = testFileName; + const children = await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).root.upload(fo); + let unfollowDriveItem = null; + if (children != null) { + // Set up test file + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).follow(); + try{ + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).unfollow(); + unfollowDriveItem = true; + }catch(err){ + unfollowDriveItem = false; + } + // Clean up test file + await this.pnp.graph.users.getById(testUserName).drives.getById(driveId).getItemById(children.id).delete(); + } + return expect(unfollowDriveItem).to.be.true; + }); }); diff --git a/test/graph/planner.ts b/test/graph/planner.ts index c535e4943..1b5a9dfc6 100644 --- a/test/graph/planner.ts +++ b/test/graph/planner.ts @@ -5,7 +5,7 @@ import { IPlanAdd } from "@pnp/graph/planner"; import getValidUser from "./utilities/getValidUser.js"; // Tests can't be run until planner support application permissions, incomplete -describe.only("Planner", function () { +describe("Planner", function () { const planTemplate: IPlanAdd = { container: { url: "",