@grafana/data#AppEvents TypeScript Examples

The following examples show how to use @grafana/data#AppEvents. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: playlist_edit_ctrl.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
savePlaylist(playlist: any, playlistItems: PlaylistItem[]) {
    let savePromise;

    playlist.items = playlistItems;

    savePromise = playlist.id
      ? promiseToDigest(this.$scope)(getBackendSrv().put('/api/playlists/' + playlist.id, playlist))
      : promiseToDigest(this.$scope)(getBackendSrv().post('/api/playlists', playlist));

    savePromise.then(
      () => {
        this.$scope.appEvent(AppEvents.alertSuccess, ['Playlist saved']);
        this.$location.path('/playlists');
      },
      () => {
        this.$scope.appEvent(AppEvents.alertError, ['Unable to save playlist']);
      }
    );
  }
Example #2
Source File: config_ctrl.ts    From loudml-grafana-app with MIT License 6 votes vote down vote up
async startModel(name: any) {
    const ds = (await getDataSourceSrv().loadDatasource(this.current.name)) as LoudMLDatasource;

    try {
      ds.loudml.startModel(name).then(response => {
        window.console.log(response);
        appEvents.emit(AppEvents.alertSuccess, ['Model has been started on Loud ML server']);
        this.refreshModels();
      });
    } catch (err) {
      console.error(err);
      appEvents.emit(AppEvents.alertError, ['Model start error', err]);
    }
  }
Example #3
Source File: CreateFolderCtrl.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
create() {
    if (this.hasValidationError) {
      return;
    }

    promiseToDigest(this.$scope)(
      backendSrv.createFolder({ title: this.title }).then((result: any) => {
        appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
        this.$location.url(locationUtil.stripBaseFromUrl(result.url));
      })
    );
  }
Example #4
Source File: config_ctrl.ts    From loudml-grafana-app with MIT License 6 votes vote down vote up
async stopModel(name: any) {
    const ds = (await getDataSourceSrv().loadDatasource(this.current.name)) as LoudMLDatasource;

    try {
      ds.loudml.stopModel(name).then(response => {
        window.console.log(response);
        appEvents.emit(AppEvents.alertSuccess, ['Model has been stoped on Loud ML server']);
        this.refreshModels();
      });
    } catch (err) {
      console.error(err);
      appEvents.emit(AppEvents.alertError, ['Model stop error', err]);
    }
  }
Example #5
Source File: playlists_ctrl.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
removePlaylistConfirmed(playlist: any) {
    _.remove(this.playlists, { id: playlist.id });

    promiseToDigest(this.$scope)(
      getBackendSrv()
        .delete('/api/playlists/' + playlist.id)
        .then(
          () => {
            this.$scope.appEvent(AppEvents.alertSuccess, ['Playlist deleted']);
          },
          () => {
            this.$scope.appEvent(AppEvents.alertError, ['Unable to delete playlist']);
            this.playlists.push(playlist);
          }
        )
    );
  }
Example #6
Source File: GraphPanelController.tsx    From loudml-grafana-app with MIT License 6 votes vote down vote up
_trainModel(name: string, output_bucket: string) {
    const loudml = this.ds.loudml;

    try {
      loudml
        .trainModel(name, this.data, output_bucket)
        .then(result => {
          window.console.log('trainModel', result);
          appEvents.emit(AppEvents.alertSuccess, ['Model train job started on Loud ML server']);
        })
        .catch(err => {
          window.console.log('trainModel error', err);
          appEvents.emit(AppEvents.alertError, ['Model train job error', err.data.message]);
          return;
        });
    } catch (error) {
      console.error(error);
      appEvents.emit(AppEvents.alertError, ['Model train job error', err.message]);
    }
  }
Example #7
Source File: FolderPicker.tsx    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
createNewFolder = async (folderName: string) => {
    // @ts-ignore
    const newFolder = await getBackendSrv().createFolder({ title: folderName });
    let folder = { value: -1, label: 'Not created' };
    if (newFolder.id > -1) {
      appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
      folder = { value: newFolder.id, label: newFolder.title };
      await this.onFolderChange(folder);
    } else {
      appEvents.emit(AppEvents.alertError, ['Folder could not be created']);
    }

    return folder;
  };
Example #8
Source File: GraphPanelController.tsx    From loudml-grafana-app with MIT License 6 votes vote down vote up
trainModel() {
    if (this.model) {
      const output_bucket = this.props.panelOptions.datasourceOptions.output_bucket;

      try {
        this.loudml
          .trainModel(this.modelName, this.props.data, output_bucket)
          .then(result => {
            window.console.log('ML trainModel', result);
            appEvents.emit(AppEvents.alertSuccess, ['Model train job started on Loud ML server']);
          })
          .catch(err => {
            window.console.log('ML trainModel error', err);
            appEvents.emit(AppEvents.alertError, ['Model train job error', err.data.message]);
            return;
          });
      } catch (error) {
        console.error(error);
        appEvents.emit(AppEvents.alertError, ['Model train job error', err.message]);
      }
    }
  }
Example #9
Source File: misc.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
function clipboardButton() {
  return {
    scope: {
      getText: '&clipboardButton',
    },
    link: (scope: any, elem: any) => {
      scope.clipboard = new Clipboard(elem[0], {
        text: () => {
          return scope.getText();
        },
      });

      scope.clipboard.on('success', () => {
        appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
      });

      scope.$on('$destroy', () => {
        if (scope.clipboard) {
          scope.clipboard.destroy();
        }
      });
    },
  };
}
Example #10
Source File: GraphPanelController.tsx    From loudml-grafana-app with MIT License 6 votes vote down vote up
onCreateBaselineClick() {
    const datasourceOptions = this.props.panelOptions.datasourceOptions;

    window.console.log('-- Create Baseline --');
    window.console.log('LoudML server:', datasourceOptions.datasource);
    window.console.log('Input bucket:', datasourceOptions.input_bucket);
    window.console.log('Output bucket:', datasourceOptions.output_bucket);

    this.dsName = datasourceOptions.datasource;

    if (!this.dsName) {
      appEvents.emit(AppEvents.alertError, ['Please choose Loud ML Server in panel settings']);
      return;
    }

    this.getLoudMLDatasource()
      .then(result => {
        this.ds = result;

        if (this.isValid()) {
          this._createAndTrainModel();
        } else {
          appEvents.emit(AppEvents.alertError, ['In Query settings please choose One metric; Group by != auto; Fill != linear']);
        }
      })
      .catch(err => {
        window.console.log('Error getting Loud ML datasource', err);
        appEvents.emit(AppEvents.alertError, [err.message]);
        return;
      });
  }
Example #11
Source File: NotificationsEditCtrl.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
save() {
    if (!this.theForm.$valid) {
      return;
    }

    if (this.model.id) {
      promiseToDigest(this.$scope)(
        getBackendSrv()
          .put(`/api/alert-notifications/${this.model.id}`, this.model)
          .then((res: any) => {
            this.model = res;
            appEvents.emit(AppEvents.alertSuccess, ['Notification updated']);
          })
          .catch((err: any) => {
            if (err.data && err.data.error) {
              appEvents.emit(AppEvents.alertError, [err.data.error]);
            }
          })
      );
    } else {
      promiseToDigest(this.$scope)(
        getBackendSrv()
          .post(`/api/alert-notifications`, this.model)
          .then((res: any) => {
            appEvents.emit(AppEvents.alertSuccess, ['Notification created']);
            this.$location.path('alerting/notifications');
          })
          .catch((err: any) => {
            if (err.data && err.data.error) {
              appEvents.emit(AppEvents.alertError, [err.data.error]);
            }
          })
      );
    }
  }
Example #12
Source File: Graph2.tsx    From loudml-grafana-app with MIT License 6 votes vote down vote up
constructor(props) {
    super(props);

    const promises = [];
    const dsPromises = [];
    const range = props.timeRange;
    this.annotations = [];

    if (props.panelChrome) {
      this.dashboard = props.panelChrome.props.dashboard;

      Promise.all([this.getGlobalAnnotations(range)])
        .then(results => {
          this.annotations = flattenDeep(results[0]);
          // filter out annotations that do not belong to requesting panel
          this.annotations = this.annotations.filter(item => {
            // if event has panel id and query is of type dashboard then panel and requesting panel id must match
            if (item.panelId && item.source.type === 'dashboard') {
              return item.panelId === props.panelChrome.props.panel.id;
            }
            return true;
          });

          this.annotations = this.dedupAnnotations(this.annotations);
          this.draw();
        })
        .catch(err => {
          if (!err.message && err.data && err.data.message) {
            err.message = err.data.message;
          }
          console.log('AnnotationSrv.query error', err);
          appEvents.emit(AppEvents.alertError, ['Annotation Query Failed', err.message || err]);
          return [];
        });
    }
  }
Example #13
Source File: FolderPickerCtrl.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
createFolder(evt: any) {
    if (evt) {
      evt.stopPropagation();
      evt.preventDefault();
    }

    return promiseToDigest(this.$scope)(
      backendSrv.createFolder({ title: this.newFolderName }).then((result: { title: string; id: number }) => {
        appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);

        this.closeCreateFolder();
        this.folder = { text: result.title, value: result.id };
        this.onFolderChange(this.folder);
      })
    );
  }
Example #14
Source File: config_ctrl.ts    From loudml-grafana-app with MIT License 6 votes vote down vote up
async deleteModel(name: any) {
    const ds = (await getDataSourceSrv().loadDatasource(this.current.name)) as LoudMLDatasource;

    try {
      ds.loudml.deleteModel(name).then(response => {
        window.console.log(response);
        appEvents.emit(AppEvents.alertSuccess, ['Model has been deleted on Loud ML server']);
        this.refreshModels();
      });
    } catch (err) {
      console.error(err);
      appEvents.emit(AppEvents.alertError, ['Model delete error', err]);
    }
  }
Example #15
Source File: AnnoListPanel.tsx    From grafana-chinese with Apache License 2.0 5 votes vote down vote up
onAnnoClick = (e: React.SyntheticEvent, anno: AnnotationEvent) => {
    e.stopPropagation();
    const { options } = this.props;
    const dashboardSrv = getDashboardSrv();
    const current = dashboardSrv.getCurrent();

    const params: any = {
      from: this._timeOffset(anno.time, options.navigateBefore, true),
      to: this._timeOffset(anno.time, options.navigateAfter, false),
    };

    if (options.navigateToPanel) {
      params.panelId = anno.panelId;
      params.fullscreen = true;
    }

    if (current.id === anno.dashboardId) {
      store.dispatch(
        updateLocation({
          query: params,
          partial: true,
        })
      );
      return;
    }

    getBackendSrv()
      .get('/api/search', { dashboardIds: anno.dashboardId })
      .then((res: any[]) => {
        if (res && res.length && res[0].id === anno.dashboardId) {
          const dash = res[0];
          store.dispatch(
            updateLocation({
              query: params,
              path: dash.url,
            })
          );
          return;
        }
        appEvents.emit(AppEvents.alertWarning, ['Unknown Dashboard: ' + anno.dashboardId]);
      });
  };
Example #16
Source File: config_ctrl.ts    From loudml-grafana-app with MIT License 5 votes vote down vote up
/**
   * Posts dialog data to LoudML server
   */
  async addModelPost() {
    console.log(this.model);

    if (this.model.features[0].default !== 'previous') {
      this.model.features[0].default = 0;
    }

    delete this.model.features[0].$$hashKey;

    const ds = (await getDataSourceSrv().loadDatasource(this.current.name)) as LoudMLDatasource;
    ds.loudml
      .getModel(this.model.name)
      .then(result => {
        console.log('Model exists, updating it...');
        ds.loudml
          .patchModel(this.model.name, this.model)
          .then(result => {})
          .catch(err => {
            sorry_its_error(err);
            return;
          });
        //   // Let remove it and recreate
        //   ds.loudml.deleteModel(this.model.name).then(response => {
        //     ds.loudml
        //       .createModel(this.model)
        //       .then(result => {
        //         ds.loudml
        //           .createModelHook(this.model.name, ds.loudml.createHook(ANOMALY_HOOK, this.model.default_bucket))
        //           .then(result => {
        //             appEvents.emit(AppEvents.alertSuccess, ['Model has been updated on Loud ML server']);
        //             this.refreshModels();
        //           })
        //           .catch(err => {
        //             window.console.log('createModelHook error', err);
        //             appEvents.emit(AppEvents.alertError, [err.message]);
        //             return;
        //           });
        //       })
        //       .catch(err => {
        //         window.console.log('Model create error', err);
        //         appEvents.emit(AppEvents.alertError, ['Model create error', err.data]);
        //         return;
        //       });
        //   });
      })
      .catch(err => {
        // New model
        console.log('New model, creating it...');
        ds.loudml
          .createModel(this.model)
          .then(result => {
            ds.loudml
              .createModelHook(this.model.name, ds.loudml.createHook(ANOMALY_HOOK, this.model.default_bucket))
              .then(result => {
                appEvents.emit(AppEvents.alertSuccess, ['Model has been created on Loud ML server']);
                this.refreshModels();
              })
              .catch(err => {
                window.console.log('createModelHook error', err);
                appEvents.emit(AppEvents.alertError, [err.message]);
                return;
              });
          })
          .catch(err => {
            window.console.log('createModel error', err);
            appEvents.emit(AppEvents.alertError, ['Model create error', err.data]);
            return;
          });
      });
  }
Example #17
Source File: uploadDashboardDirective.ts    From grafana-chinese with Apache License 2.0 5 votes vote down vote up
/** @ngInject */
export function uploadDashboardDirective(timer: any, $location: ILocationService) {
  return {
    restrict: 'E',
    template: template,
    scope: {
      onUpload: '&',
      btnText: '@?',
    },
    link: (scope: any, elem: JQuery) => {
      scope.btnText = angular.isDefined(scope.btnText) ? scope.btnText : 'Upload .json file';

      function file_selected(evt: any) {
        const files = evt.target.files; // FileList object
        const readerOnload = () => {
          return (e: any) => {
            let dash: any;
            try {
              dash = JSON.parse(e.target.result);
            } catch (err) {
              console.log(err);
              appEvents.emit(AppEvents.alertError, [
                'Import failed',
                'JSON -> JS Serialization failed: ' + err.message,
              ]);
              return;
            }

            scope.$apply(() => {
              scope.onUpload({ dash });
            });
          };
        };

        let i = 0;
        let file = files[i];

        while (file) {
          const reader = new FileReader();
          reader.onload = readerOnload();
          reader.readAsText(file);
          i += 1;
          file = files[i];
        }
      }

      const wnd: any = window;
      // Check for the various File API support.
      if (wnd.File && wnd.FileReader && wnd.FileList && wnd.Blob) {
        // Something
        elem[0].addEventListener('change', file_selected, false);
      } else {
        appEvents.emit(AppEvents.alertError, ['Oops', 'The HTML5 File APIs are not fully supported in this browser']);
      }
    },
  };
}
Example #18
Source File: GraphPanelController.tsx    From loudml-grafana-app with MIT License 4 votes vote down vote up
_createAndTrainModel() {
    // Model Example:
    // {
    //     "bucket_interval": "5m",
    //     "default_bucket": "telegraf_autogen_cpu",
    //     "features": [
    //         {
    //             "name": "mean_usage_user",
    //             "measurement": "cpu",
    //             "field": "usage_user",
    //             "metric": "mean",
    //             "io": "io",
    //             "default": null,
    //             "match_all": [
    //                 {
    //                     "tag": "cpu",
    //                     "value": "cpu-total"
    //                 },
    //                 {
    //                     "tag": "host",
    //                     "value": "macbook4823"
    //                 }
    //             ]
    //         }
    //     ],
    //     "interval": "60s",
    //     "max_evals": 10,
    //     "name": "telegraf_cpu_mean_usage_user_cpu_cpu_total_host_macbook4823_5m",
    //     "offset": "10s",
    //     "span": 100,
    //     "type": "donut"
    // }

    const source = this.data.request.targets[0];
    const fields = source.select || [source];
    const loudml = this.ds.loudml;

    this.getDatasource(source.datasource)
      .then(result => {
        this.datasource = result;
        // TODO: find a way to pass all this.datasource connection params to Loud ML server
        // This will allow to auto create bucket to store ML Model training results

        // this.ds.loudml.createAndGetBucket(
        //   this.datasource.database,
        //   source.policy,
        //   source.measurement,
        //   this.datasource
        // ).then(result => {
        //     const bucket = result;
        const bucket = this.props.panelOptions.datasourceOptions.input_bucket;
        const output_bucket = this.props.panelOptions.datasourceOptions.output_bucket;
        const measurement = extract_model_measurement(source);
        const fill = extract_model_fill(source);
        const match_all = extract_model_tags_map(source);
        const name = [
          extract_model_database(this.datasource),
          measurement,
          extract_model_select(source, fields[0]), // will use 1st metric to name model - fields[0]
          extract_model_tags(source),
          extract_model_time_format(source),
        ]
          .join('_')
          .replace(/\./g, '_');

        // Group By Value – [{params: ["5m"], type: "time"}, {params: ["linear"], type: "fill"}]
        // Let parse a "5m" time from it
        const time = extract_model_time(source);
        const model = {
          ...DEFAULT_MODEL,
          max_evals: 10,
          name: name,
          interval: this.normalizeInterval(time),
          span: this.normalizeSpan(time),
          default_bucket: bucket, //bucket.name - if we will use createAndGetBucket()
          bucket_interval: time,
          features: fields.map(field => ({
            name: extract_model_select(source, field),
            measurement: measurement,
            field: extract_model_feature(source, field),
            metric: extract_model_func(source, field), // aggregator, avg/mean
            io: 'io',
            default: fill,
            match_all: match_all,
          })),
        };

        window.console.log('ML Model', model);
        this.props.panelOptions.modelName = name;
        this.props.onOptionsChange(this.props.panelOptions);

        loudml
          .getModel(name)
          .then(result => {
            window.console.log('ML model already exists. Let train it on the current dataframe');
            this.props.panelOptions.modelName = name;
            this.props.onOptionsChange(this.props.panelOptions);
            this._trainModel(name, output_bucket);
          })
          .catch(err => {
            window.console.log('New ML model case. Let create and train it.');
            loudml
              .createModel(model)
              .then(result => {
                loudml
                  .createModelHook(model.name, loudml.createHook(ANOMALY_HOOK, model.default_bucket))
                  .then(result => {
                    appEvents.emit(AppEvents.alertSuccess, ['Model has been created on Loud ML server']);

                    this.props.panelOptions.modelName = name;
                    this.props.onOptionsChange(this.props.panelOptions);
                    this._trainModel(name, output_bucket);
                  })
                  .catch(err => {
                    window.console.log('createModelHook error', err);
                    appEvents.emit(AppEvents.alertError, [err.message]);
                    return;
                  });
              })
              .catch(err => {
                window.console.log('createModel error', err);
                appEvents.emit(AppEvents.alertError, ['Model create error', err.data]);
                return;
              });
          });
        // })
      })
      .catch(err => {
        console.error(err);
        appEvents.emit(AppEvents.alertError, [err.message]);
        return;
      });
  }
Example #19
Source File: backend_srv.test.ts    From grafana-chinese with Apache License 2.0 4 votes vote down vote up
describe('backendSrv', () => {
  describe('parseRequestOptions', () => {
    it.each`
      retry        | url                                      | orgId        | expected
      ${undefined} | ${'http://localhost:3000/api/dashboard'} | ${undefined} | ${{ retry: 0, url: 'http://localhost:3000/api/dashboard' }}
      ${1}         | ${'http://localhost:3000/api/dashboard'} | ${1}         | ${{ retry: 1, url: 'http://localhost:3000/api/dashboard' }}
      ${undefined} | ${'api/dashboard'}                       | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
      ${undefined} | ${'/api/dashboard'}                      | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
      ${undefined} | ${'/api/dashboard/'}                     | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
      ${1}         | ${'/api/dashboard/'}                     | ${undefined} | ${{ retry: 1, url: 'api/dashboard' }}
      ${undefined} | ${'/api/dashboard/'}                     | ${1}         | ${{ retry: 0, url: 'api/dashboard', headers: { 'X-Grafana-Org-Id': 1 } }}
      ${1}         | ${'/api/dashboard/'}                     | ${1}         | ${{ retry: 1, url: 'api/dashboard', headers: { 'X-Grafana-Org-Id': 1 } }}
    `(
      "when called with retry: '$retry', url: '$url' and orgId: '$orgId' then result should be '$expected'",
      ({ retry, url, orgId, expected }) => {
        expect(getBackendSrv()['parseRequestOptions']({ retry, url }, orgId)).toEqual(expected);
      }
    );
  });

  describe('parseDataSourceRequestOptions', () => {
    it.each`
      retry        | url                                      | headers                           | orgId        | noBackendCache | expected
      ${undefined} | ${'http://localhost:3000/api/dashboard'} | ${undefined}                      | ${undefined} | ${undefined}   | ${{ retry: 0, url: 'http://localhost:3000/api/dashboard' }}
      ${1}         | ${'http://localhost:3000/api/dashboard'} | ${{ Authorization: 'Some Auth' }} | ${1}         | ${true}        | ${{ retry: 1, url: 'http://localhost:3000/api/dashboard', headers: { Authorization: 'Some Auth' } }}
      ${undefined} | ${'api/dashboard'}                       | ${undefined}                      | ${undefined} | ${undefined}   | ${{ retry: 0, url: 'api/dashboard' }}
      ${undefined} | ${'/api/dashboard'}                      | ${undefined}                      | ${undefined} | ${undefined}   | ${{ retry: 0, url: 'api/dashboard' }}
      ${undefined} | ${'/api/dashboard/'}                     | ${undefined}                      | ${undefined} | ${undefined}   | ${{ retry: 0, url: 'api/dashboard/' }}
      ${undefined} | ${'/api/dashboard/'}                     | ${{ Authorization: 'Some Auth' }} | ${undefined} | ${undefined}   | ${{ retry: 0, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth' } }}
      ${undefined} | ${'/api/dashboard/'}                     | ${{ Authorization: 'Some Auth' }} | ${1}         | ${undefined}   | ${{ retry: 0, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth', 'X-Grafana-Org-Id': 1 } }}
      ${undefined} | ${'/api/dashboard/'}                     | ${{ Authorization: 'Some Auth' }} | ${1}         | ${true}        | ${{ retry: 0, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth', 'X-Grafana-Org-Id': 1, 'X-Grafana-NoCache': 'true' } }}
      ${1}         | ${'/api/dashboard/'}                     | ${undefined}                      | ${undefined} | ${undefined}   | ${{ retry: 1, url: 'api/dashboard/' }}
      ${1}         | ${'/api/dashboard/'}                     | ${{ Authorization: 'Some Auth' }} | ${undefined} | ${undefined}   | ${{ retry: 1, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth' } }}
      ${1}         | ${'/api/dashboard/'}                     | ${{ Authorization: 'Some Auth' }} | ${1}         | ${undefined}   | ${{ retry: 1, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth', 'X-Grafana-Org-Id': 1 } }}
      ${1}         | ${'/api/dashboard/'}                     | ${{ Authorization: 'Some Auth' }} | ${1}         | ${true}        | ${{ retry: 1, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth', 'X-Grafana-Org-Id': 1, 'X-Grafana-NoCache': 'true' } }}
    `(
      "when called with retry: '$retry', url: '$url', headers: '$headers', orgId: '$orgId' and noBackendCache: '$noBackendCache' then result should be '$expected'",
      ({ retry, url, headers, orgId, noBackendCache, expected }) => {
        expect(
          getBackendSrv()['parseDataSourceRequestOptions']({ retry, url, headers }, orgId, noBackendCache)
        ).toEqual(expected);
      }
    );
  });

  describe('request', () => {
    describe('when making a successful call and conditions for showSuccessAlert are not favorable', () => {
      it('then it should return correct result and not emit anything', async () => {
        const { backendSrv, appEventsMock, expectRequestCallChain } = getTestContext({
          data: { message: 'A message' },
        });
        const url = '/api/dashboard/';
        const result = await backendSrv.request({ url, method: 'DELETE', showSuccessAlert: false });
        expect(result).toEqual({ message: 'A message' });
        expect(appEventsMock.emit).not.toHaveBeenCalled();
        expectRequestCallChain({ url, method: 'DELETE', showSuccessAlert: false });
      });
    });

    describe('when making a successful call and conditions for showSuccessAlert are favorable', () => {
      it('then it should emit correct message', async () => {
        const { backendSrv, appEventsMock, expectRequestCallChain } = getTestContext({
          data: { message: 'A message' },
        });
        const url = '/api/dashboard/';
        const result = await backendSrv.request({ url, method: 'DELETE', showSuccessAlert: true });
        expect(result).toEqual({ message: 'A message' });
        expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
        expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertSuccess, ['A message']);
        expectRequestCallChain({ url, method: 'DELETE', showSuccessAlert: true });
      });
    });

    describe('when called with the same requestId twice', () => {
      it('then it should cancel the first call and the first call should be unsubscribed', async () => {
        const url = '/api/dashboard/';
        const { backendSrv, fromFetchMock } = getTestContext({ url });
        const unsubscribe = jest.fn();
        const slowData = { message: 'Slow Request' };
        const slowFetch = new Observable(subscriber => {
          subscriber.next({
            ok: true,
            status: 200,
            statusText: 'Ok',
            text: () => Promise.resolve(JSON.stringify(slowData)),
            headers: {
              map: {
                'content-type': 'application/json',
              },
            },
            redirected: false,
            type: 'basic',
            url,
          });
          return unsubscribe;
        }).pipe(delay(10000));
        const fastData = { message: 'Fast Request' };
        const fastFetch = of({
          ok: true,
          status: 200,
          statusText: 'Ok',
          text: () => Promise.resolve(JSON.stringify(fastData)),
          headers: {
            map: {
              'content-type': 'application/json',
            },
          },
          redirected: false,
          type: 'basic',
          url,
        });
        fromFetchMock.mockImplementationOnce(() => slowFetch);
        fromFetchMock.mockImplementation(() => fastFetch);
        const options = {
          url,
          method: 'GET',
          requestId: 'A',
        };
        const slowRequest = backendSrv.request(options);
        const fastResponse = await backendSrv.request(options);
        expect(fastResponse).toEqual({ message: 'Fast Request' });

        const result = await slowRequest;
        expect(result).toEqual([]);
        expect(unsubscribe).toHaveBeenCalledTimes(1);
      });
    });

    describe('when making an unsuccessful call and conditions for retry are favorable and loginPing does not throw', () => {
      it('then it should retry', async () => {
        jest.useFakeTimers();
        const url = '/api/dashboard/';
        const { backendSrv, appEventsMock, logoutMock, expectRequestCallChain } = getTestContext({
          ok: false,
          status: 401,
          statusText: 'UnAuthorized',
          data: { message: 'UnAuthorized' },
          url,
        });
        backendSrv.loginPing = jest
          .fn()
          .mockResolvedValue({ ok: true, status: 200, statusText: 'OK', data: { message: 'Ok' } });
        await backendSrv
          .request({ url, method: 'GET', retry: 0 })
          .catch(error => {
            expect(error.status).toBe(401);
            expect(error.statusText).toBe('UnAuthorized');
            expect(error.data).toEqual({ message: 'UnAuthorized' });
            expect(appEventsMock.emit).not.toHaveBeenCalled();
            expect(logoutMock).not.toHaveBeenCalled();
            expect(backendSrv.loginPing).toHaveBeenCalledTimes(1);
            expectRequestCallChain({ url, method: 'GET', retry: 0 });
            jest.advanceTimersByTime(50);
          })
          .catch(error => {
            expect(error).toEqual({ message: 'UnAuthorized' });
            expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
            expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertWarning, ['UnAuthorized', '']);
          });
      });
    });

    describe('when making an unsuccessful call and conditions for retry are favorable and retry throws', () => {
      it('then it throw error', async () => {
        jest.useFakeTimers();
        const { backendSrv, appEventsMock, logoutMock, expectRequestCallChain } = getTestContext({
          ok: false,
          status: 401,
          statusText: 'UnAuthorized',
          data: { message: 'UnAuthorized' },
        });
        backendSrv.loginPing = jest
          .fn()
          .mockRejectedValue({ status: 403, statusText: 'Forbidden', data: { message: 'Forbidden' } });
        const url = '/api/dashboard/';
        await backendSrv
          .request({ url, method: 'GET', retry: 0 })
          .catch(error => {
            expect(error.status).toBe(403);
            expect(error.statusText).toBe('Forbidden');
            expect(error.data).toEqual({ message: 'Forbidden' });
            expect(appEventsMock.emit).not.toHaveBeenCalled();
            expect(backendSrv.loginPing).toHaveBeenCalledTimes(1);
            expect(logoutMock).not.toHaveBeenCalled();
            expectRequestCallChain({ url, method: 'GET', retry: 0 });
            jest.advanceTimersByTime(50);
          })
          .catch(error => {
            expect(error).toEqual({ message: 'Forbidden' });
            expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
            expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertWarning, ['Forbidden', '']);
          });
      });
    });

    describe('when making an unsuccessful 422 call', () => {
      it('then it should emit Validation failed message', async () => {
        jest.useFakeTimers();
        const { backendSrv, appEventsMock, logoutMock, expectRequestCallChain } = getTestContext({
          ok: false,
          status: 422,
          statusText: 'Unprocessable Entity',
          data: { message: 'Unprocessable Entity' },
        });
        const url = '/api/dashboard/';
        await backendSrv
          .request({ url, method: 'GET' })
          .catch(error => {
            expect(error.status).toBe(422);
            expect(error.statusText).toBe('Unprocessable Entity');
            expect(error.data).toEqual({ message: 'Unprocessable Entity' });
            expect(appEventsMock.emit).not.toHaveBeenCalled();
            expect(logoutMock).not.toHaveBeenCalled();
            expectRequestCallChain({ url, method: 'GET' });
            jest.advanceTimersByTime(50);
          })
          .catch(error => {
            expect(error).toEqual({ message: 'Unprocessable Entity' });
            expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
            expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertWarning, [
              'Validation failed',
              'Unprocessable Entity',
            ]);
          });
      });
    });

    describe('when making an unsuccessful call and we handle the error', () => {
      it('then it should not emit message', async () => {
        jest.useFakeTimers();
        const { backendSrv, appEventsMock, logoutMock, expectRequestCallChain } = getTestContext({
          ok: false,
          status: 404,
          statusText: 'Not found',
          data: { message: 'Not found' },
        });
        const url = '/api/dashboard/';
        await backendSrv.request({ url, method: 'GET' }).catch(error => {
          expect(error.status).toBe(404);
          expect(error.statusText).toBe('Not found');
          expect(error.data).toEqual({ message: 'Not found' });
          expect(appEventsMock.emit).not.toHaveBeenCalled();
          expect(logoutMock).not.toHaveBeenCalled();
          expectRequestCallChain({ url, method: 'GET' });
          error.isHandled = true;
          jest.advanceTimersByTime(50);
          expect(appEventsMock.emit).not.toHaveBeenCalled();
        });
      });
    });
  });

  describe('datasourceRequest', () => {
    describe('when making a successful call and silent is true', () => {
      it('then it should not emit message', async () => {
        const url = 'http://localhost:3000/api/some-mock';
        const { backendSrv, appEventsMock, expectDataSourceRequestCallChain } = getTestContext({ url });
        const options = { url, method: 'GET', silent: true };
        const result = await backendSrv.datasourceRequest(options);
        expect(result).toEqual({
          data: { test: 'hello world' },
          ok: true,
          redirected: false,
          status: 200,
          statusText: 'Ok',
          type: 'basic',
          url,
          config: options,
        });
        expect(appEventsMock.emit).not.toHaveBeenCalled();
        expectDataSourceRequestCallChain({ url, method: 'GET', silent: true });
      });
    });

    describe('when making a successful call and silent is not defined', () => {
      it('then it should not emit message', async () => {
        const url = 'http://localhost:3000/api/some-mock';
        const { backendSrv, appEventsMock, expectDataSourceRequestCallChain } = getTestContext({ url });
        const options = { url, method: 'GET' };
        const result = await backendSrv.datasourceRequest(options);
        const expectedResult = {
          data: { test: 'hello world' },
          ok: true,
          redirected: false,
          status: 200,
          statusText: 'Ok',
          type: 'basic',
          url,
          config: options,
        };

        expect(result).toEqual(expectedResult);
        expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
        expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestResponse, expectedResult);
        expectDataSourceRequestCallChain({ url, method: 'GET' });
      });
    });

    describe('when called with the same requestId twice', () => {
      it('then it should cancel the first call and the first call should be unsubscribed', async () => {
        const url = '/api/dashboard/';
        const { backendSrv, fromFetchMock } = getTestContext({ url });
        const unsubscribe = jest.fn();
        const slowData = { message: 'Slow Request' };
        const slowFetch = new Observable(subscriber => {
          subscriber.next({
            ok: true,
            status: 200,
            statusText: 'Ok',
            text: () => Promise.resolve(JSON.stringify(slowData)),
            redirected: false,
            type: 'basic',
            url,
          });
          return unsubscribe;
        }).pipe(delay(10000));
        const fastData = { message: 'Fast Request' };
        const fastFetch = of({
          ok: true,
          status: 200,
          statusText: 'Ok',
          text: () => Promise.resolve(JSON.stringify(fastData)),
          redirected: false,
          type: 'basic',
          url,
        });
        fromFetchMock.mockImplementationOnce(() => slowFetch);
        fromFetchMock.mockImplementation(() => fastFetch);
        const options = {
          url,
          method: 'GET',
          requestId: 'A',
        };
        const slowRequest = backendSrv.datasourceRequest(options);
        const fastResponse = await backendSrv.datasourceRequest(options);
        expect(fastResponse).toEqual({
          data: { message: 'Fast Request' },
          ok: true,
          redirected: false,
          status: 200,
          statusText: 'Ok',
          type: 'basic',
          url: '/api/dashboard/',
          config: options,
        });

        const result = await slowRequest;
        expect(result).toEqual({
          data: [],
          status: -1,
          statusText: 'Request was aborted',
          config: options,
        });
        expect(unsubscribe).toHaveBeenCalledTimes(1);
      });
    });

    describe('when making an unsuccessful call and conditions for retry are favorable and loginPing does not throw', () => {
      it('then it should retry', async () => {
        const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
          ok: false,
          status: 401,
          statusText: 'UnAuthorized',
          data: { message: 'UnAuthorized' },
        });
        backendSrv.loginPing = jest
          .fn()
          .mockResolvedValue({ ok: true, status: 200, statusText: 'OK', data: { message: 'Ok' } });
        const url = '/api/dashboard/';
        await backendSrv.datasourceRequest({ url, method: 'GET', retry: 0 }).catch(error => {
          expect(error.status).toBe(401);
          expect(error.statusText).toBe('UnAuthorized');
          expect(error.data).toEqual({ message: 'UnAuthorized' });
          expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
          expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestError, {
            data: { message: 'UnAuthorized' },
            status: 401,
            statusText: 'UnAuthorized',
          });
          expect(backendSrv.loginPing).toHaveBeenCalledTimes(1);
          expect(logoutMock).not.toHaveBeenCalled();
          expectDataSourceRequestCallChain({ url, method: 'GET', retry: 0 });
        });
      });
    });

    describe('when making an unsuccessful call and conditions for retry are favorable and retry throws', () => {
      it('then it throw error', async () => {
        const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
          ok: false,
          status: 401,
          statusText: 'UnAuthorized',
          data: { message: 'UnAuthorized' },
        });
        backendSrv.loginPing = jest
          .fn()
          .mockRejectedValue({ status: 403, statusText: 'Forbidden', data: { message: 'Forbidden' } });
        const url = '/api/dashboard/';
        await backendSrv.datasourceRequest({ url, method: 'GET', retry: 0 }).catch(error => {
          expect(error.status).toBe(403);
          expect(error.statusText).toBe('Forbidden');
          expect(error.data).toEqual({ message: 'Forbidden' });
          expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
          expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestError, {
            data: { message: 'Forbidden' },
            status: 403,
            statusText: 'Forbidden',
          });
          expect(backendSrv.loginPing).toHaveBeenCalledTimes(1);
          expect(logoutMock).not.toHaveBeenCalled();
          expectDataSourceRequestCallChain({ url, method: 'GET', retry: 0 });
        });
      });
    });

    describe('when making an Internal Error call', () => {
      it('then it should throw cancelled error', async () => {
        const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
          ok: false,
          status: 500,
          statusText: 'Internal Server Error',
          data: 'Internal Server Error',
        });
        const url = '/api/dashboard/';
        await backendSrv.datasourceRequest({ url, method: 'GET' }).catch(error => {
          expect(error).toEqual({
            status: 500,
            statusText: 'Internal Server Error',
            data: {
              error: 'Internal Server Error',
              response: 'Internal Server Error',
              message: 'Internal Server Error',
            },
          });
          expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
          expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestError, {
            status: 500,
            statusText: 'Internal Server Error',
            data: {
              error: 'Internal Server Error',
              response: 'Internal Server Error',
              message: 'Internal Server Error',
            },
          });
          expect(logoutMock).not.toHaveBeenCalled();
          expectDataSourceRequestCallChain({ url, method: 'GET' });
        });
      });
    });

    describe('when formatting prometheus error', () => {
      it('then it should throw cancelled error', async () => {
        const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
          ok: false,
          status: 403,
          statusText: 'Forbidden',
          data: { error: 'Forbidden' },
        });
        const url = '/api/dashboard/';
        await backendSrv.datasourceRequest({ url, method: 'GET' }).catch(error => {
          expect(error).toEqual({
            status: 403,
            statusText: 'Forbidden',
            data: {
              error: 'Forbidden',
              message: 'Forbidden',
            },
          });
          expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
          expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestError, {
            status: 403,
            statusText: 'Forbidden',
            data: {
              error: 'Forbidden',
              message: 'Forbidden',
            },
          });
          expect(logoutMock).not.toHaveBeenCalled();
          expectDataSourceRequestCallChain({ url, method: 'GET' });
        });
      });
    });
  });
});