Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert Accidental Async Iterator update from V4 #2834

Merged
merged 1 commit into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -567,4 +567,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Dropped entire package, no longer needed

- config-store:
- Dropped entire package.
- Dropped entire package.
2 changes: 1 addition & 1 deletion debug/launch/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ export async function Example(settings: any) {
});

process.exit(0);
}
}
10 changes: 0 additions & 10 deletions docs/graph/onedrive.md
Original file line number Diff line number Diff line change
Expand Up @@ -577,16 +577,6 @@ const delta: IDeltaItems = await graph.users.getById("[email protected]

// Get the changes for the drive items from token
const delta: IDeltaItems = await graph.me.drive.root.delta("{token}")();

// consume all the values using async iterator
for await (const val of delta.next.paged()) {
console.log(JSON.stringify(val, null, 2));
}

// consume all the values using async iterator in pages of 20
for await (const val of delta.next.top(20).paged()) {
console.log(JSON.stringify(val, null, 2));
}
```

## Get Drive Item Analytics
Expand Down
71 changes: 20 additions & 51 deletions packages/graph/behaviors/paged.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,81 +5,50 @@ import { ConsistencyLevel } from "./consistency-level.js";

export interface IPagedResult {
count: number;
value: any | any[] | null;
value: any[] | null;
hasNext: boolean;
nextLink: string;
next(): Promise<IPagedResult>;
}

/**
* A function that will take a collection defining IGraphQueryableCollection and return the count of items
* in that collection. Not all Graph collections support Count.
*
* @param col The collection to count
* @returns number representing the count
*/
export async function Count<T>(col: IGraphQueryableCollection<T>): Promise<number> {

const q = GraphQueryableCollection(col).using(Paged(), ConsistencyLevel());
q.query.set("$count", "true");
q.top(1);

const y: IPagedResult = await q();
return y.count;
}

/**
* Configures a collection query to returned paged results via async iteration
* Configures a collection query to returned paged results
*
* @param col Collection forming the basis of the paged collection, this param is NOT modified
* @returns A duplicate collection which will return paged results
*/
export function AsAsyncIterable<T>(col: IGraphQueryableCollection<T>): AsyncIterable<T> {
export function AsPaged(col: IGraphQueryableCollection, supportsCount = false): IGraphQueryableCollection {

const q = GraphQueryableCollection(col).using(Paged(), ConsistencyLevel());
const q = GraphQueryableCollection(col).using(Paged(supportsCount), ConsistencyLevel());

const queryParams = ["$search", "$top", "$select", "$expand", "$filter", "$orderby"];

if (supportsCount) {

// we might be constructing our query with a next url that will already contain $count so we need
// to ensure we don't add it again, likewise if it is already in our query collection we don't add it again
if (!q.query.has("$count") && !/\$count=true/i.test(q.toUrl())) {
q.query.set("$count", "true");
}

queryParams.push("$count");
}

for (let i = 0; i < queryParams.length; i++) {
const param = col.query.get(queryParams[i]);
if (objectDefinedNotNull(param)) {
q.query.set(queryParams[i], param);
}
}

return {

[Symbol.asyncIterator]() {
return <AsyncIterator<T>>{

_next: q,

async next() {

if (this._next === null) {
return { done: true, value: undefined };
}

const result: IPagedResult = await this._next();

if (result.hasNext) {
this._next = GraphQueryableCollection([this._next, result.nextLink]);
return { done: false, value: result.value };
} else {
this._next = null;
return { done: false, value: result.value };
}
},
};
},
};
return q;
}

/**
* Behavior that converts results to pages when used with a collection (exposed through the paged method of GraphCollection)
*
* @returns A TimelinePipe used to configure the queryable
*/
export function Paged(): TimelinePipe {
export function Paged(supportsCount = false): TimelinePipe {

return (instance: IGraphQueryable) => {

Expand All @@ -90,14 +59,14 @@ export function Paged(): TimelinePipe {
const json = txt.replace(/\s/ig, "").length > 0 ? JSON.parse(txt) : {};
const nextLink = json["@odata.nextLink"];

const count = hOP(json, "@odata.count") ? parseInt(json["@odata.count"], 10) : -1;
const count = supportsCount && hOP(json, "@odata.count") ? parseInt(json["@odata.count"], 10) : 0;

const hasNext = !stringIsNullOrEmpty(nextLink);

result = {
count,
hasNext,
nextLink: hasNext ? nextLink : null,
next: () => (hasNext ? AsPaged(GraphQueryableCollection([instance, nextLink]), supportsCount)() : null),
value: parseODataJSON(json),
};

Expand Down
15 changes: 13 additions & 2 deletions packages/graph/directory-objects/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DirectoryObject as IDirectoryObjectType } from "@microsoft/microsoft-gr
import { defaultPath, getById, IGetById, deleteable, IDeleteable } from "../decorators.js";
import { body } from "@pnp/queryable";
import { graphPost } from "../operations.js";
import { Count } from "../behaviors/paged.js";
import { AsPaged, IPagedResult } from "../behaviors/paged.js";

/**
* Represents a Directory Object entity
Expand Down Expand Up @@ -65,7 +65,18 @@ export class _DirectoryObjects<GetType = IDirectoryObjectType[]> extends _GraphQ
* If the resource doesn't support count, this value will always be zero
*/
public async count(): Promise<number> {
return Count(this);
const q = AsPaged(this, true);
const r: IPagedResult = await q.top(1)();
return r.count;
}

/**
* Allows reading through a collection as pages of information whose size is determined by top or the api method's default
*
* @returns an object containing results, the ability to determine if there are more results, and request the next page of results
*/
public paged(): Promise<IPagedResult> {
return AsPaged(this, true)();
}
}
export interface IDirectoryObjects extends _DirectoryObjects, IGetById<IDirectoryObjectType> { }
Expand Down
11 changes: 6 additions & 5 deletions packages/graph/graphqueryable.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isArray } from "@pnp/core";
import { IInvokable, Queryable, queryableFactory } from "@pnp/queryable";
import { ConsistencyLevel } from "./behaviors/consistency-level.js";
import { AsAsyncIterable } from "./behaviors/paged.js";
import { AsPaged, IPagedResult } from "./behaviors/paged.js";

export type GraphInit = string | IGraphQueryable | [IGraphQueryable, string];

Expand Down Expand Up @@ -167,17 +167,18 @@ export class _GraphQueryableCollection<GetType = any[]> extends _GraphQueryable<
* If the resource doesn't support count, this value will always be zero
*/
public async count(): Promise<number> {
// TODO::do we want to do this, or just attach count to the collections that support it? we could use a decorator for countable on the few collections that support count.
return -1;
const q = AsPaged(this);
const r: IPagedResult = await q.top(1)();
return r.count;
}

/**
* Allows reading through a collection as pages of information whose size is determined by top or the api method's default
*
* @returns an object containing results, the ability to determine if there are more results, and request the next page of results
*/
public paged(): AsyncIterable<GetType> {
return AsAsyncIterable(this);
public paged(): Promise<IPagedResult> {
return AsPaged(this)();
}
}
export interface IGraphQueryableCollection<GetType = any[]> extends _GraphQueryableCollection<GetType> { }
Expand Down
11 changes: 5 additions & 6 deletions packages/graph/onedrive/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { defaultPath, getById, IGetById, deleteable, IDeleteable, updateable, IU
import { body, BlobParse, CacheNever, errorCheck, InjectHeaders } from "@pnp/queryable";
import { graphPatch, graphPost, graphPut } from "../operations.js";
import { driveItemUpload } from "./funcs.js";
import { AsPaged } from "../behaviors/paged.js";

/**
* Describes a Drive instance
Expand Down Expand Up @@ -149,17 +150,15 @@ export class _Root extends _GraphQueryableInstance<IDriveItemType> {
public delta(token?: string): IGraphQueryableCollection<IDeltaItems> {
const path = `delta${(token) ? `(token=${token})` : ""}`;

const query: IGraphQueryableCollection<IDeltaItems> = <any>GraphQueryableCollection(this, path);
const query = GraphQueryableCollection(this, path);
query.on.parse.replace(errorCheck);
query.on.parse(async (url: URL, response: Response, result: any): Promise<[URL, Response, any]> => {

const json = await response.json();
const nextLink = json["@odata.nextLink"];
const deltaLink = json["@odata.deltaLink"];

result = {
// TODO:: update docs to show how to load next with async iterator
next: () => (nextLink ? GraphQueryableCollection([this, nextLink]) : null),
next: () => (nextLink ? AsPaged(GraphQueryableCollection([this, nextLink]))() : null),
delta: () => (deltaLink ? GraphQueryableCollection([query, deltaLink])() : null),
values: json.value,
};
Expand Down Expand Up @@ -351,8 +350,8 @@ export class _DriveItem extends _GraphQueryableInstance<IDriveItemType> {
* @returns IGraphQueryableCollection<IItemAnalytics>
*/
public analytics(analyticsOptions?: IAnalyticsOptions): IGraphQueryableCollection<IItemAnalytics> {
const query = `analytics/${analyticsOptions ? analyticsOptions.timeRange : "lastSevenDays"}`;
return <IGraphQueryableCollection<IItemAnalytics>>GraphQueryableCollection(this, query);
const query = `analytics/${analyticsOptions?analyticsOptions.timeRange:"lastSevenDays"}`;
return GraphQueryableCollection(this, query);
}
}
export interface IDriveItem extends _DriveItem, IDeleteable, IUpdateable { }
Expand Down
8 changes: 7 additions & 1 deletion packages/graph/onedrive/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ _Drive.prototype.special = function special(specialFolder: SpecialFolder): IDriv
return DriveItem(this, `special/${specialFolder}`);
};

export type SpecialFolder = "documents" | "photos" | "cameraroll" | "approot" | "music";
export enum SpecialFolder {
"Documents" = "documents",
"Photos" = "photos",
"CameraRoll" = "cameraroll",
"AppRoot" = "approot",
"Music" = "music",
}

_DriveItem.prototype.restore = function restore(restoreOptions: IItemOptions): Promise<IDriveItem> {
return graphPost(DriveItem(this, "restore"), body(restoreOptions));
Expand Down
71 changes: 55 additions & 16 deletions test/graph/paging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,32 +35,48 @@ describe("Groups", function () {
}
});

it("pages users 1", async function () {

let users = await this.pnp.graph.users.top(2).paged();

expect(users).to.have.property("hasNext", true);

users = await users.next();

expect(users).to.have.property("hasNext", true);
});

it("pages all users", async function () {

const count = await this.pnp.graph.users.count();

const allUsers = [];
let users = await this.pnp.graph.users.top(20).select("displayName").paged();

for await (const users of this.pnp.graph.users.top(20).select("displayName").paged()) {
allUsers.push(...users);
allUsers.push(...users.value);

while (users.hasNext) {
users = await users.next();
allUsers.push(...users.value);
}

expect(allUsers.length).to.eq(count);
});

it("pages groups", async function () {

const count = await this.pnp.graph.groups.count();

expect(count).is.gt(0);
let groups = await this.pnp.graph.groups.top(2).paged();

const allGroups = [];
expect(groups).to.have.property("hasNext", true);
expect(groups).to.have.property("count").gt(0);
expect(groups.value.length).to.eq(2);

for await (const groups of this.pnp.graph.groups.top(20).select("displayName").paged()) {
allGroups.push(...groups);
}
groups = await groups.next();

expect(allGroups.length).to.eq(count);
expect(groups).to.have.property("hasNext", true);
// count only returns on the first call, not subsequent paged calls
expect(groups).to.have.property("count").eq(0);
expect(groups.value.length).to.eq(2);
});

it("groups count", async function () {
Expand All @@ -70,22 +86,45 @@ describe("Groups", function () {
expect(count).to.be.gt(0);
});

it("pages items", async function () {
it("pages all groups", async function () {

const allItems = [];
const count = await this.pnp.graph.groups.count();

const allGroups = [];
let groups = await this.pnp.graph.groups.top(20).select("mailNickname").paged();

for await (const items of itemsCol.paged()) {
allItems.push(...items);
allGroups.push(...groups.value);

while (groups.hasNext) {
groups = await groups.next();
allGroups.push(...groups.value);
}

expect(allItems.length).to.be.gt(0);
expect(allGroups.length).to.be.gt((count - 10)).and.lt((count + 10));
});

it("pages items", async function () {

let pagedResults = await itemsCol.top(5).paged();

expect(pagedResults.value.length).to.eq(5);
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
expect(pagedResults.hasNext).to.be.true;
expect(pagedResults.count).to.eq(0);

pagedResults = await pagedResults.next();

expect(pagedResults.value.length).to.eq(5);
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
expect(pagedResults.hasNext).to.be.true;
expect(pagedResults.count).to.eq(0);
});

it("items count", async function () {

const count = await itemsCol.count();

// items doesn't support count, should be zero
expect(count).to.eq(-1);
expect(count).to.eq(0);
});
});
Loading