typescriptpalantir-foundryfoundry-functions

How to have flexible grouping column in Foundry Functions?


In my Workshop application, I would like to have a bar chart with a changeable x-axis. A dropdown widget would be used to select the desired x-axis.

For this I am writing a TypeScript function, that will return the data that will feed the chart widget.

I have wrote the following function:

@Function()
public async flexibleCounter2DimensionTest(
    objects: ObjectSet<Data>,
    xaxis : string ): Promise<TwoDimensionalAggregation<string>> {
    return objects
            .groupBy(val => val[xaxis].topValues())
            .count()
}

The xaxis parameter would define the column name, on which to perform the grouping.

I am struggling to do this in Foundry Functions, I am always receiving the following error on compilation:

{ "stdout": "src/hospital_provider_analysis.ts(170,9): error TS2322: Type 'TwoDimensionalAggregation<BucketKey, number>' is not assignable to type 'TwoDimensionalAggregation<string, number>'.\n Type 'BucketKey' is not assignable to type 'string'.\n Type 'false' is not assignable to type 'string'.\nsrc/hospital_provider_analysis.ts(171,33): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'BucketableProperties'.\n No index signature with a parameter of type 'string' was found on type 'BucketableProperties'.\n", "stderr": "" }

Any ideas how to fix this issue?


Solution

  • Some of the more powerful parts of Typescript's type-system allow us to specify a type-safe version of this aggregation function. I detail one approach to this in Part 1 below.

    When exposing this type-safe aggregation function, we need to perform some additional checks because Functions doesn't currently support union types (see 'Functions API: Input and output types' in your Palantir documentation). I list my code for this in Part 2 below.

    Part 1: A type-safe aggregation

    First, we list all the property API names that we'd like to group-by as a new union type of literal types. These should be the API names you'd normally use to access these properties in a Function.

    type AggregatableStringProperties = "propertyA" | "propertyB" | ...;
    

    Now, we can define a type-safe aggregation function that takes an object set along with which property to aggregated by (of the ones in AggregatableStringProperties):

    private async flexibleCounter2DimensionTestImpl(
        objects: ObjectSet<Data>,
        xaxis: AggregatableStringProperties,
    ) : Promise<TwoDimensionalAggregation<string>> {
        return objects
            .groupBy(val => val[xaxis].topValues())
            .count();
    }
    

    Part 2: Exposing this as a @Function()

    Using the type-safe aggregation function that we defined in Part 1, we can now expose this using type assertions. In the code below, I add some optional logic to provide the user of this Function with helpful debugging information.

    @Function()
    public async flexibleCounter2DimensionTest(
        objects: ObjectSet<Data>,
        xaxis : string
    ): Promise<TwoDimensionalAggregation<string>> {
        const xaxis_t = xaxis as AggregatableStringProperties;
        if (Data.properties[xaxis_t] === undefined || Data.properties[xaxis_t].baseType.type !== "string") {
            throw new UserFacingError("xaxis argument ('" + xaxis + "') is not a string property of the Data object");
        }
        return this.flexibleCounter2DimensionTestImpl(objects, xaxis_t);
    }