Skip to content

Commit

Permalink
feat(template): add lookupArray and distinct helpers (#30618)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
  • Loading branch information
amezin and viceice authored Aug 19, 2024
1 parent dc43ad4 commit f753015
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 1 deletion.
22 changes: 22 additions & 0 deletions docs/usage/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ In the example above `depName` is the string you want to decode.

Read the [MDN Web Docs, decodeURIComponent()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent) to learn more.

### distinct

Removes duplicate elements from an array.

`{{#each (distinct (lookupArray (lookupArray upgrades "prBodyDefinitions") "Issue"))}} {{{.}}}{{/each}}`

### encodeBase64

If you want to convert a string to Base64, use the built-in function `encodeBase64` like this:
Expand All @@ -79,6 +85,22 @@ Returns `true` if two values equals (checks strict equality, i.e. `===`).

`{{#if (equals datasource 'git-refs')}}git-refs{{else}}Other{{/if}}`

### lookupArray

Similar to the built-in [`lookup`](https://handlebarsjs.com/guide/builtin-helpers.html#lookup)
helper, but performs lookups in every element of an array, instead of just one object.

For example:

`{{#each (lookupArray upgrades "prBodyDefinitions")}} {{{Issue}}}{{/each}}`

will produce the same output as:

`{{#each upgrades}}{{#with prBodyDefinitions}} {{{Issue}}}{{/with}}{{/each}}`.

The return value of `lookupArray` can be passed to other helpers - for example,
to `distinct`.

### lowercase

The `lowercase` helper converts a given string to lower case.
Expand Down
124 changes: 124 additions & 0 deletions lib/util/template/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,4 +423,128 @@ describe('util/template/index', () => {
expect(output).toBe('bar');
});
});

describe('lookupArray', () => {
it('performs lookup for every array element', () => {
const output = template.compile(
'{{#each (lookupArray upgrades "prBodyDefinitions")}} {{{Issue}}}{{/each}}',
{
upgrades: [
{
prBodyDefinitions: {
Issue: 'ABC-123',
},
},
{},
{
prBodyDefinitions: {
Issue: 'DEF-456',
},
},
null,
undefined,
],
},
);

expect(output).toBe(' ABC-123 DEF-456');
});

it('handles null input array', () => {
const output = template.compile(
'{{#each (lookupArray testArray "prBodyDefinitions")}} {{{Issue}}}{{/each}}',
{
testArray: null,
},
false,
);

expect(output).toBe('');
});

it('handles empty string key', () => {
const output = template.compile(
'{{#each (lookupArray testArray "")}} {{{.}}}{{/each}}',
{
testArray: [
{
'': 'ABC-123',
},
],
},
false,
);

expect(output).toBe(' ABC-123');
});

it('handles null key', () => {
const output = template.compile(
'{{#each (lookupArray testArray null)}} {{{.}}}{{/each}}',
{
testArray: [
{
null: 'ABC-123',
},
],
},
false,
);

expect(output).toBe(' ABC-123');
});
});

describe('distinct', () => {
it('skips duplicate values', () => {
const output = template.compile(
'{{#each (distinct (lookupArray (lookupArray upgrades "prBodyDefinitions") "Issue"))}} {{{.}}}{{/each}}',
{
upgrades: [
{
prBodyDefinitions: {
Issue: 'ABC-123',
},
},
{
prBodyDefinitions: {
Issue: 'DEF-456',
},
},
{
prBodyDefinitions: {
Issue: 'ABC-123',
},
},
],
},
);

expect(output).toBe(' ABC-123 DEF-456');
});

it('handles null elements', () => {
const output = template.compile(
'{{#each (distinct input)}}{{{.}}}{{/each}}',
{
input: [null, null],
},
false,
);

expect(output).toBe('');
});

it('handles null input', () => {
const output = template.compile(
'{{#each (distinct input)}}{{{.}}}{{/each}}',
{
input: null,
},
false,
);

expect(output).toBe('');
});
});
});
36 changes: 35 additions & 1 deletion lib/util/template/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import is from '@sindresorhus/is';
import handlebars from 'handlebars';
import handlebars, { type HelperOptions } from 'handlebars';
import { GlobalConfig } from '../../config/global';
import { logger } from '../../logger';
import { toArray } from '../array';
import { getChildEnv } from '../exec/utils';
import { regEx } from '../regex';

// Missing in handlebars
type Options = HelperOptions & {
lookupProperty: (element: unknown, key: unknown) => unknown;
};

handlebars.registerHelper('encodeURIComponent', encodeURIComponent);
handlebars.registerHelper('decodeURIComponent', decodeURIComponent);

Expand Down Expand Up @@ -87,6 +93,34 @@ handlebars.registerHelper({
},
});

handlebars.registerHelper(
'lookupArray',
(obj: unknown, key: unknown, options: Options): unknown[] => {
return (
toArray(obj)
// skip elements like #with does
.filter((element) => !handlebars.Utils.isEmpty(element))
.map((element) => options.lookupProperty(element, key))
.filter((value) => value !== undefined)
);
},
);

handlebars.registerHelper('distinct', (obj: unknown): unknown[] => {
const seen = new Set<string>();

return toArray(obj).filter((value) => {
const str = JSON.stringify(value);

if (seen.has(str)) {
return false;
}

seen.add(str);
return true;
});
});

export const exposedConfigOptions = [
'additionalBranchPrefix',
'addLabels',
Expand Down

0 comments on commit f753015

Please sign in to comment.