mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f2247f19b | ||
|
|
7a3c782bb5 |
No files matched your search
@@ -13,6 +13,7 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
### 🧹 Chores
|
||||
|
||||
- Unify channel graphql query types. ([#1949](https://github.com/expo/eas-cli/pull/1949) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Fetch entire relay compliant dataset. ([#1953](https://github.com/expo/eas-cli/pull/1953) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [3.17.0](https://github.com/expo/eas-cli/releases/tag/v3.17.0) - 2023-07-24
|
||||
|
||||
|
||||
Generated
+464
@@ -15554,6 +15554,80 @@
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "BuildLimitThresholdExceededMetadata",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "account",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Account",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "thresholdsExceeded",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "NotificationThresholdExceeded",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "BuildLimitThresholdExceededMetadataType",
|
||||
"description": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "IOS",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "TOTAL",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "BuildLogs",
|
||||
@@ -16520,6 +16594,81 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "BuildPlanCreditThresholdExceededMetadata",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "account",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Account",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "buildCreditUsage",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "planLimit",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "threshold",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "BuildPriority",
|
||||
@@ -25965,6 +26114,109 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Notification",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "createdAt",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "DateTime",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "event",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "NotificationEvent",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "metadata",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "UNION",
|
||||
"name": "NotificationMetadata",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "NotificationType",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "updatedAt",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "DateTime",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "NotificationEvent",
|
||||
@@ -25996,10 +26248,42 @@
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "TEST",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "UNION",
|
||||
"name": "NotificationMetadata",
|
||||
"description": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": [
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "BuildLimitThresholdExceededMetadata",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "BuildPlanCreditThresholdExceededMetadata",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "TestNotificationMetadata",
|
||||
"ofType": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "NotificationSubscription",
|
||||
@@ -26280,6 +26564,81 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "NotificationThresholdExceeded",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "count",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "threshold",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "BuildLimitThresholdExceededMetadataType",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "NotificationType",
|
||||
@@ -26293,6 +26652,12 @@
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "WEB",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
@@ -30078,6 +30443,30 @@
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "webNotifications",
|
||||
"description": "Web notifications linked to a user",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Notification",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
@@ -32672,6 +33061,33 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "TestNotificationMetadata",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "message",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "TimelineActivityConnection",
|
||||
@@ -35289,6 +35705,30 @@
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "webNotifications",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Notification",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
@@ -35892,6 +36332,30 @@
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "webNotifications",
|
||||
"description": "Web notifications linked to a user",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Notification",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { UpdateBranchWithCurrentGroupFragment } from '../../graphql/generated';
|
||||
import { UpdateChannelObject } from '../../graphql/queries/ChannelQuery';
|
||||
import { UpdateBranchObject, UpdateChannelObject } from '../../graphql/queries/ChannelQuery';
|
||||
|
||||
export const testUpdateBranch1: UpdateBranchWithCurrentGroupFragment = {
|
||||
export const testUpdateBranch1: UpdateBranchObject = {
|
||||
id: '754bf17f-efc0-46ab-8a59-a03f20e53e9b',
|
||||
name: 'wrong-channel',
|
||||
updateGroups: [
|
||||
@@ -61,7 +60,7 @@ export const testUpdateBranch1: UpdateBranchWithCurrentGroupFragment = {
|
||||
__typename: 'UpdateBranch',
|
||||
};
|
||||
|
||||
const testUpdateBranch2: UpdateBranchWithCurrentGroupFragment = {
|
||||
const testUpdateBranch2: UpdateBranchObject = {
|
||||
id: '6941a8dd-5c0a-48bc-8876-f49c88ed419f',
|
||||
name: 'production',
|
||||
updateGroups: [
|
||||
|
||||
@@ -8,6 +8,8 @@ import { withErrorHandlingAsync } from '../graphql/client';
|
||||
import {
|
||||
CreateUpdateChannelOnAppMutation,
|
||||
CreateUpdateChannelOnAppMutationVariables,
|
||||
PageInfo,
|
||||
UpdateChannelBasicInfoFragment,
|
||||
ViewBranchesOnUpdateChannelQueryVariables,
|
||||
ViewUpdateChannelsOnAppQueryVariables,
|
||||
} from '../graphql/generated';
|
||||
@@ -15,12 +17,14 @@ import { BranchQuery, UpdateBranchOnChannelObject } from '../graphql/queries/Bra
|
||||
import { ChannelQuery, UpdateChannelObject } from '../graphql/queries/ChannelQuery';
|
||||
import { UpdateChannelBasicInfoFragmentNode } from '../graphql/types/UpdateChannelBasicInfo';
|
||||
import Log from '../log';
|
||||
import { ora } from '../ora';
|
||||
import formatFields from '../utils/formatFields';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
import {
|
||||
paginatedQueryWithConfirmPromptAsync,
|
||||
paginatedQueryWithSelectPromptAsync,
|
||||
} from '../utils/queries';
|
||||
import { Connection, QueryParams, getPaginatedDatasetAsync } from '../utils/relay';
|
||||
import { logChannelDetails } from './utils';
|
||||
|
||||
export const CHANNELS_LIMIT = 25;
|
||||
@@ -264,3 +268,46 @@ export async function ensureChannelExistsAsync(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getChannelsDatasetAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
appId,
|
||||
filterPredicate,
|
||||
batchSize = 100,
|
||||
}: {
|
||||
appId: string;
|
||||
filterPredicate?: (channelInfo: UpdateChannelBasicInfoFragment) => boolean;
|
||||
batchSize?: number;
|
||||
}
|
||||
): Promise<UpdateChannelBasicInfoFragment[]> {
|
||||
const queryAsync = async ({
|
||||
first,
|
||||
after,
|
||||
}: QueryParams): Promise<Connection<UpdateChannelBasicInfoFragment>> =>
|
||||
await ChannelQuery.viewUpdateChannelsBasicInfoPaginatedOnAppAsync(graphqlClient, {
|
||||
appId,
|
||||
first,
|
||||
after,
|
||||
});
|
||||
|
||||
const assetSpinner = ora().start('Fetching channels...');
|
||||
const afterEachQuery = (
|
||||
totalNodesFetched: number,
|
||||
_dataset: UpdateChannelBasicInfoFragment[],
|
||||
_batch: UpdateChannelBasicInfoFragment[],
|
||||
pageInfo: PageInfo
|
||||
): void => {
|
||||
if (pageInfo.hasNextPage) {
|
||||
assetSpinner.text = `Fetched ${totalNodesFetched} channels`;
|
||||
}
|
||||
};
|
||||
const dataset = await getPaginatedDatasetAsync({
|
||||
queryAsync,
|
||||
afterEachQuery,
|
||||
filterPredicate,
|
||||
batchSize,
|
||||
});
|
||||
assetSpinner.succeed(`Fetched all channels`);
|
||||
return dataset;
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { UpdateBranchWithCurrentGroupFragment } from '../graphql/generated';
|
||||
import { UpdateChannelObject } from '../graphql/queries/ChannelQuery';
|
||||
import { UpdateBranchObject, UpdateChannelObject } from '../graphql/queries/ChannelQuery';
|
||||
import Log from '../log';
|
||||
import {
|
||||
FormattedBranchDescription,
|
||||
@@ -114,7 +113,7 @@ export function logChannelDetails(channel: UpdateChannelObject): void {
|
||||
function getUpdateBranchNullable(
|
||||
channel: UpdateChannelObject,
|
||||
branchId: string
|
||||
): UpdateBranchWithCurrentGroupFragment | null {
|
||||
): UpdateBranchObject | null {
|
||||
const updateBranches = channel.updateBranches;
|
||||
const updateBranch = updateBranches.find(branch => branch.id === branchId);
|
||||
return updateBranch ?? null;
|
||||
@@ -123,7 +122,7 @@ function getUpdateBranchNullable(
|
||||
export function getUpdateBranch(
|
||||
channel: UpdateChannelObject,
|
||||
branchId: string
|
||||
): UpdateBranchWithCurrentGroupFragment {
|
||||
): UpdateBranchObject {
|
||||
const updateBranch = getUpdateBranchNullable(channel, branchId);
|
||||
if (!updateBranch) {
|
||||
throw new Error(
|
||||
|
||||
+54
-4
@@ -2236,6 +2236,17 @@ export enum BuildJobStatus {
|
||||
Started = 'STARTED'
|
||||
}
|
||||
|
||||
export type BuildLimitThresholdExceededMetadata = {
|
||||
__typename?: 'BuildLimitThresholdExceededMetadata';
|
||||
account: Account;
|
||||
thresholdsExceeded: Array<NotificationThresholdExceeded>;
|
||||
};
|
||||
|
||||
export enum BuildLimitThresholdExceededMetadataType {
|
||||
Ios = 'IOS',
|
||||
Total = 'TOTAL'
|
||||
}
|
||||
|
||||
export type BuildLogs = {
|
||||
__typename?: 'BuildLogs';
|
||||
format?: Maybe<BuildJobLogsFormat>;
|
||||
@@ -2381,6 +2392,14 @@ export type BuildParamsInput = {
|
||||
sdkVersion?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type BuildPlanCreditThresholdExceededMetadata = {
|
||||
__typename?: 'BuildPlanCreditThresholdExceededMetadata';
|
||||
account: Account;
|
||||
buildCreditUsage: Scalars['Int'];
|
||||
planLimit: Scalars['Int'];
|
||||
threshold: Scalars['Int'];
|
||||
};
|
||||
|
||||
export enum BuildPriority {
|
||||
High = 'HIGH',
|
||||
Normal = 'NORMAL',
|
||||
@@ -3758,13 +3777,26 @@ export type MeteredBillingStatus = {
|
||||
EAS_UPDATE: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type Notification = {
|
||||
__typename?: 'Notification';
|
||||
createdAt: Scalars['DateTime'];
|
||||
event: NotificationEvent;
|
||||
id: Scalars['ID'];
|
||||
metadata?: Maybe<NotificationMetadata>;
|
||||
type: NotificationType;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export enum NotificationEvent {
|
||||
BuildComplete = 'BUILD_COMPLETE',
|
||||
BuildLimitThresholdExceeded = 'BUILD_LIMIT_THRESHOLD_EXCEEDED',
|
||||
BuildPlanCreditThresholdExceeded = 'BUILD_PLAN_CREDIT_THRESHOLD_EXCEEDED',
|
||||
SubmissionComplete = 'SUBMISSION_COMPLETE'
|
||||
SubmissionComplete = 'SUBMISSION_COMPLETE',
|
||||
Test = 'TEST'
|
||||
}
|
||||
|
||||
export type NotificationMetadata = BuildLimitThresholdExceededMetadata | BuildPlanCreditThresholdExceededMetadata | TestNotificationMetadata;
|
||||
|
||||
export type NotificationSubscription = {
|
||||
__typename?: 'NotificationSubscription';
|
||||
account?: Maybe<Account>;
|
||||
@@ -3805,8 +3837,17 @@ export type NotificationSubscriptionMutationUnsubscribeArgs = {
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type NotificationThresholdExceeded = {
|
||||
__typename?: 'NotificationThresholdExceeded';
|
||||
count: Scalars['Int'];
|
||||
limit: Scalars['Int'];
|
||||
threshold: Scalars['Int'];
|
||||
type: BuildLimitThresholdExceededMetadataType;
|
||||
};
|
||||
|
||||
export enum NotificationType {
|
||||
Email = 'EMAIL'
|
||||
Email = 'EMAIL',
|
||||
Web = 'WEB'
|
||||
}
|
||||
|
||||
export type Offer = {
|
||||
@@ -4309,6 +4350,8 @@ export type SsoUser = Actor & UserActor & {
|
||||
/** @deprecated No longer supported */
|
||||
twitterUsername?: Maybe<Scalars['String']>;
|
||||
username: Scalars['String'];
|
||||
/** Web notifications linked to a user */
|
||||
webNotifications: Array<Notification>;
|
||||
};
|
||||
|
||||
|
||||
@@ -4745,6 +4788,11 @@ export type SubscriptionDetailsPlanEnablementArgs = {
|
||||
serviceMetric: EasServiceMetric;
|
||||
};
|
||||
|
||||
export type TestNotificationMetadata = {
|
||||
__typename?: 'TestNotificationMetadata';
|
||||
message: Scalars['String'];
|
||||
};
|
||||
|
||||
export type TimelineActivityConnection = {
|
||||
__typename?: 'TimelineActivityConnection';
|
||||
edges: Array<TimelineActivityEdge>;
|
||||
@@ -5062,6 +5110,7 @@ export type User = Actor & UserActor & {
|
||||
/** @deprecated No longer supported */
|
||||
twitterUsername?: Maybe<Scalars['String']>;
|
||||
username: Scalars['String'];
|
||||
webNotifications: Array<Notification>;
|
||||
};
|
||||
|
||||
|
||||
@@ -5150,6 +5199,8 @@ export type UserActor = {
|
||||
/** @deprecated No longer supported */
|
||||
twitterUsername?: Maybe<Scalars['String']>;
|
||||
username: Scalars['String'];
|
||||
/** Web notifications linked to a user */
|
||||
webNotifications: Array<Notification>;
|
||||
};
|
||||
|
||||
|
||||
@@ -6173,6 +6224,7 @@ export type ViewBuildsOnAppQuery = { __typename?: 'RootQuery', app: { __typename
|
||||
export type ViewUpdateChannelOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
channelName: Scalars['String'];
|
||||
filter?: InputMaybe<UpdatesFilter>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -6308,8 +6360,6 @@ export type UpdateFragment = { __typename?: 'Update', id: string, group: string,
|
||||
|
||||
export type UpdateBranchFragment = { __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null }> };
|
||||
|
||||
export type UpdateBranchWithCurrentGroupFragment = { __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null }>> };
|
||||
|
||||
export type UpdateChannelBasicInfoFragment = { __typename?: 'UpdateChannel', id: string, name: string, branchMapping: string };
|
||||
|
||||
export type WebhookFragment = { __typename?: 'Webhook', id: string, event: WebhookType, url: string, createdAt: any, updatedAt: any };
|
||||
|
||||
@@ -5,8 +5,6 @@ import { ChannelNotFoundError } from '../../channel/errors';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
PageInfo,
|
||||
UpdateChannelBasicInfoFragment,
|
||||
ViewUpdateChannelOnAppQuery,
|
||||
ViewUpdateChannelOnAppQueryVariables,
|
||||
ViewUpdateChannelsOnAppQuery,
|
||||
@@ -14,7 +12,7 @@ import {
|
||||
ViewUpdateChannelsPaginatedOnAppQuery,
|
||||
ViewUpdateChannelsPaginatedOnAppQueryVariables,
|
||||
} from '../generated';
|
||||
import { UpdateBranchWithCurrentGroupFragmentNode } from '../types/UpdateBranchWithCurrentUpdate';
|
||||
import { UpdateFragmentNode } from '../types/Update';
|
||||
import { UpdateChannelBasicInfoFragmentNode } from '../types/UpdateChannelBasicInfo';
|
||||
|
||||
type ViewUpdateChannelsOnAppObject = NonNullable<
|
||||
@@ -28,16 +26,22 @@ type UpdateChannelByNameObject = NonNullable<
|
||||
// these types should have the same fields
|
||||
export type UpdateChannelObject = ViewUpdateChannelsOnAppObject & UpdateChannelByNameObject;
|
||||
|
||||
export type UpdateBranchObject = UpdateChannelObject['updateBranches'][number];
|
||||
|
||||
export const ChannelQuery = {
|
||||
async viewUpdateChannelAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, channelName }: ViewUpdateChannelOnAppQueryVariables
|
||||
{ appId, channelName, filter }: ViewUpdateChannelOnAppQueryVariables
|
||||
): Promise<UpdateChannelByNameObject> {
|
||||
const response = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<ViewUpdateChannelOnAppQuery, ViewUpdateChannelOnAppQueryVariables>(
|
||||
gql`
|
||||
query ViewUpdateChannelOnApp($appId: String!, $channelName: String!) {
|
||||
query ViewUpdateChannelOnApp(
|
||||
$appId: String!
|
||||
$channelName: String!
|
||||
$filter: UpdatesFilter
|
||||
) {
|
||||
app {
|
||||
byId(appId: $appId) {
|
||||
id
|
||||
@@ -48,15 +52,19 @@ export const ChannelQuery = {
|
||||
branchMapping
|
||||
updateBranches(offset: 0, limit: 5) {
|
||||
id
|
||||
...UpdateBranchWithCurrentGroupFragment
|
||||
name
|
||||
updateGroups(offset: 0, limit: 1, filter: $filter) {
|
||||
id
|
||||
...UpdateFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(UpdateBranchWithCurrentGroupFragmentNode)}
|
||||
${print(UpdateFragmentNode)}
|
||||
`,
|
||||
{ appId, channelName },
|
||||
{ appId, channelName, filter },
|
||||
{ additionalTypenames: ['UpdateChannel', 'UpdateBranch', 'Update'] }
|
||||
)
|
||||
.toPromise()
|
||||
@@ -88,13 +96,17 @@ export const ChannelQuery = {
|
||||
branchMapping
|
||||
updateBranches(offset: 0, limit: 5) {
|
||||
id
|
||||
...UpdateBranchWithCurrentGroupFragment
|
||||
name
|
||||
updateGroups(offset: 0, limit: 1) {
|
||||
id
|
||||
...UpdateFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(UpdateBranchWithCurrentGroupFragmentNode)}
|
||||
${print(UpdateFragmentNode)}
|
||||
`,
|
||||
{ appId, offset, limit },
|
||||
{ additionalTypenames: ['UpdateChannel', 'UpdateBranch', 'Update'] }
|
||||
@@ -112,7 +124,7 @@ export const ChannelQuery = {
|
||||
async viewUpdateChannelsBasicInfoPaginatedOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, first, after }: ViewUpdateChannelsPaginatedOnAppQueryVariables
|
||||
): Promise<[UpdateChannelBasicInfoFragment[], PageInfo]> {
|
||||
): Promise<ViewUpdateChannelsPaginatedOnAppQuery['app']['byId']['channelsPaginated']> {
|
||||
const response = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<
|
||||
@@ -153,7 +165,6 @@ export const ChannelQuery = {
|
||||
if (!channelsPaginated) {
|
||||
throw new Error(`Could not find channels on project with id ${appId}`);
|
||||
}
|
||||
|
||||
return [channelsPaginated.edges.map(edge => edge.node) ?? [], channelsPaginated.pageInfo];
|
||||
return channelsPaginated;
|
||||
},
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { UpdateFragmentNode } from './Update';
|
||||
|
||||
export const UpdateBranchWithCurrentGroupFragmentNode = gql`
|
||||
fragment UpdateBranchWithCurrentGroupFragment on UpdateBranch {
|
||||
id
|
||||
name
|
||||
updateGroups(offset: 0, limit: 1) {
|
||||
id
|
||||
...UpdateFragment
|
||||
}
|
||||
}
|
||||
${print(UpdateFragmentNode)}
|
||||
`;
|
||||
@@ -0,0 +1,94 @@
|
||||
import { getPaginatedDatasetAsync } from '../relay';
|
||||
|
||||
describe('getPaginatedDatasetAsync', () => {
|
||||
const mockQueryAsync = jest.fn(({ first, after }) =>
|
||||
Promise.resolve({
|
||||
edges: Array(first).fill({ node: 'node', cursor: 'cursor' }),
|
||||
pageInfo: {
|
||||
hasNextPage: after !== 'next',
|
||||
endCursor: after ? null : 'next',
|
||||
hasPreviousPage: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should query data page by page until no more data', async () => {
|
||||
const dataset = await getPaginatedDatasetAsync({
|
||||
queryAsync: mockQueryAsync,
|
||||
batchSize: 1,
|
||||
});
|
||||
|
||||
expect(mockQueryAsync).toHaveBeenCalledTimes(2);
|
||||
expect(mockQueryAsync).toHaveBeenCalledWith({ first: 1, after: undefined });
|
||||
expect(mockQueryAsync).toHaveBeenCalledWith({ first: 1, after: 'next' });
|
||||
expect(dataset).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should call beforeEachQuery and afterEachQuery with correct parameters', async () => {
|
||||
// dataset is pass by reference, so we make a copy at each call
|
||||
const datasetAtNthCall_beforeEachQuery = [] as any;
|
||||
const mockBeforeEachQuery = jest.fn((totalNodesFetched, dataset) => {
|
||||
datasetAtNthCall_beforeEachQuery.push([...dataset]);
|
||||
});
|
||||
|
||||
// dataset is pass by reference, so we make a copy at each call
|
||||
const datasetAtNthCall_afterEachQuery = [] as any;
|
||||
const mockAfterEachQuery = jest.fn((totalNodesFetched, dataset, batch, pageInfo) => {
|
||||
datasetAtNthCall_afterEachQuery.push([...dataset]);
|
||||
});
|
||||
|
||||
await getPaginatedDatasetAsync({
|
||||
queryAsync: mockQueryAsync,
|
||||
beforeEachQuery: mockBeforeEachQuery,
|
||||
afterEachQuery: mockAfterEachQuery,
|
||||
batchSize: 1,
|
||||
});
|
||||
|
||||
expect(mockBeforeEachQuery).toHaveBeenCalledTimes(2);
|
||||
expect(mockBeforeEachQuery).toHaveBeenNthCalledWith(1, 0, expect.any(Array));
|
||||
expect(datasetAtNthCall_beforeEachQuery[0]).toStrictEqual([]);
|
||||
expect(mockBeforeEachQuery).toHaveBeenNthCalledWith(2, 1, expect.any(Array));
|
||||
expect(datasetAtNthCall_beforeEachQuery[1]).toStrictEqual(['node']);
|
||||
|
||||
expect(mockAfterEachQuery).toHaveBeenCalledTimes(2);
|
||||
expect(mockAfterEachQuery).toHaveBeenNthCalledWith(1, 1, expect.any(Array), ['node'], {
|
||||
hasNextPage: true,
|
||||
endCursor: 'next',
|
||||
hasPreviousPage: false,
|
||||
});
|
||||
expect(datasetAtNthCall_afterEachQuery[0]).toStrictEqual(['node']);
|
||||
expect(mockAfterEachQuery).toHaveBeenNthCalledWith(2, 2, expect.any(Array), ['node'], {
|
||||
hasNextPage: false,
|
||||
endCursor: null,
|
||||
hasPreviousPage: false,
|
||||
});
|
||||
expect(datasetAtNthCall_afterEachQuery[1]).toStrictEqual(['node', 'node']);
|
||||
});
|
||||
|
||||
it('should filter nodes if filterPredicate is provided', async () => {
|
||||
const filterPredicate = (node: string): boolean => node !== 'node';
|
||||
|
||||
const dataset = await getPaginatedDatasetAsync({
|
||||
queryAsync: mockQueryAsync,
|
||||
filterPredicate,
|
||||
batchSize: 1,
|
||||
});
|
||||
|
||||
expect(dataset).toEqual([]); // expecting an empty array because our filterPredicate will filter out 'node'
|
||||
});
|
||||
|
||||
it('should stop fetching when maxNodesFetched limit is reached', async () => {
|
||||
const dataset = await getPaginatedDatasetAsync({
|
||||
queryAsync: mockQueryAsync,
|
||||
batchSize: 2,
|
||||
maxNodesFetched: 2,
|
||||
});
|
||||
|
||||
expect(dataset).toHaveLength(2);
|
||||
expect(mockQueryAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { PageInfo } from '../graphql/generated';
|
||||
|
||||
export type Connection<T> = {
|
||||
edges: Edge<T>[];
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
type Edge<T> = {
|
||||
node: T;
|
||||
};
|
||||
|
||||
export type QueryParams = {
|
||||
first: number;
|
||||
after?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches dataset in paginated manner (batch by batch) using GraphQL queries.
|
||||
*
|
||||
* @param queryAsync A promise based function for querying.
|
||||
* @param beforeEachQuery Optional. A callback function to be called before each query
|
||||
* @param afterEachQuery Optional. A callback function to be called after each query.
|
||||
* @param filterPredicate Optional. A predicate function to filter the node.
|
||||
* @param batchSize Optional. The batch size of the pagination. Defaults to 100.
|
||||
*
|
||||
* @return {Promise<T[]>} - A promise that resolves to an array (the dataset).
|
||||
* @throws {Error} - If an error occurs during execution of the query or pagination.
|
||||
*/
|
||||
export async function getPaginatedDatasetAsync<T>({
|
||||
queryAsync,
|
||||
beforeEachQuery,
|
||||
afterEachQuery,
|
||||
filterPredicate,
|
||||
batchSize = 100,
|
||||
maxNodesFetched = 10_000,
|
||||
}: {
|
||||
queryAsync: ({ first, after }: QueryParams) => Promise<Connection<T>>;
|
||||
beforeEachQuery?: (totalNodesFetched: number, dataset: T[]) => void;
|
||||
afterEachQuery?: (
|
||||
totalNodesFetched: number,
|
||||
dataset: T[],
|
||||
batch: T[],
|
||||
pageInfo: PageInfo
|
||||
) => void;
|
||||
filterPredicate?: (node: T) => boolean;
|
||||
batchSize?: number;
|
||||
maxNodesFetched?: number;
|
||||
}): Promise<T[]> {
|
||||
const dataset = [];
|
||||
let hasMore = true;
|
||||
let after: string | undefined;
|
||||
let totalNodesFetched = 0;
|
||||
while (hasMore) {
|
||||
if (beforeEachQuery) {
|
||||
beforeEachQuery(totalNodesFetched, dataset);
|
||||
}
|
||||
const result = await queryAsync({ first: batchSize, after });
|
||||
const { edges, pageInfo } = result;
|
||||
const nodes = edges.map(edge => edge.node);
|
||||
const batch = filterPredicate ? nodes.filter(filterPredicate) : nodes;
|
||||
dataset.push(...batch);
|
||||
hasMore = pageInfo.hasNextPage;
|
||||
after = pageInfo.endCursor ?? undefined;
|
||||
totalNodesFetched += nodes.length;
|
||||
if (afterEachQuery) {
|
||||
afterEachQuery(totalNodesFetched, dataset, batch, pageInfo);
|
||||
}
|
||||
if (totalNodesFetched >= maxNodesFetched) {
|
||||
return dataset;
|
||||
}
|
||||
}
|
||||
return dataset;
|
||||
}
|
||||
Reference in new issue
Block a user