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

fix: display retrieve warnings #121

Merged
merged 1 commit into from
Jun 28, 2021
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
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