{"version":3,"file":"uploadPrivacyRequestsFromCsv-BgjyAtG5.mjs","names":[],"sources":["../src/lib/requests/mapEnumValues.ts","../src/lib/requests/getUniqueValuesForColumn.ts","../src/lib/requests/filterRows.ts","../src/lib/requests/mapCsvColumnsToApi.ts","../src/lib/requests/mapRequestEnumValues.ts","../src/lib/requests/mapCsvRowsToRequestInputs.ts","../src/lib/requests/mapColumnsToIdentifiers.ts","../src/lib/requests/mapColumnsToAttributes.ts","../src/lib/requests/uploadPrivacyRequestsFromCsv.ts"],"sourcesContent":["import { apply, ObjByString } from '@transcend-io/type-utils';\nimport inquirer from 'inquirer';\nimport autoCompletePrompt from 'inquirer-autocomplete-prompt';\n\nimport { fuzzySearch } from './fuzzyMatchColumns.js';\n\n/**\n * Map a set of inputs to a set of outputs\n *\n * @param csvInputs - Input list\n * @param expectedOutputs - Output list\n * @param cache - Cache\n * @returns Mapping from row to enum value\n */\nexport async function mapEnumValues<TValue extends string>(\n  csvInputs: string[],\n  expectedOutputs: TValue[],\n  cache: { [k in string]: TValue },\n): Promise<{ [k in string]: TValue }> {\n  inquirer.registerPrompt('autocomplete', autoCompletePrompt);\n\n  const inputs = csvInputs.map((item) => item || '<blank>').filter((value) => !cache[value]);\n  if (inputs.length === 0) {\n    return cache;\n  }\n  const result = await inquirer.prompt<{ [k in string]: TValue }>(\n    inputs.map((value) => ({\n      name: value,\n      message: `Map value of: ${value}`,\n      type: 'autocomplete',\n      default: expectedOutputs.find((x) => fuzzySearch(value, x)),\n      source: (answersSoFar: ObjByString, input: string) =>\n        !input\n          ? expectedOutputs\n          : expectedOutputs.filter((x) => typeof x === 'string' && fuzzySearch(input, x)),\n    })),\n  );\n  return {\n    ...cache,\n    ...apply(result, (r) =>\n      typeof r === 'string' ? (r as TValue) : (Object.values(r)[0] as TValue),\n    ),\n  };\n}\n","import { ObjByString } from '@transcend-io/type-utils';\nimport { uniq } from 'lodash-es';\n\n/**\n * Return the unique set of values for a column in a CSV\n *\n * @param rows - Rows to look up\n * @param columnName - Name of column to grab values for\n * @returns Unique set of values in that column\n */\nexport function getUniqueValuesForColumn(rows: ObjByString[], columnName: string): string[] {\n  return uniq(rows.map((row) => row[columnName] || '').flat());\n}\n","import { ObjByString } from '@transcend-io/type-utils';\nimport colors from 'colors';\nimport inquirer from 'inquirer';\nimport { uniq } from 'lodash-es';\n\nimport { logger } from '../../logger.js';\nimport { NONE } from './constants.js';\nimport { getUniqueValuesForColumn } from './getUniqueValuesForColumn.js';\n\n/**\n * Filter a list of CSV rows by column values\n * Choose columns that contain metadata to filter the requests\n *\n * @param rows - Rows to filter\n * @returns Filtered rows\n */\nexport async function filterRows(rows: ObjByString[]): Promise<ObjByString[]> {\n  // Determine set of column names\n  const columnNames = uniq(rows.map((x) => Object.keys(x)).flat());\n\n  // update these variables recursively\n  let filteredRows = rows;\n  let keepFiltering = true;\n\n  // loop over\n  while (keepFiltering) {\n    // Prompt user for column to filter on\n\n    const { filterColumnName } = await inquirer.prompt<{\n      /** Name of column to filter on */\n      filterColumnName: string;\n    }>([\n      {\n        name: 'filterColumnName',\n        // eslint-disable-next-line max-len\n        message: `If you need to filter the list of requests to import, choose the column to filter on. Currently ${filteredRows.length} rows.`,\n        type: 'list',\n        default: columnNames,\n        choices: [NONE, ...columnNames],\n      },\n    ]);\n\n    // Determine if filtering should continue, or loop should be exited\n    keepFiltering = NONE !== filterColumnName;\n    if (keepFiltering) {\n      const options = getUniqueValuesForColumn(filteredRows, filterColumnName);\n\n      const { valuesToKeep } = await inquirer.prompt<{\n        /** Values to keep  */\n        valuesToKeep: string[];\n      }>([\n        {\n          name: 'valuesToKeep',\n          message: 'Keep rows matching this value',\n          type: 'checkbox',\n          default: columnNames,\n          choices: options,\n        },\n      ]);\n\n      filteredRows = filteredRows.filter((request) =>\n        valuesToKeep.includes(request[filterColumnName]),\n      );\n    }\n  }\n\n  logger.info(colors.magenta(`Importing ${filteredRows.length} requests`));\n  return filteredRows;\n}\n","import type { PersistedState } from '@transcend-io/persisted-state';\nimport { getValues, getEntries } from '@transcend-io/type-utils';\nimport inquirer from 'inquirer';\nimport { startCase } from 'lodash-es';\n\nimport { ColumnName, CachedFileState, IS_REQUIRED, CAN_APPLY_IN_BULK } from './constants.js';\nimport { fuzzyMatchColumns } from './fuzzyMatchColumns.js';\n\n/**\n * Mapping from column name to request input parameter\n */\nexport type ColumnNameMap = {\n  [k in ColumnName]?: string;\n};\n\n/**\n * Determine the mapping between columns in CSV\n *\n * @param columnNames - The set of column names\n * @param state - The cached file state used to map DSR inputs\n * @returns The column name mapping\n */\nexport async function mapCsvColumnsToApi(\n  columnNames: string[],\n  state: PersistedState<typeof CachedFileState>,\n): Promise<ColumnNameMap> {\n  // Determine the columns that should be mapped\n  const columnQuestions = getValues(ColumnName).filter(\n    (name) => !state.getValue('columnNames', name),\n  );\n\n  // Skip mapping when everything is mapped\n  const columnNameMap =\n    columnQuestions.length === 0\n      ? {}\n      : // prompt questions to map columns\n        await inquirer.prompt<{\n          [k in ColumnName]?: string;\n        }>(\n          columnQuestions.map((name) => {\n            const field = startCase(name.replace('ColumnName', ''));\n            const matches = fuzzyMatchColumns(\n              columnNames,\n              field,\n              IS_REQUIRED[name],\n              !!CAN_APPLY_IN_BULK[name],\n            );\n            return {\n              name,\n              message: `Choose the column that will be used to map in the field: ${field}`,\n              type: 'list',\n              default: matches[0],\n              choices: matches,\n            };\n          }),\n        );\n\n  await Promise.all(getEntries(columnNameMap).map(([k, v]) => state.setValue(v, 'columnNames', k)));\n  return columnNameMap;\n}\n","import { LOCALE_KEY, type LocaleValue } from '@transcend-io/internationalization';\nimport type { PersistedState } from '@transcend-io/persisted-state';\nimport {\n  CompletedRequestStatus,\n  RequestAction,\n  IsoCountryCode,\n  IsoCountrySubdivisionCode,\n} from '@transcend-io/privacy-types';\nimport { makeGraphQLRequest, DATA_SUBJECTS, type DataSubject } from '@transcend-io/sdk';\nimport { ObjByString } from '@transcend-io/type-utils';\nimport colors from 'colors';\nimport { GraphQLClient } from 'graphql-request';\n\nimport { logger } from '../../logger.js';\nimport { CachedFileState, NONE, ColumnName } from './constants.js';\nimport { getUniqueValuesForColumn } from './getUniqueValuesForColumn.js';\nimport { ColumnNameMap } from './mapCsvColumnsToApi.js';\nimport { mapEnumValues } from './mapEnumValues.js';\n\n/**\n * Map the values in a CSV to the enum values in Transcend\n *\n * @param client - GraphQL client\n * @param requests - Set of privacy requests\n * @param options - Options\n */\nexport async function mapRequestEnumValues(\n  client: GraphQLClient,\n  requests: ObjByString[],\n  {\n    state,\n    columnNameMap,\n  }: {\n    /** State value to write cache to */\n    state: PersistedState<typeof CachedFileState>;\n    /** Mapping of column names */\n    columnNameMap: ColumnNameMap;\n  },\n): Promise<void> {\n  // Get mapped value\n  const getMappedName = (attribute: ColumnName): string =>\n    state.getValue('columnNames', attribute) || columnNameMap[attribute]!;\n\n  // Fetch all data subjects in the organization\n  const { internalSubjects } = await makeGraphQLRequest<{\n    /** Query response */\n    internalSubjects: DataSubject[];\n  }>(client, DATA_SUBJECTS, { logger });\n\n  // Map RequestAction\n  logger.info(colors.magenta('Determining mapping of columns for request action'));\n  const requestTypeToRequestAction: { [k in string]: RequestAction } = await mapEnumValues(\n    getUniqueValuesForColumn(requests, getMappedName(ColumnName.RequestType)),\n    Object.values(RequestAction),\n    state.getValue('requestTypeToRequestAction'),\n  );\n  await state.setValue(requestTypeToRequestAction, 'requestTypeToRequestAction');\n\n  // Map data subject type\n  logger.info(colors.magenta('Determining mapping of columns for subject'));\n  const subjectTypeToSubjectName: { [k in string]: string } = await mapEnumValues(\n    getUniqueValuesForColumn(requests, getMappedName(ColumnName.SubjectType)),\n    internalSubjects.map(({ type }) => type),\n    state.getValue('subjectTypeToSubjectName'),\n  );\n  await state.setValue(subjectTypeToSubjectName, 'subjectTypeToSubjectName');\n\n  // Map locale\n  logger.info(colors.magenta('Determining mapping of columns for locale'));\n  const languageToLocale: { [k in string]: LocaleValue } = await mapEnumValues(\n    getUniqueValuesForColumn(requests, getMappedName(ColumnName.Locale)),\n    Object.values(LOCALE_KEY),\n    state.getValue('languageToLocale'),\n  );\n  await state.setValue(languageToLocale, 'languageToLocale');\n  logger.info(colors.magenta('Determining mapping of columns for request status'));\n\n  // Map request status\n  logger.info(colors.magenta('Determining mapping of columns for request status'));\n  const requestStatusColumn = getMappedName(ColumnName.RequestStatus);\n  const statusToRequestStatus: {\n    [k in string]: CompletedRequestStatus | typeof NONE;\n  } =\n    requestStatusColumn === NONE\n      ? {}\n      : await mapEnumValues(\n          getUniqueValuesForColumn(requests, requestStatusColumn),\n          [...Object.values(CompletedRequestStatus), NONE],\n          state.getValue('statusToRequestStatus'),\n        );\n  await state.setValue(statusToRequestStatus, 'statusToRequestStatus');\n\n  // Map country\n  logger.info(colors.magenta('Determining mapping of columns for country'));\n  const countryColumn = getMappedName(ColumnName.Country);\n  const regionToCountry: {\n    [k in string]: IsoCountryCode | typeof NONE;\n  } =\n    countryColumn === NONE\n      ? {}\n      : await mapEnumValues(\n          getUniqueValuesForColumn(requests, countryColumn),\n          [...Object.values(IsoCountryCode), NONE],\n          state.getValue('regionToCountry'),\n        );\n  await state.setValue(regionToCountry, 'regionToCountry');\n\n  // Map country sub division\n  logger.info(colors.magenta('Determining mapping of columns for country sub division'));\n  const countrySubDivisionColumn = getMappedName(ColumnName.CountrySubDivision);\n  const regionToCountrySubDivision: {\n    [k in string]: IsoCountrySubdivisionCode | typeof NONE;\n  } =\n    countrySubDivisionColumn === NONE\n      ? {}\n      : await mapEnumValues(\n          getUniqueValuesForColumn(requests, countrySubDivisionColumn),\n          [...Object.values(IsoCountrySubdivisionCode), NONE],\n          state.getValue('regionToCountrySubDivision'),\n        );\n  await state.setValue(regionToCountrySubDivision, 'regionToCountrySubDivision');\n}\n","import { LOCALE_KEY } from '@transcend-io/internationalization';\nimport type { PersistedState } from '@transcend-io/persisted-state';\nimport {\n  NORMALIZE_PHONE_NUMBER,\n  CompletedRequestStatus,\n  RequestAction,\n  IdentifierType,\n  IsoCountryCode,\n  IsoCountrySubdivisionCode,\n} from '@transcend-io/privacy-types';\nimport type { AttributeKey } from '@transcend-io/sdk';\nimport { ObjByString, valuesOf } from '@transcend-io/type-utils';\nimport { splitCsvToList } from '@transcend-io/utils';\nimport * as t from 'io-ts';\nimport { DateFromISOString } from 'io-ts-types';\n\nimport { CachedFileState, BLANK, BULK_APPLY, ColumnName, NONE } from './constants.js';\nimport { AttributeNameMap } from './mapColumnsToAttributes.js';\nimport { IdentifierNameMap } from './mapColumnsToIdentifiers.js';\nimport { ColumnNameMap } from './mapCsvColumnsToApi.js';\nimport { ParsedAttributeInput } from './parseAttributesFromString.js';\n\n/**\n * Shape of additional identifiers\n *\n * key of object is IdentifierType\n */\nexport const AttestedExtraIdentifiers = t.record(\n  t.string,\n  t.array(\n    t.intersection([\n      t.type({\n        /** Value of identifier */\n        value: t.string,\n      }),\n      t.partial({\n        /** Name of identifier - option for non-custom identifier types */\n        name: t.string,\n      }),\n    ]),\n  ),\n);\n\n/** Type override */\nexport type AttestedExtraIdentifiers = t.TypeOf<typeof AttestedExtraIdentifiers>;\n\nexport const PrivacyRequestInput = t.intersection([\n  t.type({\n    /** Email of user */\n    email: t.string,\n    /** Extra identifiers */\n    attestedExtraIdentifiers: AttestedExtraIdentifiers,\n    /** Core identifier for user */\n    coreIdentifier: t.string,\n    /** Action type being submitted  */\n    requestType: valuesOf(RequestAction),\n    /** Type of data subject */\n    subjectType: t.string,\n  }),\n  t.partial({\n    /** Country */\n    country: valuesOf(IsoCountryCode),\n    /** Country sub division */\n    countrySubDivision: valuesOf(IsoCountrySubdivisionCode),\n    /** Attribute inputs */\n    attributes: t.array(ParsedAttributeInput),\n    /** The status that the request should be created as */\n    status: valuesOf(CompletedRequestStatus),\n    /** The time that the request was created */\n    createdAt: DateFromISOString,\n    /** Data silo IDs to submit for */\n    dataSiloIds: t.array(t.string),\n    /** Language key to map to */\n    locale: valuesOf(LOCALE_KEY),\n  }),\n]);\n\n/** Type override */\nexport type PrivacyRequestInput = t.TypeOf<typeof PrivacyRequestInput>;\n\n/**\n * Transform the identifier value based on type\n *\n * @param identifierValue - Value of identifier\n * @param identifierType - Type of identifier\n * @param defaultPhoneCountryCode - Default country code for phone numbers\n * @returns Post-processed identifier\n */\nexport function normalizeIdentifierValue(\n  identifierValue: string,\n  identifierType: IdentifierType,\n  defaultPhoneCountryCode: string,\n): string {\n  // Lowercase email\n  if (identifierType === IdentifierType.Email) {\n    return identifierValue.toLowerCase();\n  }\n\n  // Normalize phone number\n  if (identifierType === IdentifierType.Phone) {\n    const normalized = identifierValue\n      .replace(NORMALIZE_PHONE_NUMBER, '')\n      .replace(/[()]/g, '')\n      .replace(/[–]/g, '')\n      .replace(/[:]/g, '')\n      .replace(/[‭‬]/g, '')\n      .replace(/[A-Za-z]/g, '');\n    return !normalized\n      ? ''\n      : normalized.startsWith('+')\n        ? normalized\n        : `+${defaultPhoneCountryCode}${normalized}`;\n  }\n  return identifierValue;\n}\n\n/**\n * Take the raw rows in a CSV upload, and map those rows to the request\n * input shape that can be passed to the Transcend API to submit a privacy\n * request.\n *\n * @param requestInputs - CSV of requests to be uploaded\n * @param state - The cached set of mapping values\n * @param options - Options\n * @returns [raw input, request input] list\n */\nexport function mapCsvRowsToRequestInputs(\n  requestInputs: ObjByString[],\n  state: PersistedState<typeof CachedFileState>,\n  {\n    columnNameMap,\n    identifierNameMap,\n    attributeNameMap,\n    requestAttributeKeys,\n    defaultPhoneCountryCode = '1', // US\n  }: {\n    /** Default country code */\n    defaultPhoneCountryCode?: string;\n    /** Mapping of column names */\n    columnNameMap: ColumnNameMap;\n    /** Mapping of identifier names */\n    identifierNameMap: IdentifierNameMap;\n    /** Mapping of attribute names */\n    attributeNameMap: AttributeNameMap;\n    /** Request attribute keys */\n    requestAttributeKeys: AttributeKey[];\n  },\n): [Record<string, string>, PrivacyRequestInput][] {\n  // map the CSV to request input\n  const getMappedName = (attribute: ColumnName): string =>\n    state.getValue('columnNames', attribute) || columnNameMap[attribute]!;\n  return requestInputs.map((input): [Record<string, string>, PrivacyRequestInput] => {\n    // The extra identifiers to upload for this request\n    const attestedExtraIdentifiers: AttestedExtraIdentifiers = {};\n    Object.entries(identifierNameMap)\n      // filter out skipped identifiers\n      .filter(([, columnName]) => columnName !== NONE)\n      .forEach(([identifierName, columnName]) => {\n        // Determine the identifier type being specified\n        const identifierType = Object.values(IdentifierType).includes(\n          identifierName as any, // eslint-disable-line @typescript-eslint/no-explicit-any\n        )\n          ? (identifierName as IdentifierType)\n          : IdentifierType.Custom;\n\n        // Only add the identifier if the value exists\n        const identifierValue = input[columnName];\n        if (identifierValue) {\n          const normalized = normalizeIdentifierValue(\n            identifierValue,\n            identifierType,\n            defaultPhoneCountryCode,\n          );\n          if (normalized) {\n            // Initialize\n            if (!attestedExtraIdentifiers[identifierType]) {\n              attestedExtraIdentifiers[identifierType] = [];\n            }\n\n            // Add the identifier\n            attestedExtraIdentifiers[identifierType]!.push({\n              value: normalized,\n              name: identifierName,\n            });\n          }\n        }\n      });\n\n    // The extra attributes to upload for this request\n    const attributes: ParsedAttributeInput[] = [];\n    Object.entries(attributeNameMap)\n      // filter out skipped attributes\n      .filter(([, columnName]) => columnName !== NONE)\n      .forEach(([attributeName, columnName]) => {\n        // Only add the identifier if the value exists\n        const attributeValueString = input[columnName];\n        if (attributeValueString) {\n          // Add the attribute\n          const isMulti =\n            requestAttributeKeys.find((attr) => attr.name === attributeName)?.type ===\n            'MULTI_SELECT';\n          attributes.push({\n            values: isMulti ? splitCsvToList(attributeValueString) : attributeValueString,\n            key: attributeName,\n          });\n        }\n      });\n\n    const requestTypeColumn = getMappedName(ColumnName.RequestType);\n    const dataSubjectTypeColumn = getMappedName(ColumnName.SubjectType);\n    return [\n      input,\n      {\n        email: input[getMappedName(ColumnName.Email)],\n        attestedExtraIdentifiers,\n        attributes,\n        coreIdentifier: input[getMappedName(ColumnName.CoreIdentifier)],\n        requestType:\n          requestTypeColumn === BULK_APPLY\n            ? state.getValue('requestTypeToRequestAction', BLANK)\n            : state.getValue('requestTypeToRequestAction', input[requestTypeColumn]),\n        subjectType:\n          dataSubjectTypeColumn === BULK_APPLY\n            ? state.getValue('subjectTypeToSubjectName', BLANK)\n            : state.getValue('subjectTypeToSubjectName', input[dataSubjectTypeColumn]),\n        ...(getMappedName(ColumnName.Locale) !== NONE && input[getMappedName(ColumnName.Locale)]\n          ? {\n              locale: state.getValue('languageToLocale', input[getMappedName(ColumnName.Locale)]),\n            }\n          : {}),\n        ...(getMappedName(ColumnName.Country) !== NONE && input[getMappedName(ColumnName.Country)]\n          ? {\n              country: state.getValue(\n                'regionToCountry',\n                input[getMappedName(ColumnName.Country)],\n              ) as IsoCountryCode,\n            }\n          : {}),\n        ...(getMappedName(ColumnName.CountrySubDivision) !== NONE &&\n        input[getMappedName(ColumnName.CountrySubDivision)]\n          ? {\n              countrySubDivision: state.getValue(\n                'regionToCountrySubDivision',\n                input[getMappedName(ColumnName.CountrySubDivision)],\n              ) as IsoCountrySubdivisionCode,\n            }\n          : {}),\n        ...(getMappedName(ColumnName.RequestStatus) !== NONE &&\n        state.getValue('statusToRequestStatus', input[getMappedName(ColumnName.RequestStatus)]) !==\n          NONE &&\n        input[getMappedName(ColumnName.RequestStatus)]\n          ? {\n              status: state.getValue(\n                'statusToRequestStatus',\n                input[getMappedName(ColumnName.RequestStatus)],\n              ) as CompletedRequestStatus,\n            }\n          : {}),\n        ...(getMappedName(ColumnName.CreatedAt) !== NONE &&\n        input[getMappedName(ColumnName.CreatedAt)]\n          ? {\n              createdAt: new Date(input[getMappedName(ColumnName.CreatedAt)]),\n            }\n          : {}),\n        ...(getMappedName(ColumnName.DataSiloIds) !== NONE &&\n        input[getMappedName(ColumnName.DataSiloIds)]\n          ? {\n              dataSiloIds: splitCsvToList(input[getMappedName(ColumnName.DataSiloIds)]),\n            }\n          : {}),\n      },\n    ];\n  });\n}\n","import type { PersistedState } from '@transcend-io/persisted-state';\nimport { INITIALIZER, type Initializer, makeGraphQLRequest } from '@transcend-io/sdk';\nimport type { GraphQLClient } from 'graphql-request';\nimport inquirer from 'inquirer';\n\nimport { logger } from '../../logger.js';\nimport { CachedFileState, IDENTIFIER_BLOCK_LIST } from './constants.js';\nimport { fuzzyMatchColumns } from './fuzzyMatchColumns.js';\n\n/**\n * Mapping from identifier name to request input parameter\n */\nexport type IdentifierNameMap = {\n  [k in string]: string;\n};\n\n/**\n * Create a mapping from the identifier names that can be included\n * at request submission, to the names of the columns that map to those\n * identifiers.\n *\n * @param client - GraphQL client\n * @param columnNames - The set of all column names\n * @param state - Cached state of this mapping\n * @returns Mapping from identifier name to column name\n */\nexport async function mapColumnsToIdentifiers(\n  client: GraphQLClient,\n  columnNames: string[],\n  state: PersistedState<typeof CachedFileState>,\n): Promise<IdentifierNameMap> {\n  // Grab the initializer\n  const { initializer } = await makeGraphQLRequest<{\n    /** Query response */\n    initializer: Initializer;\n  }>(client, INITIALIZER, { logger });\n\n  // Determine the columns that should be mapped\n  const columnQuestions = initializer.identifiers.filter(\n    ({ name }) => !state.getValue('identifierNames', name) && !IDENTIFIER_BLOCK_LIST.includes(name),\n  );\n\n  // Skip mapping when everything is mapped\n  const identifierNameMap =\n    columnQuestions.length === 0\n      ? {}\n      : // prompt questions to map columns\n        await inquirer.prompt<{\n          [k in string]: string;\n        }>(\n          columnQuestions.map(({ name }) => {\n            const matches = fuzzyMatchColumns(columnNames, name, false);\n            return {\n              name,\n              message: `Choose the column that will be used to map in the identifier: ${name}`,\n              type: 'list',\n              default: matches[0],\n              choices: matches,\n            };\n          }),\n        );\n  await Promise.all(\n    Object.entries(identifierNameMap).map(([k, v]) => state.setValue(v, 'identifierNames', k)),\n  );\n\n  return {\n    ...state.getValue('identifierNames'),\n    ...identifierNameMap,\n  };\n}\n","import type { PersistedState } from '@transcend-io/persisted-state';\nimport type { AttributeKey } from '@transcend-io/sdk';\nimport type { GraphQLClient } from 'graphql-request';\nimport inquirer from 'inquirer';\n\nimport { CachedFileState } from './constants.js';\nimport { fuzzyMatchColumns } from './fuzzyMatchColumns.js';\n\n/**\n * Mapping from attribute name to request input parameter\n */\nexport type AttributeNameMap = {\n  [k in string]: string;\n};\n\n/**\n * Create a mapping from the attributes names that can be included\n * at request submission, to the names of the columns that map to those\n * attributes.\n *\n * @param client - GraphQL client\n * @param columnNames - The set of all column names\n * @param state - Cached state of this mapping\n * @param requestAttributeKeys - Attribute keys to map\n * @returns Mapping from attributes name to column name\n */\nexport async function mapColumnsToAttributes(\n  client: GraphQLClient,\n  columnNames: string[],\n  state: PersistedState<typeof CachedFileState>,\n  requestAttributeKeys: AttributeKey[],\n): Promise<AttributeNameMap> {\n  // Determine the columns that should be mapped\n  const columnQuestions = requestAttributeKeys.filter(\n    ({ name }) => !state.getValue('attributeNames', name),\n  );\n\n  // Skip mapping when everything is mapped\n  const attributeNameMap =\n    columnQuestions.length === 0\n      ? {}\n      : // prompt questions to map columns\n        await inquirer.prompt<{\n          [k in string]: string;\n        }>(\n          columnQuestions.map(({ name }) => {\n            const matches = fuzzyMatchColumns(columnNames, name, false);\n            return {\n              name,\n              message: `Choose the column that will be used to map in the attribute: ${name}`,\n              type: 'list',\n              default: matches[0],\n              choices: matches,\n            };\n          }),\n        );\n  await Promise.all(\n    Object.entries(attributeNameMap).map(([k, v]) => state.setValue(v, 'attributeNames', k)),\n  );\n\n  return {\n    ...state.getValue('attributeNames'),\n    ...attributeNameMap,\n  };\n}\n","import { join } from 'node:path';\n\nimport { PersistedState } from '@transcend-io/persisted-state';\nimport {\n  buildTranscendGraphQLClient,\n  createSombraGotInstance,\n  fetchAllRequestAttributeKeys,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\n/* eslint-disable max-lines */\nimport colors from 'colors';\nimport * as t from 'io-ts';\nimport { uniq } from 'lodash-es';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { CachedRequestState, CachedFileState } from './constants.js';\nimport { extractClientError } from './extractClientError.js';\nimport { filterRows } from './filterRows.js';\nimport { mapColumnsToAttributes } from './mapColumnsToAttributes.js';\nimport { mapColumnsToIdentifiers } from './mapColumnsToIdentifiers.js';\nimport { mapCsvColumnsToApi } from './mapCsvColumnsToApi.js';\nimport { mapCsvRowsToRequestInputs } from './mapCsvRowsToRequestInputs.js';\nimport { mapRequestEnumValues } from './mapRequestEnumValues.js';\nimport { parseAttributesFromString } from './parseAttributesFromString.js';\nimport { readCsv } from './readCsv.js';\nimport { submitPrivacyRequest } from './submitPrivacyRequest.js';\n\n/**\n * Upload a set of privacy requests from CSV\n *\n * @param options - Options\n */\nexport async function uploadPrivacyRequestsFromCsv({\n  cacheFilepath,\n  requestReceiptFolder,\n  file,\n  auth,\n  sombraAuth,\n  concurrency = 100,\n  defaultPhoneCountryCode = '1', // USA\n  transcendUrl = DEFAULT_TRANSCEND_API,\n  attributes = [],\n  emailIsVerified = true,\n  skipFilterStep = false,\n  skipSendingReceipt = true,\n  isTest = false,\n  isSilent = true,\n  debug = false,\n  dryRun = false,\n}: {\n  /** File to cache metadata about mapping of CSV shape to script */\n  cacheFilepath: string;\n  /** File where request receipts are stored */\n  requestReceiptFolder: string;\n  /** CSV file path */\n  file: string;\n  /** Transcend API key authentication */\n  auth: string;\n  /** Default country code for phone numbers */\n  defaultPhoneCountryCode?: string;\n  /** Concurrency to upload in */\n  concurrency?: number;\n  /** API URL for Transcend backend */\n  transcendUrl?: string;\n  /** Sombra API key authentication */\n  sombraAuth?: string;\n  /** Include debug logs */\n  debug?: boolean;\n  /** Skip the step where requests are filtered */\n  skipFilterStep?: boolean;\n  /** Whether test requests are being uploaded */\n  isTest?: boolean;\n  /** Whether requests are uploaded in silent mode */\n  isSilent?: boolean;\n  /** Whether to send the email receipt */\n  skipSendingReceipt?: boolean;\n  /** Whether the email was verified up front */\n  emailIsVerified?: boolean;\n  /** Attributes string pre-parse */\n  attributes?: string[];\n  /** Whether a dry run is happening */\n  dryRun?: boolean;\n}): Promise<void> {\n  // Time duration\n  const t0 = new Date().getTime();\n  // create a new progress bar instance and use shades_classic theme\n  const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n  // Parse out the extra attributes to apply to all requests uploaded\n  const parsedAttributes = parseAttributesFromString(attributes);\n\n  // Create a new state to persist the metadata that\n  // maps the request inputs to the Transcend API shape\n  const state = new PersistedState(cacheFilepath, CachedFileState, {\n    columnNames: {},\n    requestTypeToRequestAction: {},\n    subjectTypeToSubjectName: {},\n    languageToLocale: {},\n    statusToRequestStatus: {},\n    identifierNames: {},\n    attributeNames: {},\n    regionToCountrySubDivision: {},\n    regionToCountry: {},\n  });\n\n  // Create a new state file to store the requests from this run.\n  // `toISOString()` contains colons (e.g. 2026-07-06T04:33:12.345Z) which are\n  // illegal characters in Windows filenames, so strip them out to keep the\n  // auto-generated receipt filename cross-platform.\n  const requestCacheFile = join(\n    requestReceiptFolder,\n    `tr-request-upload-${new Date()\n      .toISOString()\n      .replace(/:/g, '-')}-${file.split('/').pop()}`.replace('.csv', '.json'),\n  );\n  const requestState = new PersistedState(requestCacheFile, CachedRequestState, {\n    successfulRequests: [],\n    duplicateRequests: [],\n    failingRequests: [],\n  });\n\n  // Create sombra instance to communicate with\n  const sombra = await createSombraGotInstance(transcendUrl, auth, {\n    logger,\n    sombraApiKey: sombraAuth,\n    sombraUrl: process.env.SOMBRA_URL,\n  });\n\n  // Read in the list of integration requests\n  const requestsList = readCsv(file, t.record(t.string, t.string));\n  const columnNames = uniq(requestsList.map((x) => Object.keys(x)).flat());\n\n  // Log out an example request\n  if (requestsList.length === 0) {\n    throw new Error(\n      'No Requests found in list! Ensure the first row of the CSV is a header and the rest are requests.',\n    );\n  }\n  if (debug) {\n    const firstRequest = requestsList[0];\n    logger.info(colors.magenta(`First request: ${JSON.stringify(firstRequest, null, 2)}`));\n  }\n  // Determine what rows in the CSV should be imported\n  // Choose columns that contain metadata to filter the requests\n  const filteredRequestList = skipFilterStep ? requestsList : await filterRows(requestsList);\n\n  // Build a GraphQL client\n  const client = buildTranscendGraphQLClient(transcendUrl, auth);\n  // Grab the request attributes\n  const requestAttributeKeys = await fetchAllRequestAttributeKeys(client, { logger });\n  // Determine the columns that should be mapped\n  const columnNameMap = await mapCsvColumnsToApi(columnNames, state);\n  const identifierNameMap = await mapColumnsToIdentifiers(client, columnNames, state);\n  const attributeNameMap = await mapColumnsToAttributes(\n    client,\n    columnNames,\n    state,\n    requestAttributeKeys,\n  );\n  await mapRequestEnumValues(client, filteredRequestList, {\n    state,\n    columnNameMap,\n  });\n\n  // map the CSV to request input\n  const requestInputs = mapCsvRowsToRequestInputs(filteredRequestList, state, {\n    defaultPhoneCountryCode,\n    columnNameMap,\n    identifierNameMap,\n    attributeNameMap,\n    requestAttributeKeys,\n  });\n\n  // start the progress bar with a total value of 200 and start value of 0\n  if (!debug) {\n    progressBar.start(requestInputs.length, 0);\n  }\n  let total = 0;\n  // Submit each request\n  await map(\n    requestInputs,\n    async ([rawRow, requestInput], ind) => {\n      // The identifier to log, only include personal data if debug mode is on\n      const requestLogId = debug\n        ? `email:${requestInput.email} | coreIdentifier:${requestInput.coreIdentifier}`\n        : `row:${ind.toString()}`;\n\n      if (debug) {\n        logger.info(\n          colors.magenta(\n            `[${ind + 1}/${requestInputs.length}] Importing: ${JSON.stringify(\n              requestInput,\n              null,\n              2,\n            )}`,\n          ),\n        );\n      }\n\n      // Skip on dry run\n      if (dryRun) {\n        logger.info(colors.magenta('Bailing out on dry run because dryRun is set'));\n        return;\n      }\n\n      try {\n        // Make the GraphQL request to submit the privacy request\n        const requestResponse = await submitPrivacyRequest(sombra, requestInput, {\n          details: `Uploaded by Transcend Cli: \"tr-request-upload\" : ${JSON.stringify(\n            rawRow,\n            null,\n            2,\n          )}`,\n          isTest,\n          emailIsVerified,\n          skipSendingReceipt,\n          isSilent,\n          additionalAttributes: parsedAttributes,\n        });\n\n        // Log success\n        if (debug) {\n          logger.info(\n            colors.green(\n              `[${ind + 1}/${\n                requestInputs.length\n              }] Successfully submitted the test data subject request: \"${requestLogId}\"`,\n            ),\n          );\n          logger.info(\n            colors.green(\n              `[${ind + 1}/${requestInputs.length}] View it at: \"${requestResponse.link}\"`,\n            ),\n          );\n        }\n\n        // Cache successful upload\n        const successfulRequests = requestState.getValue('successfulRequests');\n        successfulRequests.push({\n          id: requestResponse.id,\n          link: requestResponse.link,\n          rowIndex: ind,\n          coreIdentifier: requestResponse.coreIdentifier,\n          attemptedAt: new Date().toISOString(),\n        });\n        await requestState.setValue(successfulRequests, 'successfulRequests');\n      } catch (err) {\n        const msg = `${err.message} - ${JSON.stringify(err.response?.body, null, 2)}`;\n        const clientError = extractClientError(msg);\n\n        if (clientError === 'Client error: You have already made this request.') {\n          if (debug) {\n            logger.info(\n              colors.yellow(\n                `[${ind + 1}/${requestInputs.length}] Skipping request as it is a duplicate`,\n              ),\n            );\n          }\n          const duplicateRequests = requestState.getValue('duplicateRequests');\n          duplicateRequests.push({\n            coreIdentifier: requestInput.coreIdentifier,\n            rowIndex: ind,\n            attemptedAt: new Date().toISOString(),\n          });\n          await requestState.setValue(duplicateRequests, 'duplicateRequests');\n        } else {\n          const failingRequests = requestState.getValue('failingRequests');\n          failingRequests.push({\n            ...requestInput,\n            rowIndex: ind,\n            error: clientError || msg,\n            attemptedAt: new Date().toISOString(),\n          });\n          await requestState.setValue(failingRequests, 'failingRequests');\n          if (debug) {\n            logger.error(colors.red(clientError || msg));\n            logger.error(\n              colors.red(\n                `[${ind + 1}/${\n                  requestInputs.length\n                }] Failed to submit request for: \"${requestLogId}\"`,\n              ),\n            );\n          }\n        }\n      }\n\n      total += 1;\n      if (!debug) {\n        progressBar.update(total);\n      }\n    },\n    {\n      concurrency,\n    },\n  );\n\n  progressBar.stop();\n  const t1 = new Date().getTime();\n  const totalTime = t1 - t0;\n\n  // Log completion time\n  logger.info(colors.green(`Completed upload in \"${totalTime / 1000}\" seconds.`));\n\n  // Log duplicates\n  if (requestState.getValue('duplicateRequests').length > 0) {\n    logger.info(\n      colors.yellow(\n        `Encountered \"${requestState.getValue('duplicateRequests').length}\" duplicate requests. ` +\n          `See \"${requestCacheFile}\" to review the core identifiers for these requests.`,\n      ),\n    );\n  }\n\n  // Log errors\n  if (requestState.getValue('failingRequests').length > 0) {\n    logger.error(\n      colors.red(\n        `Encountered \"${requestState.getValue('failingRequests').length}\" errors. ` +\n          `See \"${requestCacheFile}\" to review the error messages and inputs.`,\n      ),\n    );\n    process.exit(1);\n  }\n}\n/* eslint-enable max-lines */\n"],"mappings":"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"}