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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ npm-debug.log
/.idea/
/dist/
/lib/
.sfdx/
7 changes: 6 additions & 1 deletion examples/vitepress/docs/custom-objects/Price_Component__c.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,9 @@ Use this when the Price Component represents a Flat Price. To represent a Percen

**Type**

*Picklist*
*Picklist*

#### Possible values are
* List Price
* Surcharge
* Discount
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,8 @@ Represents a line item on a sales order.

**Type**

*Picklist*
*Picklist*

#### Possible values are
* Charge
* Discount
22 changes: 21 additions & 1 deletion src/core/markdown/__test__/generating-custom-object-docs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { extendExpect } from './expect-extensions';
import {
customField,
customFieldPickListValues,
customObjectGenerator,
generateDocs,
unparsedFieldBundleFromRawString,
Expand Down Expand Up @@ -46,7 +47,7 @@ describe('Generates Custom Object documentation', () => {
expect(result).documentationBundleHasLength(1);
assertEither(result, (data) => expect(data).firstDocContains('`TestObject__c`'));
});

it('displays the Fields heading if fields are present', async () => {
const customObjectBundle = unparsedObjectBundleFromRawString({
rawContent: customObjectGenerator(),
Expand All @@ -64,6 +65,25 @@ describe('Generates Custom Object documentation', () => {
assertEither(result, (data) => expect(data).firstDocContains('## Fields'));
});

it('displays the pick list values name', async () => {
const customObjectBundle = unparsedObjectBundleFromRawString({
rawContent: customObjectGenerator(),
filePath: 'src/object/TestObject__c.object-meta.xml',
});

const customFieldBundle = unparsedFieldBundleFromRawString({
rawContent: customFieldPickListValues,
filePath: 'src/object/TestField__c.field-meta.xml',
parentName: 'TestObject__c',
});

const result = await generateDocs([customObjectBundle, customFieldBundle])();
expect(result).documentationBundleHasLength(1);
assertEither(result, (data) => expect(data).firstDocContains('* Staging'));
assertEither(result, (data) => expect(data).firstDocContains('* Active'));
assertEither(result, (data) => expect(data).firstDocContains('* Inactive'));
});

it('does not display the Fields heading if no fields are present', async () => {
const input = unparsedObjectBundleFromRawString({
rawContent: customObjectGenerator(),
Expand Down
33 changes: 33 additions & 0 deletions src/core/markdown/__test__/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,36 @@ export const customField = `
<type>Url</type>
<description>A URL that points to a photo</description>
</CustomField>`;

export const customFieldPickListValues = `
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Status__c</fullName>
<externalId>false</externalId>
<label>Status</label>
<required>true</required>
<trackFeedHistory>false</trackFeedHistory>
<description>Status</description>
<type>Picklist</type>
<valueSet>
<restricted>true</restricted>
<valueSetDefinition>
<sorted>false</sorted>
<value>
<fullName>Staging</fullName>
<default>false</default>
<label>Staging</label>
</value>
<value>
<fullName>Active</fullName>
<default>false</default>
<label>Active</label>
</value>
<value>
<fullName>Inactive</fullName>
<default>false</default>
<label>Inactive</label>
</value>
</valueSetDefinition>
</valueSet>
</CustomField>`;
5 changes: 5 additions & 0 deletions src/core/markdown/adapters/type-to-renderable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ function fieldMetadataToRenderable(
description: field.description ? [field.description] : [],
apiName: getApiName(field.name, config),
fieldType: field.type,
pickListValues: field.pickListValues ? {
headingLevel: headingLevel + 1,
heading: 'Possible values are',
value: field.pickListValues,
} : undefined,
};
}

Expand Down
7 changes: 7 additions & 0 deletions src/core/markdown/templates/custom-object-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ export const customObjectTemplate = `
**Type**

*{{fieldType}}*

{{#if pickListValues}}
{{ heading pickListValues.headingLevel pickListValues.heading }}
{{#each pickListValues.value}}
* {{{this}}}
{{/each}}
{{/if}}
{{/if}}

{{#unless @last}}---{{/unless}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,52 @@ describe('when parsing custom field metadata', () => {
assertEither(result, (data) => expect(data[0].type.description).toBe('A Photo URL field'));
});

test('can parse picklist values', async() => {
const unparsed: UnparsedCustomFieldBundle = {
type: 'customfield',
name: 'Status__c',
parentName: 'MyFirstObject__c',
filePath: 'src/field/Status__c.field-meta.xml',
content: `
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Status__c</fullName>
<externalId>false</externalId>
<label>Status</label>
<required>true</required>
<trackFeedHistory>false</trackFeedHistory>
<description>Status</description>
<type>Picklist</type>
<valueSet>
<restricted>true</restricted>
<valueSetDefinition>
<sorted>false</sorted>
<value>
<fullName>Staging</fullName>
<default>false</default>
<label>Staging</label>
</value>
<value>
<fullName>Active</fullName>
<default>false</default>
<label>Active</label>
</value>
<value>
<fullName>Inactive</fullName>
<default>false</default>
<label>Inactive</label>
</value>
</valueSetDefinition>
</valueSet>
</CustomField>`,
};

const result = await reflectCustomFieldSources([unparsed])();

assertEither(result, (data) => expect(data[0].type.description).toBe('Status'));
assertEither(result, (data) => expect(data[0].type.pickListValues).toEqual(['Staging', 'Active', 'Inactive']));
});

test('An error is returned when the XML is in an invalid format', async () => {
const unparsed: UnparsedCustomFieldBundle = {
type: 'customfield',
Expand Down
27 changes: 25 additions & 2 deletions src/core/reflection/sobject/reflect-custom-field-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type CustomFieldMetadata = {
label?: string | null;
type?: string | null;
parentName: string;
pickListValues?: string[];
};

export function reflectCustomFieldSources(
Expand Down Expand Up @@ -58,12 +59,34 @@ function validate(parsedResult: unknown): E.Either<Error, { CustomField: unknown
}

function toCustomFieldMetadata(parserResult: { CustomField: unknown }): CustomFieldMetadata {
const customField = typeof parserResult.CustomField === 'object' ? parserResult.CustomField : {};
const customField =
parserResult?.CustomField != null && typeof parserResult.CustomField === 'object' ? parserResult.CustomField : {};
const defaultValues = {
description: null,
};

return { ...defaultValues, ...customField, type_name: 'customfield' } as CustomFieldMetadata;
const pickListValues =
hasType(customField) && customField.type?.toLowerCase() === 'picklist' ? toPickListValues(customField) : undefined;
return { ...defaultValues, ...customField, type_name: 'customfield', pickListValues } as CustomFieldMetadata;
}

function toPickListValues(customField: object): string[] {
if ('valueSet' in customField) {
const valueSet = customField.valueSet as object;
if ('valueSetDefinition' in valueSet) {
const valueSetDefinition = valueSet.valueSetDefinition as object;
if ('value' in valueSetDefinition) {
const pickListValues = valueSetDefinition.value as object[];
return pickListValues.filter((each) => 'fullName' in each).map((each) => each.fullName as string);
}
}
}

return [];
}

function hasType(customField: object): customField is CustomFieldMetadata {
return !!(customField as CustomFieldMetadata).type;
}

function addName(metadata: CustomFieldMetadata, name: string): CustomFieldMetadata {
Expand Down
1 change: 1 addition & 0 deletions src/core/renderables/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export type RenderableCustomField = {
heading: string;
apiName: string;
description: RenderableContent[];
pickListValues?: RenderableSection<string[]>
type: 'field';
fieldType?: string | null;
};
Expand Down
Loading