Skip to content

Commit

Permalink
[eas-cli] [ENG-10146] Allow submission of builds in progress (#2543)
Browse files Browse the repository at this point in the history
* [eas-cli] Add tests

Added tests for allowing to select in progress builds

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Allow in progress builds

When prompting the user with a list of builds the in-progress builds are now returned first

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Adjust description

When prompting the user with a list of builds the build status is now displayed let the user know which are finished and which are still in progress

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Reformat

Adjusted formatting

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Reformat

Adjusted formatting

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Lint fix

lint --fix

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Add changelog

Added the changelog entry since the bot does not seem to be working

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Return more statuses and sort

Query returning the potential builds for submission now also returns build in `new` and `in-queue` statuses. All builds are then sorted decreasingly by `createdAt` and up to `limit` are returned

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Lint

yarn lint --fix

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Add mapping

Created build status mapping instead string modification

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to
  • Loading branch information
radoslawkrzemien authored Sep 16, 2024
1 parent a7f939f commit dadf58f
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features

- Log command execution to assist in debugging local builds. ([#2526](https://github.com/expo/eas-cli/pull/2526) by [@trajano](https://github.com/trajano))
- Allow submitting builds in progress ([#2543](https://github.com/expo/eas-cli/pull/2543) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))

### 🐛 Bug fixes

Expand Down
14 changes: 13 additions & 1 deletion packages/eas-cli/src/submit/ArchiveSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as uuid from 'uuid';
import { getRecentBuildsForSubmissionAsync } from './utils/builds';
import { isExistingFileAsync, uploadAppArchiveAsync } from './utils/files';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { BuildFragment } from '../graphql/generated';
import { BuildFragment, BuildStatus } from '../graphql/generated';
import { BuildQuery } from '../graphql/queries/BuildQuery';
import { toAppPlatform } from '../graphql/types/AppPlatform';
import Log, { learnMore } from '../log';
Expand Down Expand Up @@ -89,6 +89,16 @@ export type ArchiveSource =

export type ResolvedArchiveSource = ArchiveUrlSource | ArchiveGCSSource | ArchiveBuildSource;

const buildStatusMapping: Record<BuildStatus, string> = {
[BuildStatus.New]: 'new',
[BuildStatus.InQueue]: 'in queue',
[BuildStatus.InProgress]: 'in progress',
[BuildStatus.Finished]: 'finished',
[BuildStatus.Errored]: 'errored',
[BuildStatus.PendingCancel]: 'canceled',
[BuildStatus.Canceled]: 'canceled',
};

export async function getArchiveAsync(
ctx: ArchiveResolverContext,
source: ArchiveSource
Expand Down Expand Up @@ -344,6 +354,7 @@ function formatBuildChoice(build: BuildFragment): prompts.Choice {
gitCommitMessage,
channel,
message,
status,
} = build;
const buildDate = new Date(updatedAt);

Expand All @@ -368,6 +379,7 @@ function formatBuildChoice(build: BuildFragment): prompts.Choice {
? chalk.bold(message.length > 200 ? `${message.slice(0, 200)}...` : message)
: null,
},
{ name: 'Status', value: buildStatusMapping[status] },
];

const filteredDescriptionArray: string[] = descriptionItems
Expand Down
65 changes: 64 additions & 1 deletion packages/eas-cli/src/submit/__tests__/ArchiveSource-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/creat
import {
AppPlatform,
BuildFragment,
BuildStatus,
SubmissionArchiveSourceType,
UploadSessionType,
} from '../../graphql/generated';
Expand Down Expand Up @@ -41,6 +42,17 @@ const MOCK_BUILD_FRAGMENT: Partial<BuildFragment> = {
appVersion: '1.2.3',
platform: AppPlatform.Android,
updatedAt: Date.now(),
status: BuildStatus.Finished,
};
const MOCK_IN_PROGRESS_BUILD_FRAGMENT: Partial<BuildFragment> = {
id: uuidv4(),
artifacts: {
buildUrl: ARCHIVE_SOURCE.url,
},
appVersion: '1.2.3',
platform: AppPlatform.Android,
updatedAt: Date.now(),
status: BuildStatus.InProgress,
};

const SOURCE_STUB_INPUT = {
Expand Down Expand Up @@ -246,7 +258,7 @@ describe(getArchiveAsync, () => {
expect(archive.sourceType).toBe(ArchiveSourceType.url);
});

it('handles build-list-select source', async () => {
it('handles build-list-select source for finished builds', async () => {
const projectId = uuidv4();
jest
.mocked(getRecentBuildsForSubmissionAsync)
Expand All @@ -269,6 +281,57 @@ describe(getArchiveAsync, () => {
expect(archive.sourceType).toBe(ArchiveSourceType.build);
});

it('handles build-list-select source for in-progress builds', async () => {
const projectId = uuidv4();
jest
.mocked(getRecentBuildsForSubmissionAsync)
.mockResolvedValueOnce([MOCK_IN_PROGRESS_BUILD_FRAGMENT as BuildFragment]);
jest
.mocked(promptAsync)
.mockResolvedValueOnce({ selectedBuild: MOCK_IN_PROGRESS_BUILD_FRAGMENT });

const archive = await getArchiveAsync(
{ ...SOURCE_STUB_INPUT, graphqlClient, projectId },
{
sourceType: ArchiveSourceType.buildList,
}
);

expect(getRecentBuildsForSubmissionAsync).toBeCalledWith(
graphqlClient,
toAppPlatform(SOURCE_STUB_INPUT.platform),
projectId,
{ limit: BUILD_LIST_ITEM_COUNT }
);
expect(archive.sourceType).toBe(ArchiveSourceType.build);
});

it('handles build-list-select source for both finished and in-progress builds', async () => {
const projectId = uuidv4();
jest
.mocked(getRecentBuildsForSubmissionAsync)
.mockResolvedValueOnce([
MOCK_IN_PROGRESS_BUILD_FRAGMENT as BuildFragment,
MOCK_BUILD_FRAGMENT as BuildFragment,
]);
jest.mocked(promptAsync).mockResolvedValueOnce({ selectedBuild: MOCK_BUILD_FRAGMENT });

const archive = await getArchiveAsync(
{ ...SOURCE_STUB_INPUT, graphqlClient, projectId },
{
sourceType: ArchiveSourceType.buildList,
}
);

expect(getRecentBuildsForSubmissionAsync).toBeCalledWith(
graphqlClient,
toAppPlatform(SOURCE_STUB_INPUT.platform),
projectId,
{ limit: BUILD_LIST_ITEM_COUNT }
);
expect(archive.sourceType).toBe(ArchiveSourceType.build);
});

it('prompts again if all builds have expired', async () => {
jest.mocked(getRecentBuildsForSubmissionAsync).mockResolvedValueOnce([
{
Expand Down
226 changes: 226 additions & 0 deletions packages/eas-cli/src/submit/utils/__tests__/builds-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import { v4 as uuidv4 } from 'uuid';

import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import {
AppPlatform,
BuildFragment,
BuildStatus,
SubmissionArchiveSourceType,
} from '../../../graphql/generated';
import { BuildQuery } from '../../../graphql/queries/BuildQuery';
import { getRecentBuildsForSubmissionAsync } from '../builds';

jest.mock('../../../graphql/queries/BuildQuery', () => ({
BuildQuery: {
viewBuildsOnAppAsync: jest.fn(),
},
}));

const ARCHIVE_SOURCE = {
type: SubmissionArchiveSourceType.Url,
url: 'https://url.to/archive.tar.gz',
};

const MOCK_BUILD_FRAGMENTS: Partial<BuildFragment>[] = Array(5).map(() => ({
id: uuidv4(),
artifacts: {
buildUrl: ARCHIVE_SOURCE.url,
},
appVersion: '1.2.3',
platform: AppPlatform.Android,
updatedAt: Date.now(),
createdAt: Date.now(),
status: BuildStatus.Finished,
}));
const MOCK_IN_PROGRESS_BUILD_FRAGMENTS: Partial<BuildFragment>[] = Array(2).map(() => ({
id: uuidv4(),
artifacts: {
buildUrl: ARCHIVE_SOURCE.url,
},
appVersion: '1.2.3',
platform: AppPlatform.Android,
updatedAt: Date.now(),
createdAt: Date.now(),
status: BuildStatus.InProgress,
}));
const MOCK_IN_QUEUE_BUILD_FRAGMENTS = Array(2).map(() => ({
id: uuidv4(),
artifacts: {
buildUrl: ARCHIVE_SOURCE.url,
},
appVersion: '1.2.3',
platform: AppPlatform.Android,
updatedAt: Date.now(),
createdAt: Date.now(),
status: BuildStatus.InQueue,
}));
const MOCK_NEW_BUILD_FRAGMENTS = Array(1).map(() => ({
id: uuidv4(),
artifacts: {
buildUrl: ARCHIVE_SOURCE.url,
},
appVersion: '1.2.3',
platform: AppPlatform.Android,
updatedAt: Date.now(),
createdAt: Date.now(),
status: BuildStatus.New,
}));

describe(getRecentBuildsForSubmissionAsync, () => {
let graphqlClient: ExpoGraphqlClient;

beforeEach(() => {
graphqlClient = {} as any as ExpoGraphqlClient;
});

it('returns finished builds', async () => {
const appId = uuidv4();
const limit = 2;
jest
.mocked(BuildQuery.viewBuildsOnAppAsync)
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce(
MOCK_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_BUILD_FRAGMENTS.length)
) as BuildFragment[]
);

const result = await getRecentBuildsForSubmissionAsync(
graphqlClient,
AppPlatform.Android,
appId,
{ limit }
);

expect(result).toMatchObject(
MOCK_BUILD_FRAGMENTS.slice(0, Math.min(limit, MOCK_BUILD_FRAGMENTS.length))
);
});

it('returns in-progress builds', async () => {
const appId = uuidv4();
const limit = 2;
jest
.mocked(BuildQuery.viewBuildsOnAppAsync)
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce(
MOCK_IN_PROGRESS_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_IN_PROGRESS_BUILD_FRAGMENTS.length)
) as BuildFragment[]
)
.mockResolvedValueOnce([] as BuildFragment[]);

const result = await getRecentBuildsForSubmissionAsync(
graphqlClient,
AppPlatform.Android,
appId,
{ limit }
);

expect(result).toMatchObject(
MOCK_IN_PROGRESS_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_IN_PROGRESS_BUILD_FRAGMENTS.length)
)
);
});

it('returns in-queue builds', async () => {
const appId = uuidv4();
const limit = 2;
jest
.mocked(BuildQuery.viewBuildsOnAppAsync)
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce(
MOCK_IN_QUEUE_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_IN_QUEUE_BUILD_FRAGMENTS.length)
) as BuildFragment[]
)
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce([] as BuildFragment[]);

const result = await getRecentBuildsForSubmissionAsync(
graphqlClient,
AppPlatform.Android,
appId,
{ limit }
);

expect(result).toMatchObject(
MOCK_IN_QUEUE_BUILD_FRAGMENTS.slice(0, Math.min(limit, MOCK_IN_QUEUE_BUILD_FRAGMENTS.length))
);
});

it('returns new builds', async () => {
const appId = uuidv4();
const limit = 2;
jest
.mocked(BuildQuery.viewBuildsOnAppAsync)
.mockResolvedValueOnce(
MOCK_NEW_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_NEW_BUILD_FRAGMENTS.length)
) as BuildFragment[]
)
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce([] as BuildFragment[]);

const result = await getRecentBuildsForSubmissionAsync(
graphqlClient,
AppPlatform.Android,
appId,
{ limit }
);

expect(result).toMatchObject(
MOCK_NEW_BUILD_FRAGMENTS.slice(0, Math.min(limit, MOCK_NEW_BUILD_FRAGMENTS.length))
);
});

it('returns up to "limit" newest builds regardless of status', async () => {
const appId = uuidv4();
const limit = 2;
jest
.mocked(BuildQuery.viewBuildsOnAppAsync)
.mockResolvedValueOnce(
MOCK_NEW_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_NEW_BUILD_FRAGMENTS.length)
) as BuildFragment[]
)
.mockResolvedValueOnce(
MOCK_IN_QUEUE_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_IN_QUEUE_BUILD_FRAGMENTS.length)
) as BuildFragment[]
)
.mockResolvedValueOnce(
MOCK_IN_PROGRESS_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_IN_PROGRESS_BUILD_FRAGMENTS.length)
) as BuildFragment[]
)
.mockResolvedValueOnce(
MOCK_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_BUILD_FRAGMENTS.length)
) as BuildFragment[]
);

const result = await getRecentBuildsForSubmissionAsync(
graphqlClient,
AppPlatform.Android,
appId,
{ limit }
);

expect(result).toMatchObject([MOCK_NEW_BUILD_FRAGMENTS[0], MOCK_IN_QUEUE_BUILD_FRAGMENTS[1]]);
});
});
Loading

0 comments on commit dadf58f

Please sign in to comment.