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

Add copy-param to builtins #854

Merged
merged 11 commits into from
Jun 27, 2024
20 changes: 20 additions & 0 deletions manifests/examples/copy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: copy-param
description:
tags:
initialize:
plugins:
copy-param:
path: builtin
method: Copy
global-config:
keep-existing: true
from: original
to: copy
tree:
children:
child-1:
pipeline:
- copy-param
inputs:
- timestamp: "2023-12-12T00:00:00.000Z"
original: 'hello'
124 changes: 124 additions & 0 deletions src/__tests__/unit/builtins/copy-param.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {ERRORS} from '@grnsft/if-core/utils';

import {Copy} from '../../../builtins/copy-param';

import {STRINGS} from '../../../config';

const {GlobalConfigError, InputValidationError} = ERRORS;
const {MISSING_GLOBAL_CONFIG} = STRINGS;

describe('builtins/copy: ', () => {
describe('Copy: ', () => {
const globalConfig = {
'keep-existing': true,
from: 'original',
to: 'copy',
};
const copy = Copy(globalConfig);

describe('init: ', () => {
it('successfully initalized.', () => {
expect(copy).toHaveProperty('metadata');
expect(copy).toHaveProperty('execute');
});
});

describe('execute(): ', () => {
it('successfully applies Copy strategy to given input.', () => {
expect.assertions(1);

const expectedResult = [
{
duration: 3600,
original: 'hello',
copy: 'hello',
timestamp: '2021-01-01T00:00:00Z',
},
];

const result = copy.execute([
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
original: 'hello',
},
]);

expect(result).toStrictEqual(expectedResult);
});

it('throws an error when global config is not provided.', () => {
const config = undefined;
const copy = Copy(config!);

expect.assertions(1);

try {
copy.execute([
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
original: 1,
},
]);
} catch (error) {
expect(error).toStrictEqual(
new GlobalConfigError(MISSING_GLOBAL_CONFIG)
);
}
});

it('throws an error on missing params in input.', () => {
const globalConfig = {
'keep-existing': true,
from: 'original',
to: 'copy',
};
const copy = Copy(globalConfig);
expect.assertions(1);

try {
copy.execute([
{
duration: 3600,
timestamp: '2021-01-01T00:00:00Z',
},
]);
} catch (error) {
expect(error).toStrictEqual(
new InputValidationError(
'"original" parameter is required. Error code: invalid_type.'
)
);
}
});
it('does not persist the original value when keep-existing==false.', () => {
expect.assertions(1);
const globalConfig = {
'keep-existing': false,
from: 'original',
to: 'copy',
};
const copy = Copy(globalConfig);

const expectedResult = [
{
duration: 3600,
copy: 'hello',
timestamp: '2021-01-01T00:00:00Z',
},
];

const result = copy.execute([
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
original: 'hello',
},
]);

expect(result).toStrictEqual(expectedResult);
});
});
});
});
104 changes: 104 additions & 0 deletions src/builtins/copy-param/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Copy-param

`copy-param` is a generic plugin that duplicates an existing parameter in the `input` data and assigns it to a new key. You can either keep or delete the original copied parameter. A common use case for this is to rename parameters in the `inputs` array.

You provide the name of the value you want to copy, and a name to assign the copy to. You also toggle a `keep-existing` parameter to either persist or del;et the original copied value.

For example, you could copy `energy` into `energy-copy`, with `keep-existing=true`. In this case your inputs:

```yaml
- timestamp: "2023-12-12T00:00:13.000Z",
duration: 30,
energy: 30
```

would become

```yaml
- timestamp: "2023-12-12T00:00:13.000Z",
duration: 30,
energy: 30
energy-copy: 30
```

but with `keep-existing=false`, the same inputs would yield:

```yaml
- timestamp: "2023-12-12T00:00:13.000Z",
duration: 30,
energy-copy: 30
```

## Parameters

### Config

Three parameters are required in config: `from` and `to` and `keep-existing`.

`from`: an array of strings. Each string should match an existing key in the `inputs` array
`to`: a string defining the name to use to add the result of summing the input parameters to the output array.
`keep-existing`: toggles whether to keep or delete the copied parameter (defined in `to`)

### Inputs

As with all plugins, `timestamp` and `duration` are required. The key passed to `from` must exist in the `input` data.

## Returns

The plugin adds a new parameter with the name defined in `to` to the `input` data.


## Implementation

To run the plugin, you must first create an instance of `Copy`. Then, you can call `execute()`.

```typescript
import { Copy } from ".";

const plugin = Copy({ 'keep-existing': true, from: 'from-param', to: 'to-param' });

const result = plugin.execute([{
timestamp: "2023-12-12T00:00:13.000Z",
duration: 30,
'from-param': 'hello',
}])

console.log(result)

```

## Example manifest

IF users will typically call the plugin as part of a pipeline defined in a manifest file. In this case, instantiating the plugin is handled by and does not have to be done explicitly by the user. The following is an example manifest that calls `sum`:

```yaml
name: copy-param
description:
tags:
initialize:
plugins:
copy-param:
path: builtin
method: Copy
global-config:
keep-existing: true
from: original
to: copy
tree:
children:
child-1:
pipeline:
- copy-param
inputs:
- timestamp: "2023-12-12T00:00:00.000Z"
original: 'hello'

```

You can run this example by saving it as `./manifests/examples/copy.yml` and executing the following command from the project root:

```sh
if-run --manifest ./manifests/examples/copy.yml -s
```

The results will be displayed in the console.
89 changes: 89 additions & 0 deletions src/builtins/copy-param/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {z} from 'zod';
import {ERRORS} from '@grnsft/if-core/utils';
import {ExecutePlugin, PluginParams} from '@grnsft/if-core/types';

import {validate} from '../../util/validations';

import {STRINGS} from '../../config';

const {MISSING_GLOBAL_CONFIG} = STRINGS;
const {GlobalConfigError} = ERRORS;
// keep-existing: true/false (whether to remove the parameter you are copying from)
// from-param: the parameter you are copying from (e.g. cpu/name)
// to-field: the parameter you are copying to (e.g. cpu/processor-name)

export const Copy = (globalConfig: Record<string, any>): ExecutePlugin => {
const metadata = {
kind: 'execute',
};

/**
* Checks global config value are valid.
*/
const validateGlobalConfig = () => {
if (!globalConfig) {
throw new GlobalConfigError(MISSING_GLOBAL_CONFIG);
}

const globalConfigSchema = z.object({
'keep-existing': z.boolean(),
from: z.string().min(1),
to: z.string().min(1),
});

return validate<z.infer<typeof globalConfigSchema>>(
globalConfigSchema,
globalConfig
);
};

/**
* Checks for required fields in input.
*/
const validateSingleInput = (
input: PluginParams,
inputParameters: string[]
) => {
const inputData = inputParameters.reduce(
(acc, param) => {
acc[param] = input[param];

return acc;
},
{} as Record<string, string>
);

const validationSchema = z.record(z.string(), z.string());

validate(validationSchema, inputData);

return input;
};

const execute = (inputs: PluginParams[]) => {
const safeGlobalConfig = validateGlobalConfig();
const keepExisting = safeGlobalConfig['keep-existing'] === true;
const from = safeGlobalConfig['from'];
const to = safeGlobalConfig['to'];

return inputs.map(input => {
const safeInput = validateSingleInput(input, [from]);

const outputValue = safeInput[from];
if (safeInput[from]) {
if (!keepExisting) {
delete safeInput[from];
}
}
return {
...safeInput, // need to return or what you provide won't be outputted, don't be evil!
[to]: outputValue,
};
});
};

return {
metadata,
execute,
};
};
1 change: 1 addition & 0 deletions src/builtins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export {Exponent} from './exponent';
export {CSVLookup} from './csv-lookup';
export {Shell} from './shell';
export {Regex} from './regex';
export {Copy} from './copy-param';