Skip to content

Plugins: Can't use Typescript Mixins to build a datasource #783

@ddelemeny

Description

@ddelemeny

What happened?

When creating a DataSource using Mixins, grafana logs the following error :

Uncaught (in promise) 
Object { message: "Datasource: fd8841fb-5d9c-4e1c-aeb7-8c5489e83032 was not found" }
[actions.ts:174:2](webpack://grafana/public/app/features/datasources/state/actions.ts)

​I tracked the bug down to this line
Typescript Mixins constructors MUST have a single rest parameter, therefore dsPlugin.DataSourceClass.length will be 0 instead of 1, incorrectly triggering the angular loading path.

What did you expect to happen?

Datasources built with mixins should work.

Did this work before?

I suppose not.

How do we reproduce it?

class Query {}
class Options {}

interface FooConstructor<
  TFoo extends AbsFooAPI<TQuery, TOptions>,
  TQuery extends Query = Query,
  TOptions extends Options = Options,
> {
  new (...args: any[]): TFoo;
}

// DatasourcePlugin
class FooPlugin<
  TFoo extends AbsFooAPI<TQuery, TOptions>, 
  TQuery extends Query, 
  TOptions extends Options, 
>{
  private foo: TFoo
  constructor(FooClass:FooConstructor<TFoo, TQuery, TOptions>){
    this.foo = new FooClass()
  }
  process(){
    console.log('Plugin')
    this.foo.doFoo()
    if (this.foo.doBar){
      this.foo.doBar()
    }
    if (this.foo.doBaz){
      this.foo.doBaz()
    }
  }
}

// DatasourceAPI
abstract class AbsFooAPI<TQuery extends Query, TOptions extends Options> {
  constructor(arg1:string){}
  abstract doFoo():void
  doBar?():void;
  doBaz?():void;
}

// DatasourceWithBackend
class Foo<
  TQuery extends Query = Query,
  TOptions extends Options = Options,
> extends AbsFooAPI<TQuery, TOptions>
{
  constructor(arg1:string){
    super(arg1)
  }
  doFoo():void {
    console.log("Done Foo");
  }
}

// My Datasource base
class FooBar extends Foo {
  constructor(arg1:string){
    super(arg1)
  }
  doBar():void{
    console.log('Done Bar')
  }
}

// My datasource feature mixin

type GConstructor<T = {}> = new (...args: any[]) => T;

function withBaz<T extends GConstructor<FooBar>>(Base : T){
  return class extends Base {
    // A mixin constructor must have only one rest parameter of type any[]
    // A constructor with a positional parameter breaks the mixin contract
    // constructor(first: any, ...args:any[]){
    //  super(first, ...args)
    // }
    doBaz():void{
      console.log('Done Baz')
    }
  }
}

class MyQuery extends Query {}
class MyOptions extends Options {}

const FooBarBaz = withBaz(FooBar)

const fbb = new FooBarBaz('settings')

const plugin = new FooPlugin<InstanceType<typeof FooBarBaz>,MyQuery,MyOptions>(FooBarBaz)

plugin.process()
console.log("AbsFooAPI LENGTH", AbsFooAPI.length)
console.log("Foo LENGTH", Foo.length)
console.log("FooBar LENGTH", FooBar.length)
console.log("FooBarBaz LENGTH", FooBarBaz.length)

This example replicates the structure of a DataSource plugin and showcases how the use of mixins breaks the assumption on the constructor's length.

Transposing it onto a real plugin should be trivial.

Is the bug inside a dashboard panel?

No response

Environment (with versions)?

Grafana: Grafana v10.3.3 (252761264e)
OS: Ubuntu 23.04
Browser: Firefox 122.0

Grafana platform?

Docker

Datasource(s)?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/bugSomething isn't working

    Type

    No type

    Projects

    Status

    🚀 Shipped

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions