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

fix(core): Fix data transformation function that are reported not to work properly #5338

Merged
merged 171 commits into from
Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
171 commits
Select commit Hold shift + click to select a range
22bd573
:fire: Remove test extensions
ivov Jan 11, 2023
53f2226
:construction: Add test description
ivov Jan 11, 2023
6a8d709
:blue_book: Expand types
ivov Jan 11, 2023
0edac3b
:zap: Export extensions
ivov Jan 11, 2023
8bb5526
:zap: Export collection
ivov Jan 11, 2023
a925ece
:zap: Mark all proxies
ivov Jan 11, 2023
fe5acdc
:pencil2: Rename for clarity
ivov Jan 11, 2023
ce112cd
:zap: Export from barrel
ivov Jan 11, 2023
fccc9a3
:sparkles: Create datatype completions
ivov Jan 11, 2023
05cce74
:zap: Mount datatype completions
ivov Jan 11, 2023
9d9f610
:test_tube: Adjust tests
ivov Jan 12, 2023
7e4ab66
:twisted_rightwards_arrows: Merge master
ivov Jan 12, 2023
11dd7e2
:zap: Add `path` prop
ivov Jan 12, 2023
9146360
:fire: Remove `()` from completion labels
ivov Jan 12, 2023
09229b5
:zap: Filter out completions for pseudo-proxies
ivov Jan 12, 2023
bc4070c
:bug: Fix method error
ivov Jan 12, 2023
2d9280a
:zap: Add metrics
ivov Jan 12, 2023
269e601
:pencil2: Improve naming
ivov Jan 12, 2023
7f57c8c
:sparkles: Start completion on empty resolvable
ivov Jan 12, 2023
2ee6750
:sparkles: Implement completion previews
ivov Jan 13, 2023
4d329ec
:zap: Break out completion manager
ivov Jan 13, 2023
15e54b6
:zap: Implement in expression editor modal
ivov Jan 13, 2023
9a230bd
:pencil2: Improve naming
ivov Jan 13, 2023
01e064c
:zap: Filter out irrelevant completions
ivov Jan 13, 2023
99e8498
:sparkles: Add preview hint
ivov Jan 14, 2023
3387291
:pencil2: Improve comments
ivov Jan 14, 2023
c827cfe
:art: Style preview hint
ivov Jan 14, 2023
ef48638
:zap: Expand `hasNoParams`
ivov Jan 14, 2023
3e738db
:zap: Add spacing for readability
ivov Jan 14, 2023
8727919
:zap: Add error codes
ivov Jan 14, 2023
d5bd769
:pencil2: Add comment
ivov Jan 14, 2023
73305f9
:bug: Fix Esc behavior
ivov Jan 14, 2023
892d62e
:zap: Parse Unicode
ivov Jan 14, 2023
33f968c
:zap: Throw on invalid `DateTime`
ivov Jan 14, 2023
53331f5
:zap: Fix second root completion detection
ivov Jan 14, 2023
97abc86
:zap: Switch message at completable prefix position
ivov Jan 17, 2023
fbbd0bc
:bug: Fix function names for non-dev build
ivov Jan 18, 2023
0f8f726
:bug: Fix `json` handling
ivov Jan 18, 2023
cba7f56
:fire: Comment out previews
ivov Jan 18, 2023
1abc05f
:recycle: Apply feedback
ivov Jan 18, 2023
d9075d8
:fire: Remove extensions
ivov Jan 19, 2023
6e2f54d
:truck: Rename extensions
ivov Jan 19, 2023
b9b966c
:zap: Adjust some implementations
ivov Jan 19, 2023
e4d17f5
:fire: Remove dummy extensions
ivov Jan 19, 2023
bfa34e6
:bug: Fix object regex
ivov Jan 19, 2023
ad30a1d
:recycle: Apply feedback
ivov Jan 19, 2023
f9a382e
:pencil2: Fix typos
ivov Jan 19, 2023
19d3887
:pencil2: Add `fn is not a function` message
ivov Jan 19, 2023
4ddf260
:fire: Remove check
ivov Jan 20, 2023
8264fcb
:sparkles: Add `isNotEmpty` for objects
ivov Jan 20, 2023
ebe2ef8
:truck: Rename `global` to `alpha`
ivov Jan 20, 2023
e4652e1
:fire: Remove `encrypt`
ivov Jan 20, 2023
ad9f228
:zap: Restore `is not a function` error
ivov Jan 20, 2023
54db360
:zap: Support `week` on `extract()`
ivov Jan 20, 2023
b0b5f18
:test_tube: Fix tests
ivov Jan 20, 2023
4f2823a
:zap: Add validation to some string extensions
ivov Jan 20, 2023
2c9a58f
:zap: Validate number arrays in some extensions
ivov Jan 20, 2023
4899775
:test_tube: Fix tests
ivov Jan 20, 2023
3f03b59
:twisted_rightwards_arrows: Merge master
ivov Jan 20, 2023
f1519b3
:pencil2: Improve error message
ivov Jan 20, 2023
18b185c
:rewind: Revert extensions framework changes
ivov Jan 20, 2023
5b4c56a
:twisted_rightwards_arrows: Merge master
ivov Jan 20, 2023
6fb6e35
:broom: Previews cleanup
ivov Jan 20, 2023
67f57a8
:zap: Condense blank completions
ivov Jan 20, 2023
45b6478
:zap: Refactor dollar completions
ivov Jan 20, 2023
8292b5f
:zap: Refactor non-dollar completions
ivov Jan 20, 2023
213735d
:zap: Refactor Luxon completions
ivov Jan 20, 2023
1100f74
:zap: Refactor datatype completions
ivov Jan 21, 2023
7bfb00f
:twisted_rightwards_arrows: Merge master
ivov Jan 23, 2023
472a77d
:zap: Use `DATETIMEUNIT_MAP`
ivov Jan 23, 2023
a3693d1
:pencil2: Update test description
ivov Jan 23, 2023
623961c
:rewind: Revert "Use `DATETIMEUNIT_MAP`"
ivov Jan 23, 2023
ff670c5
:test_tube: Add tests
ivov Jan 23, 2023
1a6d3bc
:recycle: Restore generic extensions
ivov Jan 24, 2023
d9fffaa
:twisted_rightwards_arrows: Merge master
ivov Jan 24, 2023
fcc5758
:fire: Remove logs
ivov Jan 24, 2023
0183438
:test_tube: Expand tests
ivov Jan 24, 2023
206f15a
:sparkles: Add `Math` completions
ivov Jan 24, 2023
34bc278
:pencil2: List breaking change
ivov Jan 24, 2023
55c33c4
:twisted_rightwards_arrows: Merge `expression-extensions-shortlist`
ivov Jan 24, 2023
999e132
:zap: Add doc tooltips
ivov Jan 24, 2023
d301f58
:bug: Fix node selector regex
ivov Jan 24, 2023
3c79a50
:bug: Fix `context` resolution
ivov Jan 24, 2023
f42e391
:bug: Allow dollar completions in args
ivov Jan 24, 2023
891e371
:zap: Make numeric array methods context-dependent
ivov Jan 24, 2023
152f0d7
:pencil: Adjust docs
ivov Jan 24, 2023
b1d3569
:bug: Fix selector ref
ivov Jan 25, 2023
0e67e5d
:zap: Surface error for valid URL
ivov Jan 25, 2023
b3742d1
:bug: Disallow whitespace in `isEmail` check
ivov Jan 25, 2023
a5c2a9b
:test_tube: Fix test for `isUrl`
ivov Jan 25, 2023
2113474
:zap: Add comma validator in `toFloat`
ivov Jan 25, 2023
b23fe61
:zap: Add validation to `$jmespath()`
ivov Jan 25, 2023
36adc8c
:rewind: Revert valid URL error
ivov Jan 25, 2023
525391d
:zap: Adjust `$jmespath()` validation
ivov Jan 25, 2023
62bf096
:test_tube: Adjust `isUrl` test
ivov Jan 25, 2023
b4147bb
:zap: Remove `{}` and `[]` from compact
ivov Jan 25, 2023
483bc68
:pencil2: Update docs
ivov Jan 25, 2023
59e5638
:truck: Rename `stripTags` to `removeTags`
ivov Jan 25, 2023
2634f02
:zap: Do not inject whitespace inside resolvable
ivov Jan 26, 2023
065f79d
:zap: Make completions aware of `()`
ivov Jan 26, 2023
cf2266b
:pencil2: Add note
ivov Jan 26, 2023
0826772
:zap: Update sorting
ivov Jan 26, 2023
21fe300
:zap: Hide active node name from node selector
ivov Jan 26, 2023
3670cc2
:fire: Remove `length()` and its aliases
ivov Jan 26, 2023
c26c83e
:zap: Validate non-zero for `chunk`
ivov Jan 26, 2023
3827b03
:pencil2: Reword all error messages
ivov Jan 26, 2023
00816a4
:bug: Fix `$now` and `$today`
ivov Jan 26, 2023
b8e7203
:zap: Simplify with `stripExcessParens`
ivov Jan 27, 2023
486d656
:zap: Fold luxon into datatype
ivov Jan 27, 2023
cffd642
:test_tube: Clean up tests
ivov Jan 30, 2023
7437de6
:twisted_rightwards_arrows: Merge master
ivov Jan 30, 2023
65f8680
:fire: Remove tests for removed methods
ivov Jan 30, 2023
1d2c947
:shirt: Fix type
ivov Jan 30, 2023
7c30811
:arrow_up: Upgrade lang pack
ivov Jan 30, 2023
19b480f
:rewind: Undo change to `vitest` command
ivov Jan 30, 2023
42be2f5
:fire: Remove unused method
ivov Jan 30, 2023
ed4a74b
:zap: Separate `return` line
ivov Jan 30, 2023
5f0d8a2
:pencil2: Improve description
ivov Jan 30, 2023
797c1b4
:test_tube: Expand tests for initial-only completions
ivov Jan 30, 2023
034d5e2
:test_tube: Add bracket-aware completions
ivov Jan 30, 2023
21eb8dc
:zap: Make check for `all()` stricter
ivov Jan 30, 2023
1e5b585
:pencil2: Adjust explanatory comments
ivov Jan 30, 2023
82f8317
:fire: Remove unneded copy
ivov Jan 30, 2023
a6797f7
:fire: Remove outdated comment
ivov Jan 30, 2023
ad8cf66
:zap: Make naming consistent
ivov Jan 30, 2023
19d9945
:pencil2: Update comments
ivov Jan 30, 2023
a49f6cc
:zap: Improve URL scheme check
ivov Jan 30, 2023
fc241d1
:pencil2: Add comment
ivov Jan 30, 2023
4f1fd0b
:truck: Move extension
ivov Jan 30, 2023
f0bdef2
:pencil2: Update `BREAKING-CHANGES.md`
ivov Jan 30, 2023
ce7c9eb
:pencil2: Update upcoming version
ivov Jan 30, 2023
14b6143
:pencil2: Fix grammar
ivov Jan 30, 2023
0c986f8
:pencil2: Shorten message
ivov Jan 30, 2023
e3e1dc2
:bug: Fix `Esc` behavior
ivov Jan 31, 2023
cdd8911
:bug: Fix `isNumeric`
ivov Jan 31, 2023
0d84023
Merge branch 'master' into ado-258-date-extensions-updates
MiloradFilipovic Jan 31, 2023
dd38a26
✨ Using UTC to handle-dates on back-end
MiloradFilipovic Jan 31, 2023
27283a6
✅ Added more unit tests for date extensions
MiloradFilipovic Jan 31, 2023
e9dd70c
⚡ Not using `JSON.stringify` to render dates
MiloradFilipovic Feb 1, 2023
1e5a227
⚡ Using `deep-equal` library instead of our `deepCompare` function
MiloradFilipovic Feb 1, 2023
4660bb6
✅ Adding more tests to array extensions
MiloradFilipovic Feb 1, 2023
a9c3442
⚡ Fixing `inBetween` extension function
MiloradFilipovic Feb 1, 2023
25cde23
✅ Added tests for `.inBetween()`
MiloradFilipovic Feb 1, 2023
bd0ca7c
⚡ Updating `isEven` and `isOdd` to throw for floats
MiloradFilipovic Feb 1, 2023
938d025
⚡ Updating `Array.merge()` so it works without arguments
MiloradFilipovic Feb 2, 2023
965ca55
Merge branch 'master' into ado-251-data-transformation-fixes
MiloradFilipovic Feb 2, 2023
7d024b8
🔀 Fixing leftover merge confilct
MiloradFilipovic Feb 2, 2023
15da866
⚡ Updating `removeFieldsContaining` and `keepFieldsContaining` to thr…
MiloradFilipovic Feb 2, 2023
e7cbdbc
⚡ Fixing `pluck()` so it returns only plucked values
MiloradFilipovic Feb 2, 2023
4db283b
⬆️ Updating pnpm lockfile
MiloradFilipovic Feb 2, 2023
72ab548
👕 Fixing lint errors
MiloradFilipovic Feb 2, 2023
d212095
⚡ Using workflow timezone to display dates
MiloradFilipovic Feb 2, 2023
b92a434
✔️ Updating tests with workflow timezone
MiloradFilipovic Feb 2, 2023
2e7c8af
⚡ Not using system timezone when creating Luxon dates
MiloradFilipovic Feb 2, 2023
6498fa1
⚡ Updating `merge()` and `pluck()` array functions
MiloradFilipovic Feb 3, 2023
af25047
🔀 Sync with `master`: Removing code that was preserved during merge
MiloradFilipovic Feb 3, 2023
6904fb1
⚡ Updating `.pluck()` to return full array if no arguments are passed
MiloradFilipovic Feb 3, 2023
5265dbd
⚡ Updating `keepFieldsContaining` and `merge` object functions
MiloradFilipovic Feb 3, 2023
5cc4857
⚡ Using week as default for `date.extract()`
MiloradFilipovic Feb 3, 2023
ba713e1
✅ Adding more test cases for DT functions
MiloradFilipovic Feb 3, 2023
135fcb7
Merge branch 'master' into ado-251-data-transformation-fixes
MiloradFilipovic Feb 13, 2023
adc2a03
⚡ Removing `Object.merge` extension function. Adding missing `deep-eq…
MiloradFilipovic Feb 13, 2023
d70227b
⚡ Handling `toDate` case when time component is not specified
MiloradFilipovic Feb 13, 2023
5cddb1a
⚡ Using workflow's timezone to render dates in output panel, updated …
MiloradFilipovic Feb 13, 2023
7d63201
⚡ Not parsing numbers as dates
MiloradFilipovic Feb 13, 2023
bd7a559
👕 Fixing lint errors
MiloradFilipovic Feb 13, 2023
173dacb
⚡ Fixing a typo
MiloradFilipovic Feb 13, 2023
56dd765
⚡ Making date detection more strict so only stringified dates are get…
MiloradFilipovic Feb 14, 2023
f87cc6d
👌 Addressing PR feedback
MiloradFilipovic Feb 15, 2023
b9ba7ae
Merge branch 'master' into ado-251-data-transformation-fixes
MiloradFilipovic Feb 15, 2023
816ea10
🔥 Removing leftover comment
MiloradFilipovic Feb 15, 2023
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
11 changes: 8 additions & 3 deletions packages/editor-ui/src/components/RunDataJson.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@ import VueJsonPretty from 'vue-json-pretty';
import { LOCAL_STORAGE_MAPPING_FLAG } from '@/constants';
import { IDataObject, INodeExecutionData } from 'n8n-workflow';
import Draggable from '@/components/Draggable.vue';
import { convertPath, executionDataToJson, isString, shorten } from '@/utils';
import { parseDate, executionDataToJson, isString, shorten } from '@/utils';
import { INodeUi } from '@/Interface';
import { externalHooks } from '@/mixins/externalHooks';
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
import MappingPill from './MappingPill.vue';
import { getMappedExpression } from '@/utils/mappingUtils';
import { useWorkflowsStore } from '@/stores/workflows';

const runDataJsonActions = () => import('@/components/RunDataJsonActions.vue');

Expand Down Expand Up @@ -149,7 +150,7 @@ export default mixins(externalHooks).extend({
}
},
computed: {
...mapStores(useNDVStore),
...mapStores(useNDVStore, useWorkflowsStore),
jsonData(): IDataObject[] {
return executionDataToJson(this.inputData);
},
Expand Down Expand Up @@ -209,7 +210,11 @@ export default mixins(externalHooks).extend({
}, 1000); // ensure dest data gets set if drop
},
getContent(value: unknown): string {
return isString(value) ? `"${value}"` : JSON.stringify(value);
if (isString(value)) {
const parsedDate = parseDate(value, this.workflowsStore.workflow.settings?.timezone);
return parsedDate ? parsedDate.toString() : `"${value}"`;
}
return JSON.stringify(value);
},
getListItemName(path: string): string {
return path.replace(/^(\["?\d"?]\.?)/g, '');
Expand Down
7 changes: 6 additions & 1 deletion packages/editor-ui/src/components/RunDataTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@
<script lang="ts">
/* eslint-disable prefer-spread */
import { INodeUi, ITableData, NDVState } from '@/Interface';
import { getPairedItemId } from '@/utils';
import { getPairedItemId, parseDate } from '@/utils';
import Vue, { PropType } from 'vue';
import mixins from 'vue-typed-mixins';
import { GenericValue, IDataObject, INodeExecutionData } from 'n8n-workflow';
Expand All @@ -171,6 +171,7 @@ import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import MappingPill from './MappingPill.vue';
import { getMappedExpression } from '@/utils/mappingUtils';
import { DateTime } from 'luxon';

const MAX_COLUMNS_LIMIT = 40;

Expand Down Expand Up @@ -365,6 +366,10 @@ export default mixins(externalHooks).extend({
return this.$locale.baseText('runData.emptyString');
}
if (typeof value === 'string') {
const parsedDate = parseDate(value, this.workflowsStore.workflow.settings?.timezone);
if (parsedDate) {
return parsedDate.toString();
}
return value.replaceAll('\n', '\\n');
}

Expand Down
16 changes: 16 additions & 0 deletions packages/editor-ui/src/utils/typesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { IDataObject, jsonParse } from 'n8n-workflow';
import { Schema, Optional, Primitives } from '@/Interface';
import { isObj } from '@/utils/typeGuards';
import { generatePath } from '@/utils/mappingUtils';
import { DateTime } from 'luxon';
import { useWorkflowsStore } from '@/stores/workflows';

/*
Constants and utility functions than can be used to manipulate different data types and objects
Expand Down Expand Up @@ -247,3 +249,17 @@ export const getSchema = (input: Optional<Primitives | object>, path = ''): Sche

return schema;
};

// Convert UTC dates that come from back-end to workflow timezone
export const parseDate = (input: string, timezone: string | undefined): DateTime | null => {
const isUTCDate = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/.test(
input,
);
if (isUTCDate) {
const date = new Date(Date.parse(input));
if (date.toString() !== 'Invalid Date') {
return DateTime.fromJSDate(date, { zone: timezone });
}
}
return null;
};
2 changes: 2 additions & 0 deletions packages/workflow/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"devDependencies": {
"@n8n_io/eslint-config": "",
"@types/crypto-js": "^4.1.1",
"@types/deep-equal": "^1.0.1",
"@types/express": "^4.17.6",
"@types/jmespath": "^0.15.0",
"@types/lodash.get": "^4.4.6",
Expand All @@ -54,6 +55,7 @@
"@n8n_io/riot-tmpl": "^2.0.0",
"ast-types": "0.15.2",
"crypto-js": "^4.1.1",
"deep-equal": "^2.2.0",
"esprima-next": "5.8.4",
"jmespath": "^0.16.0",
"js-base64": "^3.7.2",
Expand Down
14 changes: 12 additions & 2 deletions packages/workflow/src/Expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,21 @@ export class Expression {
convertObjectValueToString(value: object): string {
const typeName = Array.isArray(value) ? 'Array' : 'Object';

if (DateTime.isDateTime(value) && value.invalidReason !== null) {
if (value instanceof DateTime && value.invalidReason !== null) {
throw new Error('invalid DateTime');
}

const result = JSON.stringify(value)
let result = '';
if (value instanceof Date) {
// We don't want to use JSON.stringify for dates since it disregards workflow timezone
result = DateTime.fromJSDate(value, {
zone: this.workflow.settings.timezone?.toString() ?? 'default',
}).toISO();
} else {
result = JSON.stringify(value);
}

result = result
.replace(/,"/g, ', "') // spacing for
.replace(/":/g, '": '); // readability

Expand Down
183 changes: 74 additions & 109 deletions packages/workflow/src/Extensions/ArrayExtensions.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,7 @@
import { ExpressionError, ExpressionExtensionError } from '../ExpressionError';
import type { ExtensionMap } from './Extensions';
import { compact as oCompact, merge as oMerge } from './ObjectExtensions';

function deepCompare(left: unknown, right: unknown): boolean {
if (left === right) {
return true;
}

// Check to see if they're the basic type
if (typeof left !== typeof right) {
return false;
}

if (typeof left === 'number' && isNaN(left) && isNaN(right as number)) {
return true;
}

// Explicitly return false if certain primitives don't equal each other
if (['number', 'string', 'bigint', 'boolean', 'symbol'].includes(typeof left) && left !== right) {
return false;
}

// Quickly check how many properties each has to avoid checking obviously mismatching
// objects
if (Object.keys(left as object).length !== Object.keys(right as object).length) {
return false;
}

// Quickly check if they're arrays
if (Array.isArray(left) !== Array.isArray(right)) {
return false;
}

// Check if arrays are equal, ordering is important
if (Array.isArray(left)) {
if (left.length !== (right as unknown[]).length) {
return false;
}
return left.every((v, i) => deepCompare(v, (right as object[])[i]));
}

// Check right first quickly. This is to see if we have mismatched properties.
// We'll check the left more indepth later to cover all our bases.
for (const key in right as object) {
if ((left as object).hasOwnProperty(key) !== (right as object).hasOwnProperty(key)) {
return false;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
} else if (typeof (left as any)[key] !== typeof (right as any)[key]) {
return false;
}
}

// Check left more in depth
for (const key in left as object) {
if ((left as object).hasOwnProperty(key) !== (right as object).hasOwnProperty(key)) {
return false;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
} else if (typeof (left as any)[key] !== typeof (right as any)[key]) {
return false;
}

if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
typeof (left as any)[key] === 'object'
) {
if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
(left as any)[key] !== (right as any)[key] &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
!deepCompare((left as any)[key], (right as any)[key])
) {
return false;
}
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
if ((left as any)[key] !== (right as any)[key]) {
return false;
}
}
}

return true;
}
import { compact as oCompact } from './ObjectExtensions';
import deepEqual from 'deep-equal';

function first(value: unknown[]): unknown {
return value[0];
Expand All @@ -103,18 +23,26 @@ function pluck(value: unknown[], extraArgs: unknown[]): unknown[] {
if (!Array.isArray(extraArgs)) {
throw new ExpressionError('arguments must be passed to pluck');
}
const fieldsToPluck = extraArgs;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (value as any[]).map((element: object) => {
const entries = Object.entries(element);
return entries.reduce((p, c) => {
const [key, val] = c as [string, Date | string | number];
if (fieldsToPluck.includes(key)) {
Object.assign(p, { [key]: val });
if (!extraArgs || extraArgs.length === 0) {
return value;
}
const plucked = value.reduce<unknown[]>((pluckedFromObject, current) => {
if (current && typeof current === 'object') {
const p: unknown[] = [];
Object.keys(current).forEach((k) => {
extraArgs.forEach((field: string) => {
if (current && field === k) {
p.push((current as { [key: string]: unknown })[k]);
}
});
});
if (p.length > 0) {
pluckedFromObject.push(p.length === 1 ? p[0] : p);
}
return p;
}, {});
}) as unknown[];
}
return pluckedFromObject;
}, new Array<unknown>());
return plucked;
}

function randomItem(value: unknown[]): unknown {
Expand All @@ -127,8 +55,13 @@ function unique(value: unknown[], extraArgs: string[]): unknown[] {
return value.reduce<unknown[]>((l, v) => {
if (typeof v === 'object' && v !== null && extraArgs.every((i) => i in v)) {
const alreadySeen = l.find((i) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
extraArgs.every((j) => deepCompare((i as any)[j], (v as any)[j])),
extraArgs.every((j) =>
deepEqual(
(i as Record<string, unknown>)[j],
(v as Record<string, unknown>, { strict: true })[j],
{ strict: true },
),
),
);
if (!alreadySeen) {
l.push(v);
Expand All @@ -138,7 +71,7 @@ function unique(value: unknown[], extraArgs: string[]): unknown[] {
}, []);
}
return value.reduce<unknown[]>((l, v) => {
if (l.findIndex((i) => deepCompare(i, v)) === -1) {
if (l.findIndex((i) => deepEqual(i, v, { strict: true })) === -1) {
l.push(v);
}
return l;
Expand Down Expand Up @@ -281,28 +214,58 @@ function renameKeys(value: unknown[], extraArgs: string[]): unknown[] {
});
}

function merge(value: unknown[], extraArgs: unknown[][]): unknown[] {
function mergeObjects(value: Record<string, unknown>, extraArgs: unknown[]): unknown {
const [other] = extraArgs;

if (!other) {
return value;
}

if (typeof other !== 'object') {
throw new ExpressionExtensionError('merge(): expected object arg');
}

const newObject = { ...value };
for (const [key, val] of Object.entries(other)) {
if (!(key in newObject)) {
newObject[key] = val;
}
}
return newObject;
}

function merge(value: unknown[], extraArgs: unknown[][]): unknown {
const [others] = extraArgs;

if (others === undefined) {
// If there are no arguments passed, merge all objects within the array
const merged = value.reduce((combined, current) => {
if (current !== null && typeof current === 'object' && !Array.isArray(current)) {
combined = mergeObjects(combined as Record<string, unknown>, [current]);
}
return combined;
}, {});
return merged;
}

if (!Array.isArray(others)) {
throw new ExpressionExtensionError(
'merge(): expected array arg, e.g. .merge([{ id: 1, otherValue: 3 }])',
);
}
const listLength = value.length > others.length ? value.length : others.length;
const newList = new Array(listLength);
let merged = {};
for (let i = 0; i < listLength; i++) {
if (value[i] !== undefined) {
if (typeof value[i] === 'object' && typeof others[i] === 'object') {
newList[i] = oMerge(value[i] as object, [others[i]]);
} else {
newList[i] = value[i];
merged = Object.assign(
merged,
mergeObjects(value[i] as Record<string, unknown>, [others[i]]),
);
}
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
newList[i] = others[i];
}
}
return newList;
return merged;
}

function union(value: unknown[], extraArgs: unknown[][]): unknown[] {
Expand All @@ -312,7 +275,8 @@ function union(value: unknown[], extraArgs: unknown[][]): unknown[] {
}
const newArr: unknown[] = Array.from(value);
for (const v of others) {
if (newArr.findIndex((w) => deepCompare(w, v)) === -1) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if (newArr.findIndex((w) => deepEqual(w, v, { strict: true })) === -1) {
newArr.push(v);
}
}
Expand All @@ -328,7 +292,7 @@ function difference(value: unknown[], extraArgs: unknown[][]): unknown[] {
}
const newArr: unknown[] = [];
for (const v of value) {
if (others.findIndex((w) => deepCompare(w, v)) === -1) {
if (others.findIndex((w) => deepEqual(w, v, { strict: true })) === -1) {
newArr.push(v);
}
}
Expand All @@ -344,12 +308,13 @@ function intersection(value: unknown[], extraArgs: unknown[][]): unknown[] {
}
const newArr: unknown[] = [];
for (const v of value) {
if (others.findIndex((w) => deepCompare(w, v)) !== -1) {
if (others.findIndex((w) => deepEqual(w, v, { strict: true })) !== -1) {
newArr.push(v);
}
}
for (const v of others) {
if (value.findIndex((w) => deepCompare(w, v)) !== -1) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if (value.findIndex((w) => deepEqual(w, v, { strict: true })) !== -1) {
newArr.push(v);
}
}
Expand Down
Loading