Skip to content
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
28 changes: 26 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cloudbeat/client",
"version": "2.2.0",
"version": "2.3.0",
"description": "CloudBeat API client library ",
"keywords": [
"cloudbeat"
Expand Down
8 changes: 8 additions & 0 deletions packages/client/src/base/CbApiError.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export class CbApiError extends Error {
public status?: number;
private path?: string;

constructor(msgOrError: string | Error) {
Expand All @@ -7,6 +8,7 @@ export class CbApiError extends Error {

if (msgOrError instanceof Error && (msgOrError as any).response) {
const { response } = msgOrError as any;
this.status = response.status;
if (response.status === 500) {
this.message = 'Internal server error, please try again later.';
}
Expand Down Expand Up @@ -44,10 +46,16 @@ export class CbApiError extends Error {

this.message = message;
}
else if ((response.status === 400 || response.status === 403) && typeof response.data === 'string' && response.data) {
this.message = response.data;
}
else {
this.message = response.statusText;
}
}
else if (msgOrError instanceof CbApiError) {
this.status = msgOrError.status;
}
Error.captureStackTrace(this, this.constructor);
}
}
20 changes: 6 additions & 14 deletions packages/client/src/base/CbRestApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,20 @@ export class CbRestApiClient {
if (!config.headers['Content-Type'] && !config.headers['content-type']) {
config.headers['Content-Type'] = 'application/json';
}
// TODO:
// Should add support for X-Api-Key header
// Should remove support for Bearer token (backend doesn't seem to support it?)
// Should eventually remove support for apiKey URL query parameter
if (this.authType === AuthenticationType.Bearer) {
config.headers.Authorization = `Bearer ${this.authToken}`;
}
else {
const params = new URLSearchParams({
apiKey: this.authToken,
});
// the key is sent as a header and never as a query parameter: URLs end up in access logs, proxies and browser history
config.headers['X-Api-Key'] = this.authToken;

// append random parameter to each GET request to prevent caching proxies interfering with our requests
if (config.method === 'get') {
params.append('rnd', Math.floor(Math.random() * 1000000000000).toString());
const params = new URLSearchParams({
rnd: Math.floor(Math.random() * 1000000000000).toString(),
});
config.url += `${config.url?.includes('?') ? '&' : '?'}${params.toString()}`;
}

// append query string to the url
if (!config.url?.endsWith('?') && !config.url?.endsWith('&')) {
config.url += '?';
}
config.url += params.toString();
}
// log request
debug(`REQ: ${config.method!} ${config.url!}`);
Expand Down
94 changes: 90 additions & 4 deletions packages/client/src/v1/ProjectApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Debug from 'debug';
import FormData from 'form-data';
import { CbApiError } from '../base/CbApiError';
import { ApiBaseClientV1 } from './ApiBaseClientV1';
import { GetSyncStatusResponse, ProjectSyncStatus } from './dto/project/GetSyncStatusResponse';
import { ProjectSyncStatus } from './dto/project/GetSyncStatusResponse';
import * as V1_API_ENDPOINTS from './endpoints';

const error = Debug('RuntimeApi:error');
Expand Down Expand Up @@ -41,15 +41,101 @@ export class ProjectApi extends ApiBaseClientV1 {
public async getSyncStatus(projectId: string): Promise<ProjectSyncStatus> {
const path = `${V1_API_ENDPOINTS.PROJECTS}/${projectId}/sync/status`;
try {
const response = await this.instance.get(path);
if (!response.data) {
const response: any = await this.instance.get(path);
const status = response?.data ?? response;
if (!status) {
throw new CbApiError('Invalid response, no data recieved.');
}
return new GetSyncStatusResponse(response).toModel();
return new ProjectSyncStatus(status);
}
catch (e: any) {
error(e);
throw new CbApiError(e as Error);
}
}

public async list(): Promise<ProjectInfo[]> {
const path = `${V1_API_ENDPOINTS.PROJECTS}/list/details`;
try {
const response: any = await this.instance.get(path);
const projects = response?.data ?? response;
if (!Array.isArray(projects)) {
throw new CbApiError('Invalid response, project list expected.');
}
return projects as ProjectInfo[];
}
catch (e: any) {
error(e);
throw new CbApiError(e as Error);
}
}

public async create(project: CreateProjectRequest, fileName?: string, fileContent?: Buffer): Promise<number> {
try {
const formData = new FormData();
formData.append('data', JSON.stringify(project), { contentType: 'application/json' });
if (fileName && fileContent) {
formData.append('file', fileContent, { filename: fileName });
}
const response: any = await this.instance.post(
V1_API_ENDPOINTS.PROJECTS,
formData,
{
headers: formData.getHeaders(),
},
);
const id = response?.data?.id ?? response?.id;
if (id === undefined) {
throw new CbApiError('Invalid response, project id expected.');
}
return id as number;
}
catch (e: any) {
error(e);
throw new CbApiError(e as Error);
}
}

public async triggerSync(projectId: string): Promise<void> {
const path = `${V1_API_ENDPOINTS.PROJECTS}/${projectId}/sync`;
try {
await this.instance.post(path);
}
catch (e: any) {
error(e);
throw new CbApiError(e as Error);
}
}
}

export type ProjectSyncType = 'Manual' | 'None' | 'Git';

export interface ProjectInfo {
id: number;
name: string;
type: string;
}

export interface ProjectGitSettings {
url: string;
branchName?: string;
userName?: string;
password?: string;
token?: string;
}

export interface CreateProjectRequest {
name: string;
type: string;
notes?: string;
settings: {
type: string;
syncType: ProjectSyncType;
execCommand?: string;
execOptions?: string;
assemblyNames?: string;
preExecCommands?: string[];
postExecCommands?: string[];
};
gitSettings?: ProjectGitSettings;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class ProjectSyncStatus {
syncDate?: string;
syncStatus?: string;
message?: string;
details?: string;

constructor(data: any) {
Object.assign<ProjectSyncStatus, any>(this, data);
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cloudbeat/playwright",
"version": "2.2.1",
"version": "2.2.2",
"description": "CloudBeat Reporter for Playwright",
"keywords": [
"cloudbeat",
Expand Down
5 changes: 5 additions & 0 deletions packages/playwright/src/cbUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,10 @@ export class cb {
}

const sendToReporter = (type: string, data: any ): void => {
// the reporter consumes these messages only when the test is executed by CloudBeat,
// otherwise they would just pollute the output of local and CI runs
if (!process.env.CB_RUN_ID) {
return;
}
process.stdout.write(JSON.stringify({ type, data }));
};
Loading