UI and Typescript bug with with `coda.ParameterType.NumberArray`

Hello, I’ve noticed that when using coda.ParameterType.NumberArray as type for a parameter with autocomplete, the sync table UI doesn’t allow multiple choices as it should, but act as a single choice list. coda.ParameterType.StringArray with a similar autocomplete function works fine.

More over, given this parameter definition :

coda.makeParameter({
  type: coda.ParameterType.NumberArray,
  name: 'locations',
  description: 'Filter results by locations.',
  autocomplete: async function (context, search, args) {
    const params = cleanQueryParams({
      limit: REST_DEFAULT_LIMIT,
      fields: ['id', 'title'].join(','),
    });
    let url = coda.withQueryParams(`${context.endpoint}/admin/api/${REST_DEFAULT_API_VERSION}/locations.json`, params);
    const response = await makeGetRequest({ url, cacheTtlSecs: 0 }, context);
    return coda.autocompleteSearchObjects(search, response.body.locations, 'title', 'id');
  }
})

typescript types it as never instead of number[]
Capture d’écran 2024-02-09 à 13.02.22

Am I the only one ?

Hi @Martin_Portevin - Thanks for raising this issue. I noticed the single-select behavior myself just the other day, and this is a good prompt to file that bug with the team. I couldn’t reproduce the never behavior though, do you think you could craft a minimal code example to reproduces it?

One of the downsides with your approach is that although Coda will display the title when the user is in the autocomplete list, afterwards it will only show the ID. This isn’t the greatest UX in my opinion, since the user can’t easily scan the parameters and determine what they mean. One workaround for this is to instead have a StringArray parameter and encode the title and ID into a single value.

For example, in ezeep Pack the user selects a paper size, but I didn’t want it to just show the ID number. So instead I used a pattern like Name (id).

image

I then created a helper function that would parse the parameter value, extracting the ID from the parenthesis, but also allowing for cases where just the ID was passed in (via a formula, etc).

function getOptionId(label: string): number {
  if (!label) return undefined;
  if (!Number.isNaN(parseInt(label))) {
    return Number(label);
  }
  let match = label.match(/^.*\((\d+)\)$/);
  if (!match) {
    throw new coda.UserVisibleError(`Invalid option: ${label}`);
  }
  return Number(match[1]);
}
3 Likes

Ho thank you for the UX suggestion !! I love it :heart_eyes: I’ll be sure to implement this everywhere I get the chance.

And about the minimal code suggestion, here you go:
The type of variable brokenParam is never if I keep the autocomplete function, but number[] if I comment it out. No type problem when I use coda.ValueType.StringArray.

import * as coda from '@codahq/packs-sdk';

export const pack = coda.newPack();

const InventoryLevelSchema = coda.makeObjectSchema({
  properties: {
    unique_id: {
      type: coda.ValueType.String,
      fixedId: 'unique_id',
      fromKey: 'id',
      required: true,
      description: 'A made up unique ID formed by combining the inventory_item_id and location_id.',
    },
  },
  displayProperty: 'unique_id',
  idProperty: 'unique_id',
});

pack.addSyncTable({
  name: 'InventoryLevels',
  description: 'Return Inventory Levels from this shop.',
  identityName: 'InventoryLevel',
  schema: InventoryLevelSchema,
  formula: {
    name: 'SyncInventoryLevels',
    description: '<Help text for the sync formula, not show to the user>',
    parameters: [
      coda.makeParameter({
        type: coda.ParameterType.NumberArray,
        name: 'locations',
        description: 'Filter results by locations.',
        autocomplete: async function (context, search) {
          const url = coda.withQueryParams(`${context.endpoint}/admin/api/2023-10/locations.json`, {
            fields: ['name', 'id'].join(', '),
            limit: 250,
          });
          const response = await context.fetcher.fetch({ method: 'GET', url });
          return coda.autocompleteSearchObjects(search, response.body.locations, 'name', 'id');
        },
      }),
    ],
    execute: async function ([brokenParam], context) {
      return { result: [], continuation: null };
    },
  },
});

Thanks for that example @Martin_Portevin, that helped me narrow it down. It appears that NumberArray parameters with autocomplete aren’t typed correctly, and I’ve reported the bug to the team. Here’s an even more minimal example I used:

import * as coda from '@codahq/packs-sdk';
export const pack = coda.newPack();

pack.addFormula({
  name: "Test",
  description: "",
  parameters: [
    coda.makeParameter({
      type: coda.ParameterType.NumberArray,
      name: "things",
      description: "",
      autocomplete: [1, 2, 3],
    }),
  ],
  resultType: coda.ValueType.String,
  execute: async function (args, context) {
    let [things] = args;
    return "";
  },
});
2 Likes

Thank you for having looked into it !