Skip to content

Commit

Permalink
Merge branch 'main' into regex-plugin
Browse files Browse the repository at this point in the history
Signed-off-by: Manushak Keramyan <[email protected]>
  • Loading branch information
manushak authored Mar 6, 2024
2 parents 0a8c5ed + 082fdfe commit 878c77f
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 12 deletions.
21 changes: 9 additions & 12 deletions .github/ISSUE_TEMPLATE/dev-ticket.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
---
name: Dev-ticket
about: General developer ticket
title: "Dev ticket - "
labels: dev-ticket
about: General developer ticket
title: ''
labels: ''
assignees: ''

---

<!--- Provide a general summary of the issue in the Title above -->

## Story
<!--- Describe the benefits of this change, ideally in the following format: -->
<!--- As a < type of user >, I want < some goal > so that < some reason >. -->

## Rationale
<!--- Provide the relevant background information to udnerstand why this change is needed -->
<!--- This is where you can provide more details thathelp the reader understand why you want to make this change -->
Expand All @@ -34,11 +31,11 @@ assignees: ''
## What does "done" look like?
<!--- Explain what needs to happen in order for this to be considered complete -->

## Does this require updaes to documentation or other materials??
## Does this require updates to documentation or other materials??
<!--- Are there documentation, website or other media changes required after this change is implemented?. -->

## What testing is required?
<!--- Describe what tests are required to ensure this change has been delivered as expected -->
## Deadline
<!--- Does this need to happen by a specific date? -->

## Is this a known/expected update?
<!--- Has this been discussed with the core team or in a discussion forum? Provide links if possible. -->
## Acceptance criteria
<!--- What has to be true for this to get merged? What threshold of testing is acceptable? -->
131 changes: 131 additions & 0 deletions src/__tests__/unit/lib/divide/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import {Divide} from '../../../../lib';

import {ERRORS} from '../../../../util/errors';

const {InputValidationError} = ERRORS;

describe('lib/divide: ', () => {
describe('Divide: ', () => {
const globalConfig = {
numerator: 'vcpus-allocated',
denominator: 2,
output: 'cpu/number-cores',
};
const divide = Divide(globalConfig);

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

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

const expectedResult = [
{
duration: 3600,
'vcpus-allocated': 24,
'cpu/number-cores': 12,
timestamp: '2021-01-01T00:00:00Z',
},
];

const result = await divide.execute([
{
duration: 3600,
'vcpus-allocated': 24,
timestamp: '2021-01-01T00:00:00Z',
},
]);

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

it('returns a result when `denominator` is provded in input.', async () => {
expect.assertions(1);
const globalConfig = {
numerator: 'vcpus-allocated',
denominator: 'duration',
output: 'vcpus-allocated-per-second',
};
const divide = Divide(globalConfig);

const input = [
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
'vcpus-allocated': 24,
},
];
const response = await divide.execute(input);

const expectedResult = [
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
'vcpus-allocated': 24,
'vcpus-allocated-per-second': 24 / 3600,
},
];

expect(response).toEqual(expectedResult);
});

it('throws an error on missing params in input.', async () => {
const expectedMessage =
'"vcpus-allocated" parameter is required. Error code: invalid_type.';

const globalConfig = {
numerator: 'vcpus-allocated',
denominator: 3600,
output: 'vcpus-allocated-per-second',
};
const divide = Divide(globalConfig);

expect.assertions(1);

try {
await divide.execute([
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
},
]);
} catch (error) {
expect(error).toStrictEqual(
new InputValidationError(expectedMessage)
);
}
});
});

it('throws an error when `denominator` is 0.', async () => {
const expectedMessage =
'"denominator" parameter is number must be greater than 0. Error code: too_small.';

const globalConfig = {
numerator: 'vcpus-allocated',
denominator: 0,
output: 'vcpus-allocated-per-second',
};
const divide = Divide(globalConfig);

expect.assertions(1);

try {
await divide.execute([
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
'vcpus-allocated': 24,
},
]);
} catch (error) {
expect(error).toStrictEqual(new InputValidationError(expectedMessage));
}
});
});
});
96 changes: 96 additions & 0 deletions src/lib/divide/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Divide

`divide` is a generic plugin for doing arithmetic division of two values in an `input` array.

You provide the names of the values you want to divide, and a name to use to add the divide to the output array.

For example, `boavizta-cpu` need `cpu/number-cores` to work, however `cloud-metadata` returns `vcpus-allocated`, to get number of cores you divide `vcpus-allocated` by 2.

## Parameters

### Plugin config

- `numerator` - a parameter by a specific configured number
- `denominator` - a parameter by a specific configured number or the number by which `numerator` is divided
- `output` - the number to a configured output parameter

### Inputs

- `numerator` - as input parameter, must be available in the input array
- `denominator` - must be available in the input array if is an input parameter
- `output` - as input parameter, must be available in the input array

## Returns

- `output`: the division of `numerator` with the parameter name into `denominator` with the parameter name defined by `output` in global config.

The plugin throws an exception if the division result is not a number.

## Calculation

```pseudocode
output = input0 / input1
```

## Implementation

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

```typescript
import {Divide} from '@grnsft/if-plugins';

const globalConfig = {
numerator: 'vcpus-allocated',
denominator: 2,
output: 'cpu/number-cores',
};
const divide = Divide(globalConfig);

const input = [
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
'vcpus-allocated': 24,
},
];
```

## 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 `if` and does not have to be done explicitly by the user. The following is an example manifest that calls `divide`:

```yaml
name: divide-demo
description:
tags:
initialize:
plugins:
divide:
method: Divide
path: '@grnsft/if-plugins'
global-config:
numerator: vcpus-allocated
denominator: 2
output: cpu/number-cores
tree:
children:
child:
pipeline:
- divide
config:
divide:
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600
vcpus-allocated: 24
```
You can run this example by saving it as `./examples/manifests/test/divide.yml` and executing the following command from the project root:

```sh
npm i -g @grnsft/if
npm i -g @grnsft/if-plugins
if --manifest ./examples/manifests/test/divide.yml --output ./examples/outputs/divide.yml
```

The results will be saved to a new `yaml` file in `./examples/outputs`.
97 changes: 97 additions & 0 deletions src/lib/divide/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {z} from 'zod';

import {buildErrorMessage} from '../../util/helpers';
import {ERRORS} from '../../util/errors';
import {validate} from '../../util/validations';

import {PluginInterface} from '../../interfaces';
import {ConfigParams, PluginParams} from '../../types/common';

const {InputValidationError, ConfigValidationError} = ERRORS;

export const Divide = (globalConfig: ConfigParams): PluginInterface => {
const errorBuilder = buildErrorMessage(Divide.name);
const metadata = {
kind: 'execute',
};

/**
* Checks global config value are valid.
*/
const validateGlobalConfig = () => {
if (!globalConfig) {
throw new ConfigValidationError(
errorBuilder({message: 'Configuration data is missing'})
);
}
const schema = z.object({
numerator: z.string().min(1),
denominator: z.string().or(z.number().gt(0)),
output: z.string(),
});

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

/**
* Checks for required fields in input.
*/
const validateSingleInput = (
input: PluginParams,
numerator: string,
denominator: number | string
) => {
const schema = z
.object({
[numerator]: z.number(),
[denominator]: z.number().optional(),
})
.refine(_data => {
if (typeof denominator === 'string' && !input[denominator]) {
throw new InputValidationError(
errorBuilder({
message: `\`${denominator}\` is missing from the input`,
})
);
}
return true;
});

return validate<z.infer<typeof schema>>(schema, input);
};

/**
* Calculate the division of each input parameter.
*/
const execute = async (inputs: PluginParams[]) => {
const safeGlobalConfig = validateGlobalConfig();
const {numerator, denominator, output} = safeGlobalConfig;

return inputs.map(input => {
const safeInput = Object.assign(
{},
input,
validateSingleInput(input, numerator, denominator)
);

return {
...input,
[output]: calculateDivide(safeInput, numerator, denominator),
};
});
};

/**
* Calculates the division of the given parameter.
*/
const calculateDivide = (
input: PluginParams,
numerator: string,
denominator: number | string
) => input[numerator] / (input[denominator] || denominator);

return {
metadata,
execute,
};
};
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export {Multiply} from './multiply';
export {Sum} from './sum';
export {Coefficient} from './coefficient';
export {Regex} from './regex';
export {Divide} from './divide';

0 comments on commit 878c77f

Please sign in to comment.