diff --git a/src/core/common/utils/dotize.spec.ts b/src/core/common/utils/dotize.spec.ts index def82ef2c..fe7366fcc 100644 --- a/src/core/common/utils/dotize.spec.ts +++ b/src/core/common/utils/dotize.spec.ts @@ -7,7 +7,7 @@ it("converts nested properties", () => { }; const output = dotize(input); - expect(output).toEqual({ + expect(output).toStrictEqual({ a: "property", "can.be": "nested", "can.really.deeply": "sometimes", @@ -19,7 +19,7 @@ it("converts properties with dates", () => { const input = { a: now, can: { be: now } }; const output = dotize(input); - expect(output).toEqual({ + expect(output).toStrictEqual({ a: now, "can.be": now, }); @@ -35,7 +35,7 @@ it("converts array properties when enabled", () => { }; const output = dotize(input); - expect(output).toEqual({ + expect(output).toStrictEqual({ "a[0].property": "with", "a[0].an": "array", "a[1].value[0].sometimes": "nested", @@ -53,7 +53,7 @@ it("does not converts array properties when disabled", () => { }; const output = dotize(input, { ignoreArrays: true }); - expect(output).toEqual({ + expect(output).toStrictEqual({ "other.times": "not", }); }); @@ -61,7 +61,22 @@ it("does not converts array properties when disabled", () => { it("does convert array properties properly", () => { expect( dotize({ wordlist: { banned: ["banned"] } }, { embedArrays: true }) - ).toEqual({ + ).toStrictEqual({ "wordlist.banned": ["banned"], }); }); + +it("does exclude undefined properties by default", () => { + expect(dotize({ test: { is: undefined, a: 1 } })).toStrictEqual({ + "test.a": 1, + }); +}); + +it("does include undefined properties when requested", () => { + expect( + dotize({ test: { is: undefined, a: 1 } }, { includeUndefined: true }) + ).toStrictEqual({ + "test.a": 1, + "test.is": undefined, + }); +}); diff --git a/src/core/common/utils/dotize.ts b/src/core/common/utils/dotize.ts index 7cdd96f17..4b9c30f69 100644 --- a/src/core/common/utils/dotize.ts +++ b/src/core/common/utils/dotize.ts @@ -1,4 +1,11 @@ -import { isArray, isNull, isNumber, isPlainObject, isString } from "lodash"; +import { + isArray, + isNull, + isNumber, + isPlainObject, + isString, + isUndefined, +} from "lodash"; function isObject(obj: any): obj is Record { return isPlainObject(obj); @@ -12,13 +19,23 @@ function deriveKey(property: string, prefix?: string) { return property; } -function reduce( - result: Record, - obj: Record | number | null | string, - ignoreArrays: boolean, - embedArrays: boolean, - prefix?: string -) { +interface ReduceOptions { + result: Record; + obj: Record | number | null | string; + ignoreArrays: boolean; + embedArrays: boolean; + includeUndefined: boolean; + prefix?: string; +} + +function reduce({ + result, + obj, + ignoreArrays, + embedArrays, + includeUndefined, + prefix, +}: ReduceOptions) { if (prefix) { if (isNumber(obj) || isString(obj) || isNull(obj)) { result[prefix] = obj; @@ -36,24 +53,38 @@ function reduce( const key = deriveKey(property, prefix); if (isPlainObject(value)) { - reduce(result, value, ignoreArrays, embedArrays, key); + reduce({ + result, + obj: value, + ignoreArrays, + embedArrays, + includeUndefined, + prefix: key, + }); } else if (isArray(value)) { if (!ignoreArrays) { if (embedArrays) { result[key] = value; } else { value.forEach((item, index) => { - reduce( + reduce({ result, - item, + obj: item, ignoreArrays, embedArrays, - `${key}[${index}]` - ); + includeUndefined, + prefix: `${key}[${index}]`, + }); }); } } } else { + // If the underlying value is undefined and we aren't including + // undefined elements, then continue. + if (!includeUndefined && isUndefined(value)) { + continue; + } + result[key] = value; } } @@ -74,9 +105,19 @@ export interface DotizeOptions { * without recusing the dotize algorithm to it. */ embedArrays?: boolean; + + /** + * includeUndefined if true, will include undefined values in the result. + */ + includeUndefined?: boolean; } export const dotize = ( obj: Record, - { ignoreArrays = false, embedArrays = false }: DotizeOptions = {} -): Record => reduce({}, obj, ignoreArrays, embedArrays); + { + ignoreArrays = false, + embedArrays = false, + includeUndefined = false, + }: DotizeOptions = {} +): Record => + reduce({ result: {}, obj, ignoreArrays, embedArrays, includeUndefined }); diff --git a/src/core/server/models/story/index.ts b/src/core/server/models/story/index.ts index c71107f1e..31f83e12a 100644 --- a/src/core/server/models/story/index.ts +++ b/src/core/server/models/story/index.ts @@ -311,7 +311,7 @@ export async function retrieveManyStoriesByURL( export type UpdateStoryInput = Omit< Partial, - "id" | "tenantID" | "createdAt" + "id" | "tenantID" | "closedAt" | "createdAt" >; export async function updateStory( diff --git a/src/core/server/services/mongodb/index.ts b/src/core/server/services/mongodb/index.ts index 71877d0a8..d3552eb28 100644 --- a/src/core/server/services/mongodb/index.ts +++ b/src/core/server/services/mongodb/index.ts @@ -7,6 +7,7 @@ export async function createMongoClient(config: Config): Promise { try { return await MongoClient.connect(config.get("mongodb"), { useNewUrlParser: true, + ignoreUndefined: true, }); } catch (err) { throw new InternalError(err, "could not connect to mongodb");