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

Documenting extension fields #210

Merged
merged 5 commits into from
Nov 11, 2024
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
4 changes: 4 additions & 0 deletions examples/vitepress/docs/.vitepress/sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
{
"text": "Object Reference",
"items": [
{
"text": "Contact",
"link": "custom-objects/Contact.md"
},
{
"text": "Event__c",
"link": "custom-objects/Event__c.md"
Expand Down
10 changes: 9 additions & 1 deletion examples/vitepress/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,12 @@ Represents a line item on a sales order.
Custom object for tracking sales orders.
### Speaker__c

Represents a speaker at an event.
Represents a speaker at an event.

## New or Removed Fields to Custom Objects or Standard Objects

These custom fields have been added or removed.

### Contact

- New Field: PhotoUrl__c
19 changes: 19 additions & 0 deletions examples/vitepress/docs/custom-objects/Contact.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Contact
---

# Contact

## API Name
`apexdocs__Contact`

## Fields
### PhotoUrl

**API Name**

`apexdocs__PhotoUrl__c`

**Type**

*Url*
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,93 @@ The date the product got discontinued

**Type**

*DateTime*
*DateTime*

---
### ID

**API Name**

`apexdocs__ID__c`

**Type**

*Number*

---
### Name

Product name

**API Name**

`apexdocs__Name__c`

**Type**

*Text*

---
### Price

Product price in the default currency

**API Name**

`apexdocs__Price__c`

**Type**

*Number*

---
### Products

**API Name**

`apexdocs__Products__c`

**Type**

*ExternalLookup*

---
### Rating

Rating

**API Name**

`apexdocs__Rating__c`

**Type**

*Number*

---
### ReleaseDate

ReleaseDate

**API Name**

`apexdocs__ReleaseDate__c`

**Type**

*DateTime*

---
### Type

**API Name**

`apexdocs__Type__c`

**Type**

*Picklist*

#### Possible values are
* Merchandise
* Bundle
2 changes: 2 additions & 0 deletions examples/vitepress/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ hero:

## Custom Objects

### [Contact](custom-objects/Contact)

### [Event__c](custom-objects/Event__c)

Represents an event that people can register for.
Expand Down
75 changes: 74 additions & 1 deletion src/core/changelog/__test__/generating-change-log.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { assertEither } from '../../test-helpers/assert-either';
import { isSkip } from '../../shared/utils';
import { unparsedFieldBundleFromRawString } from '../../test-helpers/test-data-builders';
import { CustomObjectXmlBuilder } from '../../test-helpers/test-data-builders/custom-object-xml-builder';
import { CustomFieldXmlBuilder } from '../../test-helpers/test-data-builders/custom-field-xml-builder';

const config = {
fileName: 'changelog',
Expand Down Expand Up @@ -347,7 +348,9 @@ describe('when generating a changelog', () => {
const result = await generateChangeLog(oldBundle, newBundle, config)();

assertEither(result, (data) =>
expect((data as ChangeLogPageData).content).toContain('## New or Removed Fields in Existing Objects'),
expect((data as ChangeLogPageData).content).toContain(
'## New or Removed Fields to Custom Objects or Standard Objects',
),
);
});

Expand Down Expand Up @@ -393,4 +396,74 @@ describe('when generating a changelog', () => {
);
});
});

describe('that includes extension fields', () => {
it('does not include the fields when they are in both versions', async () => {
const fieldSource = new CustomFieldXmlBuilder().build();

const oldBundle: UnparsedSourceBundle[] = [
{
type: 'customfield',
name: 'PhotoUrl__c',
content: fieldSource,
filePath: 'PhotoUrl__c.field-meta.xml',
parentName: 'MyTestObject',
},
];
const newBundle: UnparsedSourceBundle[] = [
{
type: 'customfield',
name: 'PhotoUrl__c',
content: fieldSource,
filePath: 'PhotoUrl__c.field-meta.xml',
parentName: 'MyTestObject',
},
];

const result = await generateChangeLog(oldBundle, newBundle, config)();

assertEither(result, (data) => expect((data as ChangeLogPageData).content).not.toContain('MyTestObject'));
assertEither(result, (data) => expect((data as ChangeLogPageData).content).not.toContain('PhotoUrl__c'));
});

it('includes added fields when they are not in the old version', async () => {
const fieldSource = new CustomFieldXmlBuilder().build();

const oldBundle: UnparsedSourceBundle[] = [];
const newBundle: UnparsedSourceBundle[] = [
{
type: 'customfield',
name: 'PhotoUrl__c',
content: fieldSource,
filePath: 'PhotoUrl__c.field-meta.xml',
parentName: 'MyTestObject',
},
];

const result = await generateChangeLog(oldBundle, newBundle, config)();

assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('MyTestObject'));
assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('PhotoUrl__c'));
});

it('includes removed fields when they are not in the new version', async () => {
const fieldSource = new CustomFieldXmlBuilder().build();

const oldBundle: UnparsedSourceBundle[] = [
{
type: 'customfield',
name: 'PhotoUrl__c',
content: fieldSource,
filePath: 'PhotoUrl__c.field-meta.xml',
parentName: 'MyTestObject',
},
];
const newBundle: UnparsedSourceBundle[] = [];

const result = await generateChangeLog(oldBundle, newBundle, config)();

assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('MyTestObject'));
assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('PhotoUrl__c'));
});
});
});
75 changes: 69 additions & 6 deletions src/core/changelog/__test__/processing-changelog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@ function apexTypeFromRawString(raw: string): Type {
}

class CustomFieldMetadataBuilder {
name: string = 'MyField';

withName(name: string): CustomFieldMetadataBuilder {
this.name = name;
return this;
}

build(): CustomFieldMetadata {
return {
type: 'Text',
type_name: 'customfield',
label: 'MyField',
name: 'MyField',
name: this.name,
description: null,
parentName: 'MyObject',
};
Expand All @@ -29,11 +36,6 @@ class CustomObjectMetadataBuilder {
label: string = 'MyObject';
fields: CustomFieldMetadata[] = [];

withLabel(label: string): CustomObjectMetadataBuilder {
this.label = label;
return this;
}

withField(field: CustomFieldMetadata): CustomObjectMetadataBuilder {
this.fields.push(field);
return this;
Expand Down Expand Up @@ -570,4 +572,65 @@ describe('when generating a changelog', () => {
]);
});
});

describe('with custom field code', () => {
it('does not list fields that are the same in both versions', () => {
// A field that is the same in both versions is defined as one
// with both the same name and the same parent

const oldField = new CustomFieldMetadataBuilder().build();
const newField = new CustomFieldMetadataBuilder().build();

const oldManifest = { types: [oldField] };
const newManifest = { types: [newField] };

const changeLog = processChangelog(oldManifest, newManifest);

expect(changeLog.customObjectModifications).toEqual([]);
});

it('lists new fields of a custom object', () => {
const oldField = new CustomFieldMetadataBuilder().build();
const newField = new CustomFieldMetadataBuilder().withName('NewField').build();

const oldManifest = { types: [oldField] };
const newManifest = { types: [oldField, newField] };

const changeLog = processChangelog(oldManifest, newManifest);

expect(changeLog.customObjectModifications).toEqual([
{
typeName: newField.parentName,
modifications: [
{
__typename: 'NewField',
name: newField.name,
},
],
},
]);
});

it('lists removed fields of a custom object', () => {
const oldField = new CustomFieldMetadataBuilder().withName('OldField').build();
const unchangedField = new CustomFieldMetadataBuilder().build();

const oldManifest = { types: [unchangedField, oldField] };
const newManifest = { types: [unchangedField] };

const changeLog = processChangelog(oldManifest, newManifest);

expect(changeLog.customObjectModifications).toEqual([
{
typeName: oldField.parentName,
modifications: [
{
__typename: 'RemovedField',
name: oldField.name,
},
],
},
]);
});
});
});
25 changes: 15 additions & 10 deletions src/core/changelog/generate-change-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ import { changelogTemplate } from './templates/changelog-template';
import { ReflectionErrors } from '../errors/errors';
import { apply } from '#utils/fp';
import { filterScope } from '../reflection/apex/filter-scope';
import { skip } from '../shared/utils';
import { isInSource, skip } from '../shared/utils';
import { reflectCustomFieldsAndObjects } from '../reflection/sobject/reflectCustomFieldsAndObjects';
import { CustomObjectMetadata } from '../reflection/sobject/reflect-custom-object-sources';
import { Type } from '@cparra/apex-reflection';
import { filterApexSourceFiles, filterCustomObjectsAndFields } from '#utils/source-bundle-utils';
import { CustomFieldMetadata } from '../reflection/sobject/reflect-custom-field-source';

export type ChangeLogPageData = {
content: string;
Expand Down Expand Up @@ -71,16 +72,20 @@ function reflect(bundles: UnparsedSourceBundle[], config: Omit<UserDefinedChange
);
}

function toManifests({
oldVersion,
newVersion,
}: {
oldVersion: ParsedFile<Type | CustomObjectMetadata>[];
newVersion: ParsedFile<Type | CustomObjectMetadata>[];
}) {
function parsedFilesToManifest(parsedFiles: ParsedFile<Type | CustomObjectMetadata>[]): VersionManifest {
function toManifests({ oldVersion, newVersion }: { oldVersion: ParsedFile[]; newVersion: ParsedFile[] }) {
function parsedFilesToManifest(parsedFiles: ParsedFile[]): VersionManifest {
return {
types: parsedFiles.map((parsedFile) => parsedFile.type),
types: parsedFiles.reduce(
(previousValue: (Type | CustomObjectMetadata | CustomFieldMetadata)[], parsedFile: ParsedFile) => {
if (!isInSource(parsedFile.source) && parsedFile.type.type_name === 'customobject') {
// When we are dealing with a custom object that was not in the source (for extension fields), we return all
// of its fields.
return [...previousValue, ...parsedFile.type.fields];
}
return [...previousValue, parsedFile.type];
},
[] as (Type | CustomObjectMetadata | CustomFieldMetadata)[],
),
};
}

Expand Down
Loading
Loading