Skip to content

Commit

Permalink
fix: display retrieve warnings (#121)
Browse files Browse the repository at this point in the history
Changes the retrieveResultFormatter to display warnings properly
  • Loading branch information
shetzel authored Jun 28, 2021
1 parent 95a7601 commit a2330ed
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 66 deletions.
1 change: 1 addition & 0 deletions messages/retrieve.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"SourceRetrieveError": "Could not retrieve files in the sourcepath%s",
"retrieveTimeout": "Your retrieve request did not complete within the specified wait time [%s minutes]. Try again with a longer wait time.",
"retrievedSourceHeader": "Retrieved Source",
"retrievedSourceWarningsHeader": "Retrieved Source Warnings",
"fullNameTableColumn": "FULL NAME",
"typeTableColumn": "TYPE",
"workspacePathTableColumn": "PROJECT PATH",
Expand Down
74 changes: 48 additions & 26 deletions src/formatters/retrieveResultFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { blue } from 'chalk';
import { blue, yellow } from 'chalk';
import { UX } from '@salesforce/command';
import { Logger, Messages } from '@salesforce/core';
import { get, getString, getNumber } from '@salesforce/ts-types';
import { RetrieveResult, MetadataApiRetrieveStatus } from '@salesforce/source-deploy-retrieve';
import { FileResponse, RequestStatus, RetrieveMessage } from '@salesforce/source-deploy-retrieve/lib/src/client/types';
import {
ComponentStatus,
FileResponse,
RequestStatus,
RetrieveMessage,
} from '@salesforce/source-deploy-retrieve/lib/src/client/types';
import { ResultFormatter, ResultFormatterOptions } from './resultFormatter';

Messages.importMessagesDirectory(__dirname);
Expand All @@ -31,11 +36,14 @@ export interface RetrieveCommandResult {
export class RetrieveResultFormatter extends ResultFormatter {
protected result: RetrieveResult;
protected fileResponses: FileResponse[];
protected warnings: RetrieveMessage[];

public constructor(logger: Logger, ux: UX, options: ResultFormatterOptions, result: RetrieveResult) {
super(logger, ux, options);
this.result = result;
this.fileResponses = result?.getFileResponses ? result.getFileResponses() : [];
const warnMessages = get(result, 'response.messages', []) as RetrieveMessage | RetrieveMessage[];
this.warnings = Array.isArray(warnMessages) ? warnMessages : [warnMessages];
}

/**
Expand All @@ -44,12 +52,10 @@ export class RetrieveResultFormatter extends ResultFormatter {
* @returns RetrieveCommandResult
*/
public getJson(): RetrieveCommandResult {
const warnMessages = get(this.result, 'response.messages', []);
const warnings = Array.isArray(warnMessages) ? warnMessages : [warnMessages];
return {
inboundFiles: this.fileResponses,
packages: [],
warnings,
warnings: this.warnings,
response: this.result.response,
};
}
Expand All @@ -64,34 +70,21 @@ export class RetrieveResultFormatter extends ResultFormatter {
return;
}

this.ux.styledHeader(blue(messages.getMessage('retrievedSourceHeader')));
if (this.isSuccess()) {
if (this.fileResponses?.length) {
this.sortFileResponses(this.fileResponses);
this.asRelativePaths(this.fileResponses);
const columns = [
{ key: 'fullName', label: 'FULL NAME' },
{ key: 'type', label: 'TYPE' },
{ key: 'filePath', label: 'PROJECT PATH' },
];
this.ux.table(this.fileResponses, { columns });
if (this.warnings.length) {
this.displayWarnings();
}
this.ux.styledHeader(blue(messages.getMessage('retrievedSourceHeader')));
const retrievedFiles = this.fileResponses.filter((fr) => fr.state !== ComponentStatus.Failed);
if (retrievedFiles?.length) {
this.displaySuccesses(retrievedFiles);
} else {
this.ux.log(messages.getMessage('NoResultsFound'));
}
} else {
const unknownMsg: RetrieveMessage[] = [{ fileName: 'unknown', problem: 'unknown' }];
const responseMsgs = get(this.result, 'response.messages', unknownMsg) as RetrieveMessage | RetrieveMessage[];
const errMsgs = Array.isArray(responseMsgs) ? responseMsgs : [responseMsgs];
const errMsgsForDisplay = errMsgs.reduce<string>((p, c) => `${p}\n${c.fileName}: ${c.problem}`, '');
this.ux.log(`Retrieve Failed due to: ${errMsgsForDisplay}`);
this.displayErrors();
}

// if (results.status === 'SucceededPartial' && results.successes.length && results.failures.length) {
// this.ux.log('');
// this.ux.styledHeader(yellow(messages.getMessage('metadataNotFoundWarning')));
// results.failures.forEach((warning) => this.ux.log(warning.message));
// }

// Display any package retrievals
// if (results.packages && results.packages.length) {
// this.logger.styledHeader(this.logger.color.blue('Retrieved Packages'));
Expand All @@ -109,4 +102,33 @@ export class RetrieveResultFormatter extends ResultFormatter {
protected hasComponents(): boolean {
return getNumber(this.result, 'components.size', 0) === 0;
}

private displayWarnings(): void {
this.ux.styledHeader(yellow(messages.getMessage('retrievedSourceWarningsHeader')));
const columns = [
{ key: 'fileName', label: 'FILE NAME' },
{ key: 'problem', label: 'PROBLEM' },
];
this.ux.table(this.warnings, { columns });
this.ux.log();
}

private displaySuccesses(retrievedFiles: FileResponse[]): void {
this.sortFileResponses(retrievedFiles);
this.asRelativePaths(retrievedFiles);
const columns = [
{ key: 'fullName', label: 'FULL NAME' },
{ key: 'type', label: 'TYPE' },
{ key: 'filePath', label: 'PROJECT PATH' },
];
this.ux.table(retrievedFiles, { columns });
}

private displayErrors(): void {
const unknownMsg: RetrieveMessage[] = [{ fileName: 'unknown', problem: 'unknown' }];
const responseMsgs = get(this.result, 'response.messages', unknownMsg) as RetrieveMessage | RetrieveMessage[];
const errMsgs = Array.isArray(responseMsgs) ? responseMsgs : [responseMsgs];
const errMsgsForDisplay = errMsgs.reduce<string>((p, c) => `${p}\n${c.fileName}: ${c.problem}`, '');
this.ux.log(`Retrieve Failed due to: ${errMsgsForDisplay}`);
}
}
93 changes: 58 additions & 35 deletions test/commands/source/retrieveResponses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,46 @@ import { RetrieveResult } from '@salesforce/source-deploy-retrieve';
import { RequestStatus } from '@salesforce/source-deploy-retrieve/lib/src/client/types';
import { MetadataApiRetrieveStatus } from '@salesforce/source-deploy-retrieve/lib/src/client/types';

const packageFileProp = {
createdById: '00521000007KA39AAG',
createdByName: 'User User',
createdDate: '2021-04-28T17:12:58.964Z',
fileName: 'unpackaged/package.xml',
fullName: 'unpackaged/package.xml',
id: '',
lastModifiedById: '00521000007KA39AAG',
lastModifiedByName: 'User User',
lastModifiedDate: '2021-04-28T17:12:58.964Z',
manageableState: 'unmanaged',
type: 'Package',
};

const apexClassFileProp = {
createdById: '00521000007KA39AAG',
createdByName: 'User User',
createdDate: '2021-04-23T18:55:07.000Z',
fileName: 'unpackaged/classes/ProductController.cls',
fullName: 'ProductController',
id: '01p2100000A6XiqAAF',
lastModifiedById: '00521000007KA39AAG',
lastModifiedByName: 'User User',
lastModifiedDate: '2021-04-27T22:18:05.000Z',
manageableState: 'unmanaged',
type: 'ApexClass',
};

const baseRetrieveResponse = {
done: true,
fileProperties: [
{
createdById: '00521000007KA39AAG',
createdByName: 'User User',
createdDate: '2021-04-23T18:55:07.000Z',
fileName: 'unpackaged/classes/ProductController.cls',
fullName: 'ProductController',
id: '01p2100000A6XiqAAF',
lastModifiedById: '00521000007KA39AAG',
lastModifiedByName: 'User User',
lastModifiedDate: '2021-04-27T22:18:05.000Z',
manageableState: 'unmanaged',
type: 'ApexClass',
},
{
createdById: '00521000007KA39AAG',
createdByName: 'User User',
createdDate: '2021-04-28T17:12:58.964Z',
fileName: 'unpackaged/package.xml',
fullName: 'unpackaged/package.xml',
id: '',
lastModifiedById: '00521000007KA39AAG',
lastModifiedByName: 'User User',
lastModifiedDate: '2021-04-28T17:12:58.964Z',
manageableState: 'unmanaged',
type: 'Package',
},
],
fileProperties: [apexClassFileProp, packageFileProp],
id: '09S21000002jxznEAA',
status: 'Succeeded',
success: true,
zipFile: 'UEsDBBQA...some_long_string',
};

export type RetrieveResponseType = 'success' | 'inProgress' | 'failed' | 'empty';
const warningMessage = "Entity of type 'ApexClass' named 'ProductController' cannot be found";

export type RetrieveResponseType = 'success' | 'inProgress' | 'failed' | 'empty' | 'warnings';

export const getRetrieveResponse = (
type: RetrieveResponseType,
Expand All @@ -69,6 +72,14 @@ export const getRetrieveResponse = (
response.fileProperties = [];
}

if (type === 'warnings') {
response.messages = {
fileName: packageFileProp.fileName,
problem: warningMessage,
};
response.fileProperties = [packageFileProp];
}

return response as MetadataApiRetrieveStatus;
};

Expand All @@ -85,12 +96,24 @@ export const getRetrieveResult = (
fileProps = Array.isArray(fileProps) ? fileProps : [fileProps];
return fileProps
.filter((p) => p.type !== 'Package')
.map((comp) => ({
fullName: comp.fullName,
filePath: comp.fileName,
state: 'Changed',
type: comp.type,
}));
.map((comp) => {
if (type === 'warnings') {
return {
fullName: apexClassFileProp.fullName,
state: 'Failed',
type: apexClassFileProp.type,
error: warningMessage,
problemType: 'Error',
};
} else {
return {
fullName: comp.fullName,
filePath: comp.fileName,
state: 'Changed',
type: comp.type,
};
}
});
},
} as RetrieveResult;
};
26 changes: 21 additions & 5 deletions test/formatters/retrieveResultFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('RetrieveResultFormatter', () => {
const retrieveResultFailure = getRetrieveResult('failed');
const retrieveResultInProgress = getRetrieveResult('inProgress');
const retrieveResultEmpty = getRetrieveResult('empty');
const retrieveResultWarnings = getRetrieveResult('warnings');

const logger = Logger.childFromRoot('retrieveTestLogger').useMemoryLogging();
let ux;
Expand Down Expand Up @@ -88,14 +89,16 @@ describe('RetrieveResultFormatter', () => {
expect(formatter.getJson()).to.deep.equal(expectedSuccessResults);
});

it.skip('should return expected json for a success with warnings', async () => {
it('should return expected json for a success with warnings', async () => {
const warnMessages = retrieveResultWarnings.response.messages;
const warnings = Array.isArray(warnMessages) ? warnMessages : [warnMessages];
const expectedSuccessResults: RetrieveCommandResult = {
inboundFiles: retrieveResultSuccess.getFileResponses(),
inboundFiles: retrieveResultWarnings.getFileResponses(),
packages: [],
warnings: [],
response: cloneJson(retrieveResultSuccess.response),
warnings,
response: cloneJson(retrieveResultWarnings.response),
};
const formatter = new RetrieveResultFormatter(logger, ux, {}, retrieveResultSuccess);
const formatter = new RetrieveResultFormatter(logger, ux, {}, retrieveResultWarnings);
expect(formatter.getJson()).to.deep.equal(expectedSuccessResults);
});
});
Expand Down Expand Up @@ -133,6 +136,19 @@ describe('RetrieveResultFormatter', () => {
expect(logStub.firstCall.args[0]).to.contain('Retrieve Failed due to:');
});

it('should output as expected for warnings', async () => {
const formatter = new RetrieveResultFormatter(logger, ux, {}, retrieveResultWarnings);
formatter.display();
// Should call styledHeader for warnings and the standard "Retrieved Source" header
expect(styledHeaderStub.calledTwice).to.equal(true);
expect(logStub.called).to.equal(true);
expect(tableStub.calledOnce).to.equal(true);
expect(styledHeaderStub.firstCall.args[0]).to.contain('Retrieved Source Warnings');
const warnMessages = retrieveResultWarnings.response.messages;
const warnings = Array.isArray(warnMessages) ? warnMessages : [warnMessages];
expect(tableStub.firstCall.args[0]).to.deep.equal(warnings);
});

it('should output a message when no results were returned', async () => {
const formatter = new RetrieveResultFormatter(logger, ux, {}, retrieveResultEmpty);
formatter.display();
Expand Down

0 comments on commit a2330ed

Please sign in to comment.