Skip to content

Commit

Permalink
feat: add monthlyUsage() and limits() endpoints to UserClients (#517)
Browse files Browse the repository at this point in the history
These endpoints have been around for some quite time and now we're
exposing them via the JS client.
  • Loading branch information
tobice authored Feb 22, 2024
1 parent e8b3336 commit 2767c8d
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 1 deletion.
133 changes: 133 additions & 0 deletions src/resource_clients/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { ApifyApiError } from '../apify_api_error';
import { ApiClientSubResourceOptions } from '../base/api_client';
import { ResourceClient } from '../base/resource_client';
import { ApifyRequestConfig } from '../http_client';
import { cast, catchNotFoundOrThrow, parseDateFields, pluckData } from '../utils';

export class UserClient extends ResourceClient {
/**
Expand All @@ -20,8 +23,50 @@ export class UserClient extends ResourceClient {
async get(): Promise<User> {
return this._get() as Promise<User>;
}

/**
* https://docs.apify.com/api/v2/#/reference/users/monthly-usage
*/
async monthlyUsage(): Promise<MonthlyUsage | undefined> {
const requestOpts: ApifyRequestConfig = {
url: this._url('usage/monthly'),
method: 'GET',
params: this._params(),
};
try {
const response = await this.httpClient.call(requestOpts);
return cast(parseDateFields(pluckData(response.data)));
} catch (err) {
catchNotFoundOrThrow(err as ApifyApiError);
}

return undefined;
}

/**
* https://docs.apify.com/api/v2/#/reference/users/account-and-usage-limits
*/
async limits(): Promise<AccountAndUsageLimits | undefined> {
const requestOpts: ApifyRequestConfig = {
url: this._url('limits'),
method: 'GET',
params: this._params(),
};
try {
const response = await this.httpClient.call(requestOpts);
return cast(parseDateFields(pluckData(response.data)));
} catch (err) {
catchNotFoundOrThrow(err as ApifyApiError);
}

return undefined;
}
}

//
// Response interface for /users/:userId
//

export interface User {
// Public properties
username: string;
Expand Down Expand Up @@ -83,3 +128,91 @@ export enum PlatformFeature {
Proxy = 'PROXY',
ProxyExternalAccess = 'PROXY_EXTERNAL_ACCESS',
}

//
// Response interface for /users/:userId/usage/monthly
//

export interface MonthlyUsage {
usageCycle: UsageCycle;
monthlyServiceUsage: { [key: string]: MonthlyServiceUsageData };
dailyServiceUsages: DailyServiceUsage[];
totalUsageCreditsUsdBeforeVolumeDiscount: number;
totalUsageCreditsUsdAfterVolumeDiscount: number;
}

export interface UsageCycle {
startAt: Date;
endAt: Date;
}

/** Monthly usage of a single service */
interface MonthlyServiceUsageData {
quantity: number;
baseAmountUsd: number;
baseUnitPriceUsd: number;
amountAfterVolumeDiscountUsd: number;
priceTiers: PriceTier[];
}

interface PriceTier {
quantityAbove: number;
discountPercent: number;
tierQuantity: number;
unitPriceUsd: number;
priceUsd: number;
}

interface DailyServiceUsage {
date: Date;
serviceUsage: { [key: string]: DailyServiceUsageData };
totalUsageCreditsUsd: number;
}

/** Daily usage of a single service */
interface DailyServiceUsageData {
quantity: number;
baseAmountUsd: number;
}

//
// Response interface for /users/:userId/limits
//

export interface AccountAndUsageLimits {
monthlyUsageCycle: MonthlyUsageCycle;
limits: Limits;
current: Current;
}

export interface MonthlyUsageCycle {
startAt: Date;
endAt: Date;
}

export interface Limits {
maxMonthlyUsageUsd: number;
maxMonthlyActorComputeUnits: number;
maxMonthlyExternalDataTransferGbytes: number;
maxMonthlyProxySerps: number;
maxMonthlyResidentialProxyGbytes: number;
maxActorMemoryGbytes: number;
maxActorCount: number;
maxActorTaskCount: number;
maxConcurrentActorJobs: number;
maxTeamAccountSeatCount: number;
dataRetentionDays: number;
}

export interface Current {
monthlyUsageUsd: number;
monthlyActorComputeUnits: number;
monthlyExternalDataTransferGbytes: number;
monthlyProxySerps: number;
monthlyResidentialProxyGbytes: number;
actorMemoryGbytes: number;
actorCount: number;
actorTaskCount: number;
activeActorJobCount: number;
teamAccountSeatCount: number;
}
3 changes: 2 additions & 1 deletion test/mock_server/routes/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const users = express.Router();

const ROUTES = [
{ id: 'get-user', method: 'GET', path: '/:userId' },

{ id: 'get-monthly-usage', method: 'GET', path: '/:userId/usage/monthly' },
{ id: 'get-limits', method: 'GET', path: '/:userId/limits' },
];

addRoutes(users, ROUTES);
Expand Down
24 changes: 24 additions & 0 deletions test/users.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,29 @@ describe('User methods', () => {
expect(browserRes).toEqual(res);
validateRequest({}, { userId: ME_USER_NAME_PLACEHOLDER });
});

test('monthlyUsage() works', async () => {
const userId = 'some-id';

const res = await client.user(userId).monthlyUsage();
expect(res.id).toEqual('get-monthly-usage');
validateRequest({}, { userId });

const browserRes = await page.evaluate((id) => client.user(id).monthlyUsage(), userId);
expect(browserRes).toEqual(res);
validateRequest({}, { userId });
});

test('limits() works', async () => {
const userId = 'some-id';

const res = await client.user(userId).limits();
expect(res.id).toEqual('get-limits');
validateRequest({}, { userId });

const browserRes = await page.evaluate((id) => client.user(id).limits(), userId);
expect(browserRes).toEqual(res);
validateRequest({}, { userId });
});
});
});

0 comments on commit 2767c8d

Please sign in to comment.