From 1c2be24b5d61ceee29a52494fbe28f4d8fa32a2c Mon Sep 17 00:00:00 2001 From: Beau Cameron Date: Fri, 22 Dec 2023 08:32:57 -0500 Subject: [PATCH 1/4] Places Add New Places module New Tests Updated Docs --- docs/graph/places.md | 77 +++++++++++++++++++++++++++++++ packages/graph/places/index.ts | 23 ++++++++++ packages/graph/places/types.ts | 83 ++++++++++++++++++++++++++++++++++ test/graph/places.ts | 61 +++++++++++++++++++++++++ 4 files changed, 244 insertions(+) create mode 100644 docs/graph/places.md create mode 100644 packages/graph/places/index.ts create mode 100644 packages/graph/places/types.ts create mode 100644 test/graph/places.ts diff --git a/docs/graph/places.md b/docs/graph/places.md new file mode 100644 index 000000000..37868766c --- /dev/null +++ b/docs/graph/places.md @@ -0,0 +1,77 @@ +# @pnp/graph/places + +This module allows you to work with Exchange resources such as rooms and roomLists. + +## IPlaces, Places, IPlace, Place + +[![Selective Imports Banner](https://img.shields.io/badge/Selective%20Imports-informational.svg)](../concepts/selective-imports.md) + +## Get all rooms in a Tenant + +This example shows how to retrieve all rooms in a tenant + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/places"; + +const graph = graphfi(...); +const rooms = graph.places.rooms(); +``` +## Get all roomlists in a tenant + +This example shows how to retrieve all roomlists in a tenant + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/places"; + +const graph = graphfi(...); +const roomLists = graph.places.roomlists(); + +``` +## Get Rooms in room list + +This example shows how to retrieve all rooms in a roomlist + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/places"; + +const graph = graphfi(...); +const roomsByList = await graph.places.roomLists.getById("05fb1ae2-6de6-4fa8-b852-fb0cf671b896").rooms(); + +``` +## Get Place by Id + +This example shows how to retrieve a place (room, roomlist) by id + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/places"; + +const graph = graphfi(...); + +const roomById = await graph.places.getById("05fb1ae2-6de6-4fa8-b852-fb0cf671b896")(); + +``` +## Update a place + +This example shows how to update a place (room, roomlist) + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/places"; + +const graph = graphfi(...); + +var updatedRoom = await graph.places.getById("05fb1ae2-6de6-4fa8-b852-fb0cf671b896").update( + { + '@odata.type': "microsoft.graph.room", + "nickname": "Conf Room", + "building": "1", + "label": "100", + "capacity": 50, + "isWheelChairAccessible": false, + }); + +``` diff --git a/packages/graph/places/index.ts b/packages/graph/places/index.ts new file mode 100644 index 000000000..9aaae3cb7 --- /dev/null +++ b/packages/graph/places/index.ts @@ -0,0 +1,23 @@ +import { GraphFI } from "../fi.js"; +import { IPlaces, Places } from "./types.js"; + +export { + Places, + IPlaces, + Place, + IPlace, +} from "./types.js"; + +declare module "../fi" { + interface GraphFI { + readonly places: IPlaces; + } +} + +Reflect.defineProperty(GraphFI.prototype, "places", { + configurable: true, + enumerable: true, + get: function (this: GraphFI) { + return this.create(Places); + }, +}); diff --git a/packages/graph/places/types.ts b/packages/graph/places/types.ts new file mode 100644 index 000000000..35df3b9c1 --- /dev/null +++ b/packages/graph/places/types.ts @@ -0,0 +1,83 @@ +import { defaultPath, updateable, IUpdateable, getById, IGetById } from "../decorators.js"; +import { _GraphCollection, _GraphInstance, graphInvokableFactory } from "../graphqueryable.js"; +import { Room as IRoomType, RoomList as IRoomListType, Place as IPlaceType } from "@microsoft/microsoft-graph-types"; + +/** + * Place + */ +@updateable() +export class _Place extends _GraphInstance { } +export interface IPlace extends _Place, IUpdateable { } +export const Place = graphInvokableFactory(_Place); + +/** + * Places + */ +@defaultPath("places") +@getById(Place) +export class _Places extends _GraphInstance { + + /** + * Gets all rooms in a tenant + */ + public get rooms(): IRooms { + return Rooms(this); + } + + /** + * Gets all roomLists in a tenant + */ + public get roomLists(): IRoomlists { + return RoomLists(this); + } +} +export interface IPlaces extends _Places, IGetById { } +export const Places = graphInvokableFactory(_Places); + +/** + * RoomList + */ +export class _RoomList extends _GraphInstance { + /** + * Gets all rooms in a roomList + */ + public get rooms(): IRooms { + return Rooms(this, "rooms"); + } +} +export interface IRoomlist extends _RoomList {} +export const RoomList = graphInvokableFactory(_RoomList); + +/** + * RoomLists + */ +@defaultPath("microsoft.graph.roomList") +@getById(RoomList) +export class _RoomLists extends _GraphCollection {} +export interface IRoomlists extends _RoomLists, IGetById { } +export const RoomLists = graphInvokableFactory(_RoomLists); + +/** + * Room + */ +export class _Room extends _GraphInstance {} +export interface IRoom extends _Rooms { } +export const Room = graphInvokableFactory(_Room); + +/** + * Rooms + */ +@defaultPath("microsoft.graph.room") +@getById(Room) +export class _Rooms extends _GraphCollection {} +export interface IRooms extends _Rooms, IGetById { } +export const Rooms = graphInvokableFactory(_Rooms); + +export interface IPlacesType { + readonly rooms: IRoomType[]; + readonly roomLists: IRoomListType[]; +} + +export interface IUpdatePlaceProps extends IRoomType, IRoomListType { + "@odata.type": string; +} diff --git a/test/graph/places.ts b/test/graph/places.ts new file mode 100644 index 000000000..2812c3f0e --- /dev/null +++ b/test/graph/places.ts @@ -0,0 +1,61 @@ +import { expect } from "chai"; +import { pnpTest } from "../pnp-test.js"; +import "@pnp/graph/places"; +import { getRandomString } from "@pnp/core"; + +describe("Places", function () { + + before(async function () { + + if (!this.pnp.settings.enableWebTests) { + this.skip(); + } + }); + + it("get rooms", pnpTest("7b9a74f6-22f3-4859-bae2-ddb3cba99324", async function () { + const rooms = await this.pnp.graph.places.rooms(); + return expect(rooms).to.be.an("array"); + })); + + it("get roomlists", pnpTest("25f24e27-420f-4641-b69d-962597528fdd", async function () { + const roomLists = await this.pnp.graph.places.roomLists(); + return expect(roomLists).to.be.an("array"); + })); + + it("get room in roomlist", pnpTest("25f24e27-420f-4641-b69d-962597528fdd", async function () { + const roomLists = await this.pnp.graph.places.roomLists(); + if(roomLists.length > 0){ + const rooms = await this.pnp.graph.places.roomLists.getById(roomLists[0].id).rooms(); + return expect(rooms).to.be.an("array"); + } + this.skip(); + })); + + it("get place - getById()", pnpTest("b4307200-c208-4246-a571-4ebe06c54f70", async function () { + const rooms = await this.pnp.graph.places.rooms(); + if(rooms.length > 0){ + const room = await this.pnp.graph.places.getById(rooms[0].id)(); + return expect(room).to.haveOwnProperty("id"); + } + this.skip(); + })); + + it.skip("update place", pnpTest("7c3f4418-f1b7-46bf-8944-ee7c7cf896ff", async function () { + const rooms = await this.pnp.graph.places.rooms(); + const randomName = `Conf Room_${getRandomString(4)}`; + if(rooms.length > 0){ + const room = await this.pnp.graph.places.getById(rooms[0].id)(); + const update = await this.pnp.graph.places.getById(room.id).update( + { + "@odata.type": "microsoft.graph.room", + "nickname": randomName, + "building": "1", + "label": "100", + "capacity": 50, + "isWheelChairAccessible": false, + }); + return expect(update.nickname).to.be(randomName); + } + this.skip(); + })); +}); From 42c2ce9c95c8abef7796bb656f7b08513c46df6f Mon Sep 17 00:00:00 2001 From: Beau Cameron Date: Fri, 29 Dec 2023 14:13:01 -0700 Subject: [PATCH 2/4] Update docs --- docs/graph/places.md | 2 +- packages/graph/places/index.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/graph/places.md b/docs/graph/places.md index 37868766c..c95e9aa29 100644 --- a/docs/graph/places.md +++ b/docs/graph/places.md @@ -2,7 +2,7 @@ This module allows you to work with Exchange resources such as rooms and roomLists. -## IPlaces, Places, IPlace, Place +## IPlaces, Places, IPlace, Place, IRoom, Room, IRoomList, RoomList, IRoomLists, RoomLists [![Selective Imports Banner](https://img.shields.io/badge/Selective%20Imports-informational.svg)](../concepts/selective-imports.md) diff --git a/packages/graph/places/index.ts b/packages/graph/places/index.ts index 9aaae3cb7..a4380f7af 100644 --- a/packages/graph/places/index.ts +++ b/packages/graph/places/index.ts @@ -6,6 +6,12 @@ export { IPlaces, Place, IPlace, + Room, + IRoom, + RoomList, + IRoomlist, + RoomLists, + IRoomlists } from "./types.js"; declare module "../fi" { From e6de448c9698e54d13f823ead4fd3480431607e6 Mon Sep 17 00:00:00 2001 From: Beau Cameron Date: Fri, 29 Dec 2023 14:14:59 -0700 Subject: [PATCH 3/4] Fix Eslint --- packages/graph/places/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graph/places/index.ts b/packages/graph/places/index.ts index a4380f7af..2bd10ef77 100644 --- a/packages/graph/places/index.ts +++ b/packages/graph/places/index.ts @@ -11,7 +11,7 @@ export { RoomList, IRoomlist, RoomLists, - IRoomlists + IRoomlists, } from "./types.js"; declare module "../fi" { From a81acb0b0664aecf99aead2152b5af5cb1ccf487 Mon Sep 17 00:00:00 2001 From: Beau Cameron Date: Thu, 4 Jan 2024 10:59:12 -0700 Subject: [PATCH 4/4] Fixing inheritence and clean up --- packages/graph/places/types.ts | 9 +-- packages/logging/index.js | 97 +++++++++++++++++++++++++++++++ packages/logging/listeners.js | 101 +++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 packages/logging/index.js create mode 100644 packages/logging/listeners.js diff --git a/packages/graph/places/types.ts b/packages/graph/places/types.ts index 35df3b9c1..e9695a553 100644 --- a/packages/graph/places/types.ts +++ b/packages/graph/places/types.ts @@ -61,7 +61,7 @@ export const RoomLists = graphInvokableFactory(_RoomLists); * Room */ export class _Room extends _GraphInstance {} -export interface IRoom extends _Rooms { } +export interface IRoom extends _Room { } export const Room = graphInvokableFactory(_Room); /** @@ -69,15 +69,10 @@ export const Room = graphInvokableFactory(_Room); */ @defaultPath("microsoft.graph.room") @getById(Room) -export class _Rooms extends _GraphCollection {} +export class _Rooms extends _GraphCollection {} export interface IRooms extends _Rooms, IGetById { } export const Rooms = graphInvokableFactory(_Rooms); -export interface IPlacesType { - readonly rooms: IRoomType[]; - readonly roomLists: IRoomListType[]; -} - export interface IUpdatePlaceProps extends IRoomType, IRoomListType { "@odata.type": string; } diff --git a/packages/logging/index.js b/packages/logging/index.js new file mode 100644 index 000000000..84c854091 --- /dev/null +++ b/packages/logging/index.js @@ -0,0 +1,97 @@ +export * from "./listeners.js"; +/** + * A set of logging levels + */ +export var LogLevel; +(function (LogLevel) { + LogLevel[LogLevel["Verbose"] = 0] = "Verbose"; + LogLevel[LogLevel["Info"] = 1] = "Info"; + LogLevel[LogLevel["Warning"] = 2] = "Warning"; + LogLevel[LogLevel["Error"] = 3] = "Error"; + LogLevel[LogLevel["Off"] = 99] = "Off"; +})(LogLevel || (LogLevel = {})); +const _subscribers = []; +let _activeLogLevel = 2 /* Warning */; +/** + * Class used to subscribe ILogListener and log messages throughout an application + * + */ +export class Logger { + /** + * Gets or sets the active log level to apply for log filtering + */ + static get activeLogLevel() { + return _activeLogLevel; + } + static set activeLogLevel(value) { + _activeLogLevel = value; + } + /** + * Adds ILogListener instances to the set of subscribed listeners + * + * @param listeners One or more listeners to subscribe to this log + */ + static subscribe(...listeners) { + _subscribers.push(...listeners); + } + /** + * Clears the subscribers collection, returning the collection before modification + */ + static clearSubscribers() { + const s = _subscribers.slice(0); + _subscribers.length = 0; + return s; + } + /** + * Gets the current subscriber count + */ + static get count() { + return _subscribers.length; + } + /** + * Writes the supplied string to the subscribed listeners + * + * @param message The message to write + * @param level [Optional] if supplied will be used as the level of the entry (Default: LogLevel.Info) + */ + static write(message, level = 1 /* Info */) { + Logger.log({ level: level, message: message }); + } + /** + * Writes the supplied string to the subscribed listeners + * + * @param json The json object to stringify and write + * @param level [Optional] if supplied will be used as the level of the entry (Default: LogLevel.Info) + */ + static writeJSON(json, level = 1 /* Info */) { + Logger.write(JSON.stringify(json), level); + } + /** + * Logs the supplied entry to the subscribed listeners + * + * @param entry The message to log + */ + static log(entry) { + if (entry !== undefined && Logger.activeLogLevel <= entry.level) { + _subscribers.map(subscriber => subscriber.log(entry)); + } + } + /** + * Logs an error object to the subscribed listeners + * + * @param err The error object + */ + static error(err) { + Logger.log({ data: err, level: 3 /* Error */, message: err.message }); + } +} +export function PnPLogging(activeLevel) { + return (instance) => { + instance.on.log(function (message, level) { + if (activeLevel <= level) { + _subscribers.map(subscriber => subscriber.log({ level, message })); + } + }); + return instance; + }; +} diff --git a/packages/logging/listeners.js b/packages/logging/listeners.js new file mode 100644 index 000000000..fdc636b47 --- /dev/null +++ b/packages/logging/listeners.js @@ -0,0 +1,101 @@ +export function ConsoleListener(prefix, colors) { + return new _ConsoleListener(prefix, colors); +} +function withColor(msg, color, logMethod) { + if (typeof color === "undefined") { + logMethod(msg); + } + else { + logMethod(`%c${msg}`, `color:${color}`); + } +} +/** + * Formats the message + * + * @param entry The information to format into a string + */ +function entryToString(entry, prefix) { + const msg = []; + if (prefix.length > 0) { + msg.push(`${prefix} -`); + } + msg.push(entry.message); + if (entry.data !== undefined) { + try { + msg.push("Data: " + JSON.stringify(entry.data)); + } + catch (e) { + msg.push(`Data: Error in stringify of supplied data ${e}`); + } + } + return msg.join(" "); +} +// index order matters, this is a lookup table based on the corresponding LogLevel value +const colorProps = ["verbose", "info", "warning", "error"]; +/** + * Implementation of LogListener which logs to the console + * + */ +class _ConsoleListener { + /** + * Makes a new one + * + * @param prefix Optional text to include at the start of all messages (useful for filtering) + * @param colors Optional text color settings + */ + constructor(_prefix = "", _colors = {}) { + this._prefix = _prefix; + this._colors = _colors; + } + /** + * Any associated data that a given logging listener may choose to log or ignore + * + * @param entry The information to be logged + */ + log(entry) { + let logMethod = console.log; + switch (entry.level) { + case 3 /* Error */: + logMethod = console.error; + break; + case 2 /* Warning */: + logMethod = console.warn; + break; + case 0 /* Verbose */: + logMethod = console.debug; + break; + case 1 /* Info */: + logMethod = console.info; + break; + default: + logMethod = console.log; + } + withColor(entryToString(entry, this._prefix), this._colors[colorProps[entry.level]], logMethod); + } +} +export function FunctionListener(impl) { + return new _FunctionListener(impl); +} +/** + * Implementation of LogListener which logs to the supplied function + * + */ +class _FunctionListener { + /** + * Creates a new instance of the FunctionListener class + * + * @constructor + * @param method The method to which any logging data will be passed + */ + constructor(method) { + this.method = method; + } + /** + * Any associated data that a given logging listener may choose to log or ignore + * + * @param entry The information to be logged + */ + log(entry) { + this.method(entry); + } +}