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

Feature/shayel/add support for multi region param interactive #96

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 package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mondaycom/apps-cli",
"version": "2.6.2-beta.1",
"version": "2.6.1",
"description": "A cli tool to manage apps (and monday-code projects) in monday.com",
"author": "monday.com Apps Team",
"type": "module",
Expand Down
11 changes: 8 additions & 3 deletions src/commands/app/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getManifestAssetPath, readManifestFile } from 'services/manifest-servic
import { getTasksForClientSide, getTasksForServerSide } from 'services/share/deploy';
import { ManifestHostingType } from 'types/services/manifest-service';
import logger from 'utils/logger';
import { addRegionToFlags, getRegionFromString } from 'utils/region';
import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region';

const MESSAGES = {
directory: 'Directory path of you project in your machine. If not included will use the current working directory.',
Expand Down Expand Up @@ -68,6 +68,11 @@ export default class AppDeploy extends AuthenticatedCommand {
appId = appId || manifestFileData.app.id;

appVersionId = await this.getAppVersionId(appVersionId, appId, force);

const selectedRegion = await chooseRegionIfNeeded(region, {
appVersionId: Number(appVersionId),
});

this.preparePrintCommand(this, { appVersionId: appVersionId, directoryPath: manifestFileData });

const { cdn, server } = manifestFileData.app?.hosting || {};
Expand All @@ -76,7 +81,7 @@ export default class AppDeploy extends AuthenticatedCommand {
await getTasksForClientSide(
Number(appVersionId),
getManifestAssetPath(manifestFileDir, cdn.path),
region,
selectedRegion,
).run();
}

Expand All @@ -85,7 +90,7 @@ export default class AppDeploy extends AuthenticatedCommand {
await getTasksForServerSide(
Number(appVersionId),
getManifestAssetPath(manifestFileDir, server.path),
region,
selectedRegion,
).run();
}
} catch (error) {
Expand Down
5 changes: 4 additions & 1 deletion src/commands/app/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { App } from 'types/services/apps-service';
import logger from 'utils/logger';

const printApps = (apps: Array<App>) => {
logger.table(apps);
const cleanedApps = apps.map(app => {
return { id: app.id, name: app.name };
});
logger.table(cleanedApps);
};

export default class AppList extends AuthenticatedCommand {
Expand Down
8 changes: 5 additions & 3 deletions src/commands/code/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { PromptService } from 'services/prompt-service';
import { ManageAppEnvFlags } from 'types/commands/manage-app-env';
import { AppId } from 'types/general';
import logger from 'utils/logger';
import { addRegionToFlags, getRegionFromString } from 'utils/region';
import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region';

const MODES_WITH_KEYS: Array<APP_ENV_MANAGEMENT_MODES> = [
APP_ENV_MANAGEMENT_MODES.SET,
Expand Down Expand Up @@ -107,12 +107,14 @@ export default class Env extends AuthenticatedCommand {
appId = Number(await DynamicChoicesService.chooseApp());
}

const selectedRegion = await chooseRegionIfNeeded(region, { appId });

mode = await promptForModeIfNotProvided(mode);
key = await promptForKeyIfNotProvided(mode, appId, key);
value = await promptForValueIfNotProvided(mode, value);
this.preparePrintCommand(this, { appId, mode, key, value });
this.preparePrintCommand(this, { appId, mode, key, value, region: selectedRegion });

await handleEnvironmentRequest(appId, mode, key, value, region);
await handleEnvironmentRequest(appId, mode, key, value, selectedRegion);
} catch (error: any) {
logger.debug(error, this.DEBUG_TAG);

Expand Down
6 changes: 3 additions & 3 deletions src/commands/code/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { PromptService } from 'services/prompt-service';
import { EventSource, LogType, LogsCommandArguments, LogsFilterCriteriaArguments } from 'types/commands/logs';
import { isDefined } from 'utils/guards';
import logger from 'utils/logger';
import { addRegionToFlags, getRegionFromString } from 'utils/region';
import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region';
import { TIME_IN_MILLISECONDS } from 'utils/time-enum';
import { getDayDiff, isDate } from 'utils/validations';

Expand Down Expand Up @@ -89,7 +89,7 @@ export default class Logs extends AuthenticatedCommand {
const { logsStartDate, logsEndDate, logSearchFromText, region: strRegion } = flags;
const region = getRegionFromString(strRegion);
const appVersionId = await this.getAppVersionId(flags.appVersionId);

const selectedRegion = await chooseRegionIfNeeded(region, { appVersionId });
const eventSource = (flags.eventSource || (await eventSourcePrompt())) as EventSource;
const logsType = await this.getLogType(eventSource, flags.logsType);
const logsFilterCriteria = await this.getLogsFilterCriteria(
Expand All @@ -114,7 +114,7 @@ export default class Logs extends AuthenticatedCommand {
logSearchFromText: logsFilterCriteria?.text,
});

const clientChannel = await logsStream(args.appVersionId, args.logsType, logsFilterCriteria, region);
const clientChannel = await logsStream(args.appVersionId, args.logsType, logsFilterCriteria, selectedRegion);
await streamMessages(clientChannel);
} catch (error: any) {
logger.debug(error, this.DEBUG_TAG);
Expand Down
6 changes: 4 additions & 2 deletions src/commands/code/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { APP_ID_TO_ENTER, APP_VERSION_ID_TO_ENTER } from 'consts/messages';
import { DynamicChoicesService } from 'services/dynamic-choices-service';
import { getTasksForServerSide } from 'services/share/deploy';
import logger from 'utils/logger';
import { addRegionToFlags, getRegionFromString } from 'utils/region';
import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region';

const MESSAGES = {
directory: 'Directory path of you project in your machine. If not included will use the current working directory.',
Expand Down Expand Up @@ -65,10 +65,12 @@ export default class Push extends AuthenticatedCommand {
appVersionId = appAndAppVersion.appVersionId;
}

const selectedRegion = await chooseRegionIfNeeded(region, { appVersionId });

logger.debug(`push code to appVersionId: ${appVersionId}`, this.DEBUG_TAG);
this.preparePrintCommand(this, { appVersionId, directoryPath: directoryPath });

const tasks = getTasksForServerSide(appVersionId, directoryPath, region);
const tasks = getTasksForServerSide(appVersionId, directoryPath, selectedRegion);

await tasks.run();
} catch (error: any) {
Expand Down
10 changes: 7 additions & 3 deletions src/commands/code/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getMondayCodeBuild } from 'src/services/app-builds-service';
import { HttpError } from 'types/errors';
import { AppVersionDeploymentStatus } from 'types/services/push-service';
import logger from 'utils/logger';
import { addRegionToFlags, getRegionFromString } from 'utils/region';
import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region';

const DEBUG_TAG = 'code_status';

Expand Down Expand Up @@ -58,9 +58,11 @@ export default class Status extends AuthenticatedCommand {
appVersionId = appAndAppVersion.appVersionId;
}

const selectedRegion = await chooseRegionIfNeeded(region, { appVersionId });

this.preparePrintCommand(this, { appVersionId });
const deploymentStatus = await getAppVersionDeploymentStatus(appVersionId, region);
const mondayCodeRelease = await getMondayCodeBuild(appVersionId, region);
const deploymentStatus = await getAppVersionDeploymentStatus(appVersionId, selectedRegion);
const mondayCodeRelease = await getMondayCodeBuild(appVersionId, selectedRegion);

if (deploymentStatus.deployment) {
deploymentStatus.deployment.liveUrl = mondayCodeRelease?.data?.liveUrl;
Expand All @@ -71,6 +73,8 @@ export default class Status extends AuthenticatedCommand {
logger.debug({ res: error }, DEBUG_TAG);
if (error instanceof HttpError && error.code === StatusCodes.NOT_FOUND) {
logger.error(`No deployment found for provided app version id - "${appVersionId || VAR_UNKNOWN}"`);
} else if (error instanceof HttpError && error.code === 400) {
logger.error(error.message);
} else {
logger.error(
`An unknown error happened while fetching deployment status for app version id - "${
Expand Down
7 changes: 6 additions & 1 deletion src/consts/urls.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AppFeatureType } from 'src/types/services/app-features-service';
import { LogType, LogsFilterCriteriaArguments } from 'types/commands/logs';
import { AppId } from 'types/general';
import { AppId, AppVersionId } from 'types/general';
import { Region } from 'types/general/region';

const BASE_APPS_URL = '/api/apps';
const BASE_VERSIONS_URL = '/api/app-versions';
const BASE_APP_VERSIONS_URL = '/api/app-versions';
const BASE_MONDAY_CODE_URL = '/api/code';

Expand Down Expand Up @@ -73,6 +74,10 @@ export const listAppVersionsByAppIdUrl = (appId: AppId): string => {
return `${BASE_APPS_URL}/${appId}/versions`;
};

export const getAppVersionsByAppIdUrl = (appVersionId: AppVersionId): string => {
return `${BASE_VERSIONS_URL}/${appVersionId}`;
};

export const appEnvironmentUrl = (appId: AppId, key: string): string => {
return `/api/code/${appId}/env/${key}`;
};
Expand Down
33 changes: 29 additions & 4 deletions src/services/app-versions-service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { APP_VERSION_STATUS } from 'consts/app-versions';
import { listAppVersionsByAppIdUrl } from 'consts/urls';
import { getAppVersionsByAppIdUrl, listAppVersionsByAppIdUrl } from 'consts/urls';
import { execute } from 'services/api-service';
import { listAppVersionsSchema } from 'services/schemas/app-versions-schemas';
import { getAppVersionSchema, listAppVersionsSchema } from 'services/schemas/app-versions-schemas';
import logger from 'src/utils/logger';
import { HttpError } from 'types/errors';
import { AppId } from 'types/general';
import { AppId, AppVersionId } from 'types/general';
import { HttpMethodTypes } from 'types/services/api-service';
import { AppVersion, ListAppVersionsResponse } from 'types/services/app-versions-service';
import { AppVersion, GetAppVersionResponse, ListAppVersionsResponse } from 'types/services/app-versions-service';
import { appsUrlBuilder } from 'utils/urls-builder';

export const listAppVersionsByAppId = async (appId: AppId): Promise<Array<AppVersion>> => {
Expand Down Expand Up @@ -51,3 +51,28 @@ export const defaultVersionByAppId = async (appId: AppId, useLiveVersion = false

return validVersion;
};

export const getAppVersionById = async (appVersionId: AppVersionId): Promise<AppVersion> => {
try {
const path = getAppVersionsByAppIdUrl(appVersionId);
const url = appsUrlBuilder(path);
logger.debug(`fetching logs url: ${url}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong log

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? it's for --verbose
we already have something like this
@maorb-dev

const response = await execute<GetAppVersionResponse>(
{
url,
headers: { Accept: 'application/json' },
method: HttpMethodTypes.GET,
},
getAppVersionSchema,
);

return response.appVersion;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it response.appVersion ? or response.appVersions ? because this is an array

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you say it's returning an array this is a new endpoint that returns a single app version by id
@maorb-dev

} catch (error: any) {
if (error instanceof HttpError) {
logger.error(error.message);
throw error;
}

throw new Error('Failed to list app versions.');
}
};
7 changes: 7 additions & 0 deletions src/services/apps-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,10 @@ export const createFeatures = async (ctx: AppCreateCommandTasksContext) => {

await Promise.all(createFeaturesPromises);
};

export const checkIfAppSupportMultiRegion = async (appId: number): Promise<boolean> => {
const apps = await listApps();
const app = apps.find(app => app.id === appId);
if (!app) throw new Error(`App with id ${appId} not found.`);
return Boolean(app.mondayCodeConfig?.isMultiRegion);
};
8 changes: 8 additions & 0 deletions src/services/schemas/app-versions-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { z } from 'zod';

import { APP_VERSION_STATUS } from 'consts/app-versions';
import { baseResponseHttpMetaDataSchema } from 'services/schemas/api-service-schemas';
import { mondayCodeConfigSchema } from 'services/schemas/apps-service-schemas';
import { appIdSchema, appVersionIdSchema } from 'services/schemas/general-schemas';

export const appVersionSchema = z.object({
Expand All @@ -10,10 +11,17 @@ export const appVersionSchema = z.object({
versionNumber: z.string(),
appId: appIdSchema,
status: z.nativeEnum(APP_VERSION_STATUS),
mondayCodeConfig: mondayCodeConfigSchema,
});

export const listAppVersionsSchema = z
.object({
appVersions: z.array(appVersionSchema),
})
.merge(baseResponseHttpMetaDataSchema);

export const getAppVersionSchema = z
.object({
appVersion: appVersionSchema,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment - version? versions?

})
.merge(baseResponseHttpMetaDataSchema);
6 changes: 6 additions & 0 deletions src/services/schemas/apps-service-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ import { z } from 'zod';
import { baseResponseHttpMetaDataSchema } from 'services/schemas/api-service-schemas';
import { appIdSchema } from 'services/schemas/general-schemas';

export const mondayCodeConfigSchema = z
.object({
isMultiRegion: z.boolean().optional(),
})
.optional();
export const appSchema = z.object({
id: appIdSchema,
name: z.string(),
mondayCodeConfig: mondayCodeConfigSchema,
});

export const listAppSchema = z
Expand Down
3 changes: 2 additions & 1 deletion src/types/services/app-versions-service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { z } from 'zod';

import { appVersionSchema, listAppVersionsSchema } from 'services/schemas/app-versions-schemas';
import { appVersionSchema, getAppVersionSchema, listAppVersionsSchema } from 'services/schemas/app-versions-schemas';

export type AppVersion = z.infer<typeof appVersionSchema>;
export type GetAppVersionResponse = z.infer<typeof getAppVersionSchema>;
export type ListAppVersionsResponse = z.infer<typeof listAppVersionsSchema>;
44 changes: 44 additions & 0 deletions src/utils/region.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Flags } from '@oclif/core';

import { getAppVersionById } from 'services/app-versions-service';
import { checkIfAppSupportMultiRegion } from 'services/apps-service';
import { PromptService } from 'services/prompt-service';
import { Region } from 'types/general/region';
import { Permissions } from 'types/utils/permissions';
import { isPermitted } from 'utils/permissions';
import { isANumber } from 'utils/validations';

export const addRegionToQuery = (query: object | undefined, region?: Region) => {
if (region) {
Expand Down Expand Up @@ -34,3 +38,43 @@ export function addRegionToFlags<T>(flags: T): T {

return flags;
}

const regionsPrompt = async () =>
PromptService.promptList('Choose region', [Region.US, Region.EU, Region.AU], Region.US);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this use US as default? if so, why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

US is the auto-selected option for the prompt
@maorb-dev

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdym? lets talk about this: @ShayElkana


export async function chooseRegionIfNeeded(
region?: Region,
options?: { appId?: number; appVersionId?: number },
): Promise<Region | undefined> {
if (region || !isPermitted(Permissions.MCODE_MULTI_REGION)) {
return region;
}

const { appId, appVersionId } = options || {};

let isMultiRegionApp = false;

let _appId = appId;
if (appVersionId && isANumber(appVersionId)) {
const appVersion = await getAppVersionById(appVersionId);
if (!appVersion) throw new Error(`AppVersion with id ${appVersionId} not found.`);
_appId = appVersion.appId;
if (appVersion?.mondayCodeConfig?.isMultiRegion) {
isMultiRegionApp = true;
}
}

if (!isMultiRegionApp && _appId && isANumber(_appId)) {
const isAppSupportMultiRegion = await checkIfAppSupportMultiRegion(_appId);
if (isAppSupportMultiRegion) {
isMultiRegionApp = true;
}
}

if (!isMultiRegionApp) {
return region;
}

const returnedRegion = await regionsPrompt();
return getRegionFromString(returnedRegion);
}
16 changes: 16 additions & 0 deletions src/utils/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,19 @@ export const getDayDiff = (fromDate: Date, toDate: Date): number | null => {
const diffInMS = toDate.getTime() - fromDate.getTime();
return diffInMS / TIME_IN_MILLISECONDS.DAY;
};

export function isDefined<T>(input: T | null | undefined): input is T {
return input !== null && input !== undefined;
}

/***
* This function receives an input (string or number) and returns true if it is a number
* @param input - The input to check if it is a number
* @returns true if the input is a number
*/
export function isANumber(input: unknown): input is number {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
return isDefined(input) && `${Number(input)}` === `${input}` && Number.isFinite(input);
}
Loading