diff --git a/src/features/import/types.ts b/src/features/import/types.ts new file mode 100644 index 000000000..74e501066 --- /dev/null +++ b/src/features/import/types.ts @@ -0,0 +1,35 @@ +import { ZetkinPerson } from 'utils/types/zetkin'; + +type PersonSetfieldsBulkOp = { + data: Partial; + op: 'person.setfields'; +}; + +type PersonTagBulkOp = { + op: 'person.tag'; + tag_id: number; + value?: string | number | null; +}; + +type PersonAddtoorgBulkOp = { + op: 'person.addtoorg'; + org_id: number; +}; + +type PersonCreateBulkOp = { + op: 'person.create'; + ops: BulkSubOp[]; +}; + +type PersonGetBulkOp = { + key: { id: number } | { ext_id: string }; + op: 'person.get'; + ops: BulkSubOp[]; +}; + +export type BulkSubOp = + | PersonSetfieldsBulkOp + | PersonTagBulkOp + | PersonAddtoorgBulkOp; + +export type BulkOp = PersonCreateBulkOp | PersonGetBulkOp; diff --git a/src/features/import/utils/prepareImportOperations.spec.ts b/src/features/import/utils/prepareImportOperations.spec.ts index 3bfa18806..85117dc4e 100644 --- a/src/features/import/utils/prepareImportOperations.spec.ts +++ b/src/features/import/utils/prepareImportOperations.spec.ts @@ -8,1308 +8,673 @@ import { ColumnKind, Sheet } from './types'; const countryCode = mockOrganization.country as CountryCode; describe('prepareImportOperations()', () => { - describe('when first row is header', () => { - it('converts Zetkin ID', () => { - const configData: Sheet = { - columns: [{ idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }], - firstRowIsHeaders: true, - rows: [ - { - data: ['ID', 'First name', 'Last Name', 'DevTag', 'Org'], - }, - { - data: ['123', 'Jane', 'Doe', 'Frontend', 1], - }, - { - data: ['124', 'John', 'Doe', 'Backend', 2], - }, - ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - id: 123, - }, - op: 'person.import', - }, - { - data: { - id: 124, - }, - op: 'person.import', - }, - ]); - }); + it('returns no ops when rows is empty', () => { + const sheet: Sheet = { + columns: [{ idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }], + firstRowIsHeaders: false, + rows: [], + title: 'My sheet', + }; - it('converts external ID', () => { - const configData: Sheet = { - columns: [ - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { kind: ColumnKind.UNKNOWN, selected: false }, - { kind: ColumnKind.UNKNOWN, selected: false }, - { kind: ColumnKind.UNKNOWN, selected: false }, - { kind: ColumnKind.UNKNOWN, selected: false }, - ], - firstRowIsHeaders: true, - rows: [ - { - data: ['ID', 'First name', 'Last Name', 'DevTag', 'Org'], - }, - { - data: ['123', 'Jane', 'Doe', 'Frontend', 1], - }, - { - data: ['124', 'John', 'Doe', 'Backend', 2], - }, - ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - ext_id: '123', - }, - op: 'person.import', - }, - { - data: { - ext_id: '124', - }, - op: 'person.import', - }, - ]); - }); + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([]); + }); - it('converts data only', () => { - const configData: Sheet = { - columns: [ - { kind: ColumnKind.UNKNOWN, selected: false }, - { - field: 'first_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { - field: 'last_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { kind: ColumnKind.UNKNOWN, selected: false }, - { kind: ColumnKind.UNKNOWN, selected: false }, - ], - firstRowIsHeaders: true, - rows: [ - { - data: ['ID', 'First name', 'Last Name', 'DevTag', 'Org'], - }, - { - data: ['123', 'Jane', 'Doe', 'Frontend', 1], - }, - { - data: ['124', 'John', 'Doe', 'Backend', 2], - }, - ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - first_name: 'Jane', - last_name: 'Doe', - }, - op: 'person.import', - }, - { - data: { - first_name: 'John', - last_name: 'Doe', - }, - op: 'person.import', - }, - ]); - }); - it('converts tags only', () => { - const configData: Sheet = { - columns: [ - { kind: ColumnKind.UNKNOWN, selected: false }, - { kind: ColumnKind.UNKNOWN, selected: false }, - { kind: ColumnKind.UNKNOWN, selected: false }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], - selected: true, - }, - { kind: ColumnKind.UNKNOWN, selected: false }, - ], - firstRowIsHeaders: true, - rows: [ - { - data: ['ID', 'First name', 'Last Name', 'DevTag', 'Org'], - }, - { - data: ['123', 'Jane', 'Doe', 'Frontend', 1], - }, - { - data: ['124', 'John', 'Doe', 'Backend', 2], - }, - ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - op: 'person.import', - tags: [{ id: 123 }, { id: 100 }], - }, - { - op: 'person.import', - tags: [{ id: 124 }, { id: 100 }], - }, - ]); - }); - it('converts simple data with ID', () => { - const configData: Sheet = { - columns: [ - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { - field: 'first_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { - field: 'last_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { kind: ColumnKind.UNKNOWN, selected: false }, - ], - firstRowIsHeaders: true, - rows: [ - { - data: ['ID', 'First name', 'Last Name', 'DevTag', 'Org'], - }, - { - data: ['123', 'Jane', 'Doe', 'Frontend', 1], - }, - { - data: ['124', 'John', 'Doe', 'Backend', 2], - }, - ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - ext_id: '123', - first_name: 'Jane', - last_name: 'Doe', - }, - op: 'person.import', - }, - { - data: { - ext_id: '124', - first_name: 'John', - last_name: 'Doe', - }, - op: 'person.import', - }, - ]); - }); - it('converts ID, data, tags and orgs', () => { - const configData: Sheet = { - columns: [ - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { - field: 'first_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { - field: 'last_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], - selected: true, - }, - { - kind: ColumnKind.ORGANIZATION, - mapping: [ - { orgId: 272, value: 1 }, - { orgId: 272, value: 2 }, - ], - selected: true, - }, - ], - firstRowIsHeaders: true, - rows: [ - { - data: ['ID', 'First name', 'Last Name', 'DevTag', 'Org'], - }, - { - data: ['123', 'Jane', 'Doe', 'Frontend', 1], - }, - { - data: ['124', 'John', 'Doe', 'Backend', 2], - }, - ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ + it('returns no ops when the only row is headers', () => { + const sheet: Sheet = { + columns: [{ idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }], + firstRowIsHeaders: true, + rows: [ { - data: { - ext_id: '123', - first_name: 'Jane', - last_name: 'Doe', - }, - op: 'person.import', - organizations: [272], - tags: [{ id: 123 }, { id: 100 }], + data: ['ID', 'First Name', 'Last Name', 'DevTag', 'Org'], }, + ], + title: 'My sheet', + }; + + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([]); + }); + + it('returns person.get and person.setfields when just fields', () => { + const sheet: Sheet = { + columns: [ + { field: 'first_name', kind: ColumnKind.FIELD, selected: true }, + { field: 'last_name', kind: ColumnKind.FIELD, selected: true }, + { field: 'email', kind: ColumnKind.FIELD, selected: true }, + ], + firstRowIsHeaders: false, + rows: [ { - data: { - ext_id: '124', - first_name: 'John', - last_name: 'Doe', - }, - op: 'person.import', - organizations: [272], - tags: [{ id: 124 }, { id: 100 }], + data: ['Clara', 'Zetkin', 'clara@example.com'], }, - ]); - }); - it('converts ID, data and tags', () => { - const configData: Sheet = { - columns: [ - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { - field: 'first_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { - field: 'last_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], - selected: true, - }, - ], - firstRowIsHeaders: true, - rows: [ - { - data: ['ID', 'First name', 'Last Name', 'DevTag', 'Org'], - }, - { - data: ['123', 'Jane', 'Doe', 'Frontend', 1], - }, + ], + title: 'My sheet', + }; + + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ + { + op: 'person.create', + ops: [ { - data: ['124', 'John', 'Doe', 'Backend', 2], + data: { + email: 'clara@example.com', + first_name: 'Clara', + last_name: 'Zetkin', + }, + op: 'person.setfields', }, ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - ext_id: '123', - first_name: 'Jane', - last_name: 'Doe', - }, - op: 'person.import', - tags: [{ id: 123 }, { id: 100 }], - }, + }, + ]); + }); + + it('ignores unselected columns', () => { + const sheet: Sheet = { + columns: [ + { field: 'first_name', kind: ColumnKind.FIELD, selected: true }, + { field: 'last_name', kind: ColumnKind.FIELD, selected: true }, + { field: 'email', kind: ColumnKind.FIELD, selected: false }, + ], + firstRowIsHeaders: false, + rows: [ { - data: { - ext_id: '124', - first_name: 'John', - last_name: 'Doe', - }, - op: 'person.import', - tags: [{ id: 124 }, { id: 100 }], + data: ['Clara', 'Zetkin', 'clara@example.com'], }, - ]); - }); - it('converts ID, data and enum field', () => { - const configData: Sheet = { - columns: [ - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { - field: 'first_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { - field: 'last_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { - field: 'enum_field', - kind: ColumnKind.ENUM, - mapping: [ - { key: 'first', value: 'Dummy value' }, - { key: 'second', value: null }, - ], - selected: true, - }, - ], - firstRowIsHeaders: true, - rows: [ - { - data: ['ID', 'First name', 'Last Name', 'Enum', 'Org'], - }, - { - data: ['123', 'Jane', 'Doe', 'Dummy value', 1], - }, - { - data: ['124', 'John', 'Doe', null, 2], - }, + ], + title: 'My sheet', + }; + + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ + { + op: 'person.create', + ops: [ { - data: ['125', 'John', 'Doe', undefined, 2], + data: { + first_name: 'Clara', + last_name: 'Zetkin', + }, + op: 'person.setfields', }, ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - enum_field: 'first', - ext_id: '123', - first_name: 'Jane', - last_name: 'Doe', - }, - op: 'person.import', - }, + }, + ]); + }); + + it('uses person.get if there is an external ID configured', () => { + const sheet: Sheet = { + columns: [ + { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, + { field: 'email', kind: ColumnKind.FIELD, selected: true }, + ], + firstRowIsHeaders: false, + rows: [ { - data: { - enum_field: 'second', - ext_id: '124', - first_name: 'John', - last_name: 'Doe', - }, - op: 'person.import', + data: ['123', 'clara@example.com'], }, - { - data: { - enum_field: 'second', - ext_id: '125', - first_name: 'John', - last_name: 'Doe', - }, - op: 'person.import', + ], + title: 'My sheet', + }; + + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ + { + key: { + ext_id: '123', }, - ]); - }); - it('converts other columns when ID column is not chosen', () => { - const configData: Sheet = { - columns: [ - { kind: ColumnKind.UNKNOWN, selected: false }, - { - field: 'first_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { - field: 'last_name', - kind: ColumnKind.FIELD, - selected: true, - }, + op: 'person.get', + ops: [ { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], - selected: true, - }, - { - kind: ColumnKind.ORGANIZATION, - mapping: [ - { orgId: 272, value: 1 }, - { orgId: 272, value: 2 }, - ], - selected: true, + data: { + email: 'clara@example.com', + }, + op: 'person.setfields', }, ], - firstRowIsHeaders: true, - rows: [ - { - data: ['ID', 'First name', 'Last Name', 'DevTag', 'Org'], - }, - { - data: ['123', 'Jane', 'Doe', 'Frontend', 1], - }, - { - data: ['124', 'John', 'Doe', 'Backend', 2], - }, - ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - first_name: 'Jane', - last_name: 'Doe', - }, - op: 'person.import', - organizations: [272], - tags: [{ id: 123 }, { id: 100 }], - }, - { - data: { - first_name: 'John', - last_name: 'Doe', - }, - op: 'person.import', - organizations: [272], - tags: [{ id: 124 }, { id: 100 }], - }, - ]); - }); + }, + ]); }); - describe('when first row is not header', () => { - it('converts ID, data, tags and orgs', () => { - const configData: Sheet = { - columns: [ - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { - field: 'first_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { - field: 'last_name', - kind: ColumnKind.FIELD, - selected: true, - }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], - selected: true, - }, - { - kind: ColumnKind.ORGANIZATION, - mapping: [ - { orgId: 272, value: 1 }, - { orgId: 272, value: 2 }, - ], - selected: true, - }, - ], - firstRowIsHeaders: false, - rows: [ - { - data: ['123', 'Jane', 'Doe', 'Frontend', 1], - }, - { - data: ['124', 'John', 'Doe', 'Backend', 2], - }, - ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - ext_id: '123', - first_name: 'Jane', - last_name: 'Doe', - }, - op: 'person.import', - organizations: [272], - tags: [{ id: 123 }, { id: 100 }], - }, + it('uses person.get if there is an ID configured', () => { + const sheet: Sheet = { + columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, + { field: 'email', kind: ColumnKind.FIELD, selected: true }, + ], + firstRowIsHeaders: false, + rows: [ { - data: { - ext_id: '124', - first_name: 'John', - last_name: 'Doe', - }, - op: 'person.import', - organizations: [272], - tags: [{ id: 124 }, { id: 100 }], + data: ['123', 'clara@example.com'], }, - ]); - }); - }); + ], + title: 'My sheet', + }; - describe('prepareImportOperations excludes mapping rows with empty or null values', () => { - it('excludes empty string and null in data', () => { - const configData: Sheet = { - columns: [ - { kind: ColumnKind.UNKNOWN, selected: false }, - { - field: 'city', - kind: ColumnKind.FIELD, - selected: true, - }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], - selected: true, - }, - { - kind: ColumnKind.ORGANIZATION, - mapping: [ - { orgId: 272, value: 1 }, - { orgId: 273, value: 2 }, - ], - selected: true, - }, - ], - firstRowIsHeaders: true, - rows: [ - { - data: ['ID', 'City', 'DevTag', 'Org'], - }, - { - data: ['123', null, 'Frontend', 1], - }, - { - data: ['124', 'Linköping', 'Backend', 2], - }, - ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - op: 'person.import', - organizations: [272], - tags: [{ id: 123 }, { id: 100 }], - }, - { - data: { - city: 'Linköping', - }, - op: 'person.import', - organizations: [273], - tags: [{ id: 124 }, { id: 100 }], + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ + { + key: { + id: 123, }, - ]); - }); - - it('excludes empty string, null or not matched value tags', () => { - const configData: Sheet = { - columns: [ - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { - field: 'city', - kind: ColumnKind.FIELD, - selected: true, - }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], - selected: true, - }, - { - kind: ColumnKind.ORGANIZATION, - mapping: [{ orgId: 272, value: 1 }], - selected: true, - }, - ], - firstRowIsHeaders: false, - rows: [ + op: 'person.get', + ops: [ { - data: ['123', 'Linköping', null, 1], - }, - { - data: ['124', 'Linköping', 'Backend', 1], - }, - { - data: ['125', 'Linköping', 'Designer', 1], + data: { + email: 'clara@example.com', + }, + op: 'person.setfields', }, ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - city: 'Linköping', - ext_id: '123', - }, - op: 'person.import', - organizations: [272], - }, - { - data: { - city: 'Linköping', - ext_id: '124', - }, - op: 'person.import', - organizations: [272], - tags: [{ id: 124 }, { id: 100 }], - }, - { - data: { - city: 'Linköping', - ext_id: '125', - }, - op: 'person.import', - organizations: [272], - }, - ]); - }); + }, + ]); + }); - it('excludes empty string or null in orgs', () => { - const configData: Sheet = { - columns: [ - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { - field: 'city', - kind: ColumnKind.FIELD, - selected: true, - }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], - selected: true, - }, - { - kind: ColumnKind.ORGANIZATION, - mapping: [ - { orgId: 272, value: 1 }, - { orgId: 273, value: 2 }, - ], - selected: true, - }, - ], - firstRowIsHeaders: false, - rows: [ - { - data: ['123', 'Linköping', 'Frontend', 1], - }, - { - data: ['124', 'Linköping', 'Backend', null], - }, - { - data: ['125', 'Linköping', 'Backend', 3], - }, - ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - city: 'Linköping', - ext_id: '123', - }, - op: 'person.import', - organizations: [272], - tags: [{ id: 123 }, { id: 100 }], - }, - { - data: { - city: 'Linköping', - ext_id: '124', - }, - op: 'person.import', - tags: [{ id: 124 }, { id: 100 }], - }, + it('uses person.get and sets ext_id when both IDs are configured', () => { + const sheet: Sheet = { + columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, + { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, + { field: 'email', kind: ColumnKind.FIELD, selected: true }, + ], + firstRowIsHeaders: false, + rows: [ { - data: { - city: 'Linköping', - ext_id: '125', - }, - op: 'person.import', - tags: [{ id: 124 }, { id: 100 }], + data: ['123', '9999', 'clara@example.com'], }, - ]); - }); + ], + title: 'My sheet', + }; - it('excludes all rows that has empty string or null', () => { - const configData: Sheet = { - columns: [ - { - field: 'city', - kind: ColumnKind.FIELD, - selected: true, - }, - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { - kind: ColumnKind.ORGANIZATION, - mapping: [ - { orgId: 272, value: 1 }, - { orgId: 273, value: 2 }, - ], - selected: true, - }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], - selected: true, - }, - ], - firstRowIsHeaders: false, - rows: [ - { - data: ['Linköping', '123', 1, ''], - }, - { - data: ['', '', 2, 'Backend'], - }, + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ + { + key: { + id: 123, + }, + op: 'person.get', + ops: [ { - data: ['Malmö', '125', null, 'Designer'], + data: { + email: 'clara@example.com', + ext_id: '9999', + }, + op: 'person.setfields', }, ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - city: 'Linköping', - ext_id: '123', - }, - op: 'person.import', - organizations: [272], - }, + }, + ]); + }); + + it('parses and sets date field', () => { + const sheet: Sheet = { + columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, { - op: 'person.import', - organizations: [273], - tags: [{ id: 124 }, { id: 100 }], + dateFormat: 'DD/MM YYYY', + field: 'extra_date', + kind: ColumnKind.DATE, + selected: true, }, + ], + firstRowIsHeaders: false, + rows: [ { - data: { - city: 'Malmö', - ext_id: '125', - }, - op: 'person.import', + data: ['123', '05/07 1857'], }, - ]); - }); + ], + title: 'My sheet', + }; - it('correctly assigns multiple columns of orgs', () => { - const configData: Sheet = { - columns: [ - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { - field: 'city', - kind: ColumnKind.FIELD, - selected: true, - }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], - selected: true, - }, - { - kind: ColumnKind.ORGANIZATION, - mapping: [ - { orgId: 272, value: 1 }, - { orgId: 273, value: 2 }, - ], - selected: true, - }, + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ + { + key: { + id: 123, + }, + op: 'person.get', + ops: [ { - kind: ColumnKind.ORGANIZATION, - mapping: [ - { orgId: 274, value: 3 }, - { orgId: 275, value: 4 }, - ], - selected: true, + data: { + extra_date: '1857-07-05', + }, + op: 'person.setfields', }, ], - firstRowIsHeaders: false, - rows: [ - { - data: ['123', 'Linköping', 'Frontend', 1, 3], - }, - { - data: ['124', 'Linköping', 'Backend', null, 4], - }, + }, + ]); + }); + + it('parses phone numbers and normalize their format', () => { + const sheet: Sheet = { + columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, + { field: 'phone', kind: ColumnKind.FIELD, selected: true }, + { field: 'alt_phone', kind: ColumnKind.FIELD, selected: true }, + ], + firstRowIsHeaders: false, + rows: [ + { + data: ['123', '070 123 45 67', '+46 704 123 123'], + }, + ], + title: 'My sheet', + }; + + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ + { + key: { + id: 123, + }, + op: 'person.get', + ops: [ { - data: ['125', 'Linköping', 'Backend', 3, 3], + data: { + alt_phone: '+46704123123', + phone: '+46701234567', + }, + op: 'person.setfields', }, ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ + }, + ]); + }); + + it('maps and sets gender field', () => { + const sheet: Sheet = { + columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, { - data: { - city: 'Linköping', - ext_id: '123', - }, - op: 'person.import', - organizations: [272, 274], - tags: [{ id: 123 }, { id: 100 }], + field: 'gender', + kind: ColumnKind.GENDER, + mapping: [ + { gender: 'f', value: 'woman' }, + { gender: 'm', value: 'man' }, + ], + selected: true, }, + ], + firstRowIsHeaders: false, + rows: [ { - data: { - city: 'Linköping', - ext_id: '124', - }, - op: 'person.import', - organizations: [275], - tags: [{ id: 124 }, { id: 100 }], + data: ['123', 'woman'], }, { - data: { - city: 'Linköping', - ext_id: '125', - }, - op: 'person.import', - organizations: [274], - tags: [{ id: 124 }, { id: 100 }], + data: ['125', 'man'], }, - ]); - }); - - it('correctly adds up multiple columns of tags', () => { - const configData: Sheet = { - columns: [ - { - field: 'city', - kind: ColumnKind.FIELD, - selected: true, - }, - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { - kind: ColumnKind.ORGANIZATION, - mapping: [ - { orgId: 272, value: 1 }, - { orgId: 273, value: 2 }, - ], - selected: true, - }, - { kind: ColumnKind.UNKNOWN, selected: false }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], + ], + title: 'My sheet', + }; - selected: true, - }, + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ + { + key: { + id: 123, + }, + op: 'person.get', + ops: [ { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 111 }, { id: 222 }], value: 'Cat' }, - { tags: [{ id: 333 }, { id: 444 }], value: 'Dog' }, - ], - - selected: true, + data: { + gender: 'f', + }, + op: 'person.setfields', }, ], - firstRowIsHeaders: false, - rows: [ - { - data: ['Linköping', '123', 1, 3, 'Frontend', 'Cat'], - }, + }, + { + key: { + id: 125, + }, + op: 'person.get', + ops: [ { - data: ['Linköping', '125', 2, 4, 'Backend', 'Dog'], + data: { + gender: 'm', + }, + op: 'person.setfields', }, ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ + }, + ]); + }); + + it('maps and sets enum field', () => { + const sheet: Sheet = { + columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, { - data: { - city: 'Linköping', - ext_id: '123', - }, - op: 'person.import', - organizations: [272], - tags: [{ id: 123 }, { id: 100 }, { id: 111 }, { id: 222 }], + field: 'extra_enum', + kind: ColumnKind.ENUM, + mapping: [ + { key: 'x_value', value: 'x' }, + { key: 'y_value', value: 'y' }, + ], + selected: true, + }, + ], + firstRowIsHeaders: false, + rows: [ + { + data: ['123', 'x'], }, { - data: { - city: 'Linköping', - ext_id: '125', - }, - op: 'person.import', - organizations: [273], - tags: [{ id: 124 }, { id: 100 }, { id: 333 }, { id: 444 }], + data: ['125', 'y'], }, - ]); - }); + ], + title: 'My sheet', + }; - it('removes leading/trailing spaces from email addresses', () => { - const configData: Sheet = { - columns: [ + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ + { + key: { + id: 123, + }, + op: 'person.get', + ops: [ { - field: 'email', - kind: ColumnKind.FIELD, - selected: true, + data: { + extra_enum: 'x_value', + }, + op: 'person.setfields', }, ], - firstRowIsHeaders: false, - rows: [ - { - data: [' clara@example.com '], - }, + }, + { + key: { + id: 125, + }, + op: 'person.get', + ops: [ { - data: ['zetkin@example.com'], + data: { + extra_enum: 'y_value', + }, + op: 'person.setfields', }, ], - title: '', - }; + }, + ]); + }); - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ + it('includes sub-ops to tag person with multiple tags from single mapping', () => { + const sheet: Sheet = { + columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, { - data: { - email: 'clara@example.com', - }, - op: 'person.import', + kind: ColumnKind.TAG, + mapping: [ + { + tags: [{ id: 11 }, { id: 22 }], + value: 'x', + }, + ], + selected: true, }, + ], + firstRowIsHeaders: false, + rows: [ { - data: { - email: 'zetkin@example.com', - }, - op: 'person.import', + data: ['123', 'x'], }, - ]); - }); + ], + title: 'My sheet', + }; - it('correctly de-duplicates multiple columns of orgs', () => { - const configData: Sheet = { - columns: [ - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { - field: 'city', - kind: ColumnKind.FIELD, - selected: true, - }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], - selected: true, - }, - { - kind: ColumnKind.ORGANIZATION, - mapping: [ - { orgId: 272, value: 1 }, - { orgId: 273, value: 2 }, - ], - selected: true, - }, - { - kind: ColumnKind.ORGANIZATION, - mapping: [ - { orgId: 272, value: 3 }, - { orgId: 275, value: 4 }, - ], - selected: true, - }, - ], - firstRowIsHeaders: false, - rows: [ - { - data: ['123', 'Linköping', 'Frontend', 1, 3], - }, + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ + { + key: { + id: 123, + }, + op: 'person.get', + ops: [ { - data: ['124', 'Linköping', 'Backend', null, 4], + op: 'person.tag', + tag_id: 11, }, { - data: ['125', 'Linköping', 'Backend', 3, 3], + op: 'person.tag', + tag_id: 22, }, ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ + }, + ]); + }); + + it('includes sub-ops to tag person with tags from multiple columns', () => { + const sheet: Sheet = { + columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, { - data: { - city: 'Linköping', - ext_id: '123', - }, - op: 'person.import', - organizations: [272], - tags: [{ id: 123 }, { id: 100 }], + kind: ColumnKind.TAG, + mapping: [ + { + tags: [{ id: 11 }], + value: 'x', + }, + ], + selected: true, }, { - data: { - city: 'Linköping', - ext_id: '124', - }, - op: 'person.import', - organizations: [275], - tags: [{ id: 124 }, { id: 100 }], + kind: ColumnKind.TAG, + mapping: [ + { + tags: [{ id: 22 }], + value: 'x', + }, + ], + selected: true, }, + ], + firstRowIsHeaders: false, + rows: [ { - data: { - city: 'Linköping', - ext_id: '125', - }, - op: 'person.import', - organizations: [272], - tags: [{ id: 124 }, { id: 100 }], + data: ['123', 'x', 'x'], }, - ]); - }); - it('remove duplicated tagIds', () => { - const configData: Sheet = { - columns: [ - { - field: 'city', - kind: ColumnKind.FIELD, - selected: true, - }, - { idField: 'ext_id', kind: ColumnKind.ID_FIELD, selected: true }, - { - kind: ColumnKind.ORGANIZATION, - mapping: [ - { orgId: 111, value: 1 }, - { orgId: 333, value: 2 }, - ], - selected: true, - }, - { kind: ColumnKind.UNKNOWN, selected: false }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 123 }, { id: 100 }], value: 'Frontend' }, - { tags: [{ id: 124 }, { id: 100 }], value: 'Backend' }, - ], - - selected: true, - }, - { - kind: ColumnKind.TAG, - mapping: [ - { tags: [{ id: 100 }, { id: 222 }], value: 'Cat' }, - { tags: [{ id: 333 }, { id: 444 }], value: 'Dog' }, - ], + ], + title: 'My sheet', + }; - selected: true, - }, - ], - firstRowIsHeaders: false, - rows: [ + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ + { + key: { + id: 123, + }, + op: 'person.get', + ops: [ { - data: ['Linköping', '123', 1, 3, 'Frontend', 'Cat'], + op: 'person.tag', + tag_id: 11, }, { - data: ['Linköping', '125', 2, 4, 'Backend', 'Dog'], + op: 'person.tag', + tag_id: 22, }, ], - title: 'My sheet', - }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - city: 'Linköping', - ext_id: '123', - }, - op: 'person.import', - organizations: [111], - tags: [{ id: 123 }, { id: 100 }, { id: 222 }], - }, - { - data: { - city: 'Linköping', - ext_id: '125', - }, - op: 'person.import', - organizations: [333], - tags: [{ id: 124 }, { id: 100 }, { id: 333 }, { id: 444 }], - }, - ]); - }); + }, + ]); }); - it('correctly parses dates into ISO date strings and ignores empty values', () => { - const configData: Sheet = { + it('ignores mismatching tag mappings', () => { + const sheet: Sheet = { columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, { - dateFormat: 'se', - field: 'birthday', - kind: ColumnKind.DATE, + kind: ColumnKind.TAG, + mapping: [ + { + tags: [{ id: 11 }], + value: 'x', + }, + ], selected: true, }, - { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, ], firstRowIsHeaders: false, rows: [ { - data: ['19650313-4571', 1], - }, - { - data: ['6408120923', 2], - }, - { - data: ['', 3], + data: ['123', 'this does not match x'], }, ], title: 'My sheet', }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - birthday: '1965-03-13', - id: 1, - }, - op: 'person.import', - }, - { - data: { - birthday: '1964-08-12', - id: 2, - }, - op: 'person.import', - }, + + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ { - data: { - id: 3, + key: { + id: 123, }, - op: 'person.import', + op: 'person.get', + ops: [], }, ]); }); - it('correctly parses and converts phone numbers with obvious formatting errors', () => { - const configData: Sheet = { + it('includes sub-ops to add person to org based on single mapping', () => { + const sheet: Sheet = { columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, { - field: 'phone', - kind: ColumnKind.FIELD, + kind: ColumnKind.ORGANIZATION, + mapping: [{ orgId: 101, value: 'x' }], selected: true, }, - { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, ], firstRowIsHeaders: false, rows: [ { - data: ['+46732789887', 1], - }, - { - data: ['46732789887', 2], - }, - { - data: ['0046732789887', 3], - }, - { - data: ['-460732789887', 4], + data: ['123', 'x'], }, - { - data: ['46732-78-98-87', 5], + ], + title: 'My sheet', + }; + + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ + { + key: { + id: 123, }, + op: 'person.get', + ops: [ + { + op: 'person.addtoorg', + org_id: 101, + }, + ], + }, + ]); + }); + + it('includes sub-ops to add person to org based on multiple mappings', () => { + const sheet: Sheet = { + columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, { - data: ['460732789887', 6], + kind: ColumnKind.ORGANIZATION, + mapping: [{ orgId: 101, value: 'x' }], + selected: true, }, { - data: ['0732789887', 7], + kind: ColumnKind.ORGANIZATION, + mapping: [{ orgId: 202, value: 'x' }], + selected: true, }, + ], + firstRowIsHeaders: false, + rows: [ { - // Phone number contains U202C, a Unicode control character, to validate that it is stripped. - data: ['+46 73278 98 87‬', 8], + data: ['123', 'x', 'x'], }, ], title: 'My sheet', }; - const result = prepareImportOperations(configData, countryCode); - expect(result).toEqual([ - { - data: { - id: 1, - phone: '+46732789887', - }, - op: 'person.import', - }, + + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ { - data: { - id: 2, - phone: '+46732789887', + key: { + id: 123, }, - op: 'person.import', + op: 'person.get', + ops: [ + { + op: 'person.addtoorg', + org_id: 101, + }, + { + op: 'person.addtoorg', + org_id: 202, + }, + ], }, - { - data: { - id: 3, - phone: '+46732789887', + ]); + }); + + it('does nothing when mapping maps value to null org', () => { + const sheet: Sheet = { + columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, + { + kind: ColumnKind.ORGANIZATION, + mapping: [{ orgId: null, value: 'x' }], + selected: true, }, - op: 'person.import', - }, - { - data: { - id: 4, - phone: '+46732789887', + ], + firstRowIsHeaders: false, + rows: [ + { + data: ['123', 'x'], }, - op: 'person.import', - }, + ], + title: 'My sheet', + }; + + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ { - data: { - id: 5, - phone: '+46732789887', + key: { + id: 123, }, - op: 'person.import', + op: 'person.get', + ops: [], }, - { - data: { - id: 6, - phone: '+46732789887', + ]); + }); + + it('ignores mismatching organiation mappings', () => { + const sheet: Sheet = { + columns: [ + { idField: 'id', kind: ColumnKind.ID_FIELD, selected: true }, + { + kind: ColumnKind.ORGANIZATION, + mapping: [{ orgId: 101, value: 'x' }], + selected: true, }, - op: 'person.import', - }, - { - data: { - id: 7, - phone: '+46732789887', + ], + firstRowIsHeaders: false, + rows: [ + { + data: ['123', 'this does not match x'], }, - op: 'person.import', - }, + ], + title: 'My sheet', + }; + + const ops = prepareImportOperations(sheet, countryCode); + expect(ops).toEqual([ { - data: { - id: 8, - phone: '+46732789887', + key: { + id: 123, }, - op: 'person.import', + op: 'person.get', + ops: [], }, ]); }); diff --git a/src/features/import/utils/prepareImportOperations.ts b/src/features/import/utils/prepareImportOperations.ts index 8b1165547..a3bee90d0 100644 --- a/src/features/import/utils/prepareImportOperations.ts +++ b/src/features/import/utils/prepareImportOperations.ts @@ -1,9 +1,11 @@ import { CountryCode, parsePhoneNumber } from 'libphonenumber-js'; -import getUniqueTags from './getUniqueTags'; import { CellData, ColumnKind, Sheet } from './types'; import parserFactory from './dateParsing/parserFactory'; +import { ZetkinPerson } from 'utils/types/zetkin'; +import { BulkOp, BulkSubOp } from '../types'; +// TODO: Get rid of this type and dependencies on it export type ZetkinPersonImportOp = { data?: Record; dateFormat?: string | null; //STÄMMER DETTA? @@ -13,156 +15,123 @@ export type ZetkinPersonImportOp = { }; export default function prepareImportOperations( - configuredSheet: Sheet, + sheet: Sheet, countryCode: CountryCode -): ZetkinPersonImportOp[] { - const personImportOps: ZetkinPersonImportOp[] = []; +): BulkOp[] { + const preparedOps: BulkOp[] = []; - configuredSheet.columns.forEach((column, colIdx) => { - if (column.selected) { - configuredSheet.rows.forEach((row, rowIdx) => { - if (configuredSheet.firstRowIsHeaders && rowIdx === 0) { - return; - } - - const rowIndex = configuredSheet.firstRowIsHeaders - ? rowIdx - 1 - : rowIdx; - - if (!personImportOps[rowIndex]) { - personImportOps.push({ - op: 'person.import', - }); - } - - if (column.kind === ColumnKind.ID_FIELD) { - const fieldKey = column.idField; - let value = row.data[colIdx]; - - if (value) { - if (fieldKey == 'ext_id') { - value = value.toString(); - } - - if (fieldKey == 'id') { - value = parseInt(value.toString()); - } + sheet.rows.forEach((row, index) => { + if (sheet.firstRowIsHeaders && index == 0) { + return; + } - personImportOps[rowIndex].data = { - ...personImportOps[rowIndex].data, - [`${fieldKey}`]: value, - }; + const subOps: BulkSubOp[] = []; + + let zetkinId: number | null = null; + let extId: string | null = null; + + const fields: Partial = {}; + sheet.columns.forEach((col, index) => { + if (col.selected) { + const value = row.data[index]; + + if (col.kind == ColumnKind.FIELD) { + const colIsPhoneField = + col.field == 'phone' || col.field == 'alt_phone'; + if (value && colIsPhoneField) { + const parsedPhoneNumber = parsePhoneNumber( + value.toString(), + countryCode + ); + fields[col.field] = parsedPhoneNumber.format('E.164'); + } else { + fields[col.field] = value; } - } - - if (column.kind === ColumnKind.FIELD) { - const fieldKey = column.field; - let value = row.data[colIdx]; - + } else if (col.kind == ColumnKind.ID_FIELD) { if (value) { - //Parse phone numbers to international format - if (fieldKey == 'phone' || fieldKey == 'alt_phone') { - const parsedPhoneNumber = parsePhoneNumber( - typeof value == 'string' ? value : value.toString(), - countryCode - ); - value = parsedPhoneNumber.format('E.164'); - } - - if (fieldKey == 'email') { - value = value.toString().trim(); - } - - //If they have uppecase letters we parse to lower - if (fieldKey == 'gender') { - value = value.toString().toLowerCase(); + if (col.idField == 'ext_id') { + extId = value.toString(); + } else if (col.idField == 'id') { + zetkinId = parseInt(value.toString()); } - - personImportOps[rowIndex].data = { - ...personImportOps[rowIndex].data, - [`${fieldKey}`]: value, - }; } - } - - if (column.kind === ColumnKind.TAG) { - column.mapping.forEach((mappedColumn) => { - if (mappedColumn.value === row.data[colIdx]) { - if (!personImportOps[rowIndex].tags) { - personImportOps[rowIndex].tags = []; - } - const allTags = - personImportOps[rowIndex].tags?.concat( - mappedColumn.tags.map((t) => ({ id: t.id })) - ) ?? []; - - personImportOps[rowIndex].tags = getUniqueTags(allTags); + } else if (col.kind == ColumnKind.DATE) { + if (col.dateFormat && value) { + const parser = parserFactory(col.dateFormat); + fields[col.field] = parser.parse(value.toString()); + } + } else if (col.kind == ColumnKind.GENDER) { + col.mapping.forEach((mapping) => { + if (mapping.value == value) { + fields.gender = mapping.gender; } }); - } - - if (column.kind === ColumnKind.ORGANIZATION) { - column.mapping.forEach((mappedColumn) => { - if (mappedColumn.value === row.data[colIdx] && mappedColumn.orgId) { - if (!personImportOps[rowIndex].organizations) { - personImportOps[rowIndex].organizations = []; - } - const allOrgs = - personImportOps[rowIndex]?.organizations?.concat( - mappedColumn.orgId - ) ?? []; - personImportOps[rowIndex].organizations = Array.from( - new Set(allOrgs) - ); + } else if (col.kind == ColumnKind.ENUM) { + col.mapping.forEach((mapping) => { + if (mapping.value == value) { + fields[col.field] = mapping.key; } }); - } - - if (column.kind === ColumnKind.ENUM) { - column.mapping.forEach((mappedColumn) => { - if ( - mappedColumn.key && - ((!mappedColumn.value && !row.data[colIdx]) || - mappedColumn.value === row.data[colIdx]) - ) { - personImportOps[rowIndex].data = { - ...personImportOps[rowIndex].data, - [`${column.field}`]: mappedColumn.key, - }; + } else if (col.kind == ColumnKind.TAG) { + col.mapping.forEach((mapping) => { + if (value == mapping.value) { + mapping.tags.forEach((tag) => { + subOps.push({ + op: 'person.tag', + tag_id: tag.id, + }); + }); + } + }); + } else if (col.kind == ColumnKind.ORGANIZATION) { + col.mapping.forEach((mapping) => { + if (mapping.value == value && mapping.orgId) { + subOps.push({ + op: 'person.addtoorg', + org_id: mapping.orgId, + }); } }); } + } + }); - if (column.kind === ColumnKind.DATE) { - if (column.dateFormat) { - const fieldKey = column.field; - let value = row.data[colIdx]; - - if (value) { - const parser = parserFactory(column.dateFormat); - value = parser.parse(value.toString()); + if (extId && zetkinId) { + fields.ext_id = extId; + extId = null; + } - personImportOps[rowIndex].data = { - ...personImportOps[rowIndex].data, - [`${fieldKey}`]: value, - }; - } - } - } + const hasFields = Object.keys(fields).length > 0; + if (hasFields) { + subOps.push({ + data: fields, + op: 'person.setfields', + }); + } - if (column.kind === ColumnKind.GENDER) { - const match = column.mapping.find( - (c) => c.value === row.data[colIdx] - ); - if (match !== undefined) { - personImportOps[rowIndex].data = { - ...personImportOps[rowIndex].data, - gender: match.gender as string, - }; - } - } + if (extId) { + preparedOps.push({ + key: { + ext_id: extId, + }, + op: 'person.get', + ops: subOps, + }); + } else if (zetkinId) { + preparedOps.push({ + key: { + id: zetkinId, + }, + op: 'person.get', + ops: subOps, + }); + } else { + preparedOps.push({ + op: 'person.create', + ops: subOps, }); } }); - return personImportOps; + + return preparedOps; } diff --git a/src/features/import/utils/types.ts b/src/features/import/utils/types.ts index 1b8664676..acd7743bf 100644 --- a/src/features/import/utils/types.ts +++ b/src/features/import/utils/types.ts @@ -1,5 +1,5 @@ -import { ZetkinPersonImportOp } from './prepareImportOperations'; import { Gender } from '../hooks/useGenderMapping'; +import { BulkOp } from '../types'; export type CellData = string | number | null | undefined; @@ -100,7 +100,7 @@ export type ConfigurableColumn = export type Column = UnknownColumn | FieldColumn | ConfigurableColumn; export interface ZetkinPersonImportPostBody { - ops: ZetkinPersonImportOp[]; + ops: BulkOp[]; } export type PersonImportSummary = {