Browse Source

Azure Monitor and Log Analytics converted and separated into components (#18259)

* Azure Monitor and Log Analytics converted and separated into components

* Insights creds converted

* remove angular config

* fix workspaces and missing sameas key

* fix workspace save

* set subscriptionId key

* editor fields, load workspaces btn

* workspace load req fields updated

* added tooltip to switch, disable buttons instead of hide

* master merge and tests
Shavonn Brown 6 years ago
parent
commit
f22aaa5518
20 changed files with 2514 additions and 353 deletions
  1. 26 2
      packages/grafana-ui/src/components/Switch/Switch.tsx
  2. 43 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/ConfigEditor.test.tsx
  3. 306 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/ConfigEditor.tsx
  4. 147 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/__snapshots__/ConfigEditor.test.tsx.snap
  5. 5 8
      public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.ts
  6. 80 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AnalyticsConfig.test.tsx
  7. 212 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AnalyticsConfig.tsx
  8. 53 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.test.tsx
  9. 200 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.tsx
  10. 84 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/InsightsConfig.test.tsx
  11. 115 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/InsightsConfig.tsx
  12. 132 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MonitorConfig.tsx
  13. 286 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/AnalyticsConfig.test.tsx.snap
  14. 626 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/AzureCredentialsForm.test.tsx.snap
  15. 188 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/InsightsConfig.test.tsx.snap
  16. 0 127
      public/app/plugins/datasource/grafana-azure-monitor-datasource/config_ctrl.ts
  17. 1 2
      public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts
  18. 0 11
      public/app/plugins/datasource/grafana-azure-monitor-datasource/module.ts
  19. 10 0
      public/app/plugins/datasource/grafana-azure-monitor-datasource/module.tsx
  20. 0 203
      public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/config.html

+ 26 - 2
packages/grafana-ui/src/components/Switch/Switch.tsx

@@ -1,5 +1,7 @@
 import React, { PureComponent } from 'react';
 import React, { PureComponent } from 'react';
 import uniqueId from 'lodash/uniqueId';
 import uniqueId from 'lodash/uniqueId';
+import { Tooltip } from '../Tooltip/Tooltip';
+import * as PopperJS from 'popper.js';
 
 
 export interface Props {
 export interface Props {
   label: string;
   label: string;
@@ -7,6 +9,8 @@ export interface Props {
   className?: string;
   className?: string;
   labelClass?: string;
   labelClass?: string;
   switchClass?: string;
   switchClass?: string;
+  tooltip?: string;
+  tooltipPlacement?: PopperJS.Placement;
   transparent?: boolean;
   transparent?: boolean;
   onChange: (event?: React.SyntheticEvent<HTMLInputElement>) => void;
   onChange: (event?: React.SyntheticEvent<HTMLInputElement>) => void;
 }
 }
@@ -26,7 +30,16 @@ export class Switch extends PureComponent<Props, State> {
   };
   };
 
 
   render() {
   render() {
-    const { labelClass = '', switchClass = '', label, checked, transparent, className } = this.props;
+    const {
+      labelClass = '',
+      switchClass = '',
+      label,
+      checked,
+      transparent,
+      className,
+      tooltip,
+      tooltipPlacement,
+    } = this.props;
 
 
     const labelId = this.state.id;
     const labelId = this.state.id;
     const labelClassName = `gf-form-label ${labelClass} ${transparent ? 'gf-form-label--transparent' : ''} pointer`;
     const labelClassName = `gf-form-label ${labelClass} ${transparent ? 'gf-form-label--transparent' : ''} pointer`;
@@ -35,7 +48,18 @@ export class Switch extends PureComponent<Props, State> {
     return (
     return (
       <div className="gf-form-switch-container-react">
       <div className="gf-form-switch-container-react">
         <label htmlFor={labelId} className={`gf-form gf-form-switch-container ${className || ''}`}>
         <label htmlFor={labelId} className={`gf-form gf-form-switch-container ${className || ''}`}>
-          {label && <div className={labelClassName}>{label}</div>}
+          {label && (
+            <div className={labelClassName}>
+              {label}
+              {tooltip && (
+                <Tooltip placement={tooltipPlacement ? tooltipPlacement : 'auto'} content={tooltip} theme={'info'}>
+                  <div className="gf-form-help-icon gf-form-help-icon--right-normal">
+                    <i className="fa fa-info-circle" />
+                  </div>
+                </Tooltip>
+              )}
+            </div>
+          )}
           <div className={switchClassName}>
           <div className={switchClassName}>
             <input id={labelId} type="checkbox" checked={checked} onChange={this.internalOnChange} />
             <input id={labelId} type="checkbox" checked={checked} onChange={this.internalOnChange} />
             <span className="gf-form-switch__slider" />
             <span className="gf-form-switch__slider" />

+ 43 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/ConfigEditor.test.tsx

@@ -0,0 +1,43 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import ConfigEditor, { Props } from './ConfigEditor';
+
+const setup = () => {
+  const props: Props = {
+    options: {
+      id: 21,
+      orgId: 1,
+      name: 'Azure Monitor-10-10',
+      type: 'grafana-azure-monitor-datasource',
+      typeLogoUrl: '',
+      access: 'proxy',
+      url: '',
+      password: '',
+      user: '',
+      database: '',
+      basicAuth: false,
+      basicAuthUser: '',
+      basicAuthPassword: '',
+      withCredentials: false,
+      isDefault: false,
+      jsonData: {
+        azureLogAnalyticsSameAs: true,
+        cloudName: 'azuremonitor',
+      },
+      secureJsonFields: {},
+      version: 1,
+      readOnly: false,
+    },
+    onOptionsChange: jest.fn(),
+  };
+
+  return shallow(<ConfigEditor {...props} />);
+};
+
+describe('Render', () => {
+  it('should render component', () => {
+    const wrapper = setup();
+
+    expect(wrapper).toMatchSnapshot();
+  });
+});

+ 306 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/ConfigEditor.tsx

@@ -0,0 +1,306 @@
+import React, { PureComponent } from 'react';
+import { SelectableValue } from '@grafana/data';
+import { DataSourcePluginOptionsEditorProps } from '@grafana/ui';
+import { MonitorConfig } from './components/MonitorConfig';
+import { AnalyticsConfig } from './components/AnalyticsConfig';
+import { TemplateSrv } from 'app/features/templating/template_srv';
+import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
+import AzureMonitorDatasource from './azure_monitor/azure_monitor_datasource';
+import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource';
+import { InsightsConfig } from './components/InsightsConfig';
+
+export type Props = DataSourcePluginOptionsEditorProps<any>;
+
+export interface State {
+  config: any;
+  subscriptions: SelectableValue[];
+  logAnalyticsSubscriptions: SelectableValue[];
+  logAnalyticsWorkspaces: SelectableValue[];
+}
+
+export class ConfigEditor extends PureComponent<Props, State> {
+  constructor(props: Props) {
+    super(props);
+
+    const { options } = this.props;
+
+    this.state = {
+      config: ConfigEditor.keyFill(options),
+      subscriptions: [],
+      logAnalyticsSubscriptions: [],
+      logAnalyticsWorkspaces: [],
+    };
+
+    this.backendSrv = getBackendSrv();
+    this.templateSrv = new TemplateSrv();
+
+    if (options.id) {
+      this.state.config.url = '/api/datasources/proxy/' + options.id;
+      this.init();
+    }
+
+    this.updateDatasource(this.state.config);
+  }
+
+  static getDerivedStateFromProps(props: Props, state: State) {
+    return {
+      ...state,
+      config: ConfigEditor.keyFill(props.options),
+    };
+  }
+
+  static keyFill = (options: any) => {
+    options.jsonData.cloudName = options.jsonData.cloudName || 'azuremonitor';
+
+    if (!options.jsonData.hasOwnProperty('azureLogAnalyticsSameAs')) {
+      options.jsonData.azureLogAnalyticsSameAs = true;
+    }
+
+    if (!options.hasOwnProperty('editorSecureJsonData')) {
+      options.editorSecureJsonData = {
+        clientSecret: '',
+        logAnalyticsClientSecret: '',
+        appInsightsApiKey: '',
+      };
+    }
+
+    if (!options.hasOwnProperty('editorJsonData')) {
+      options.editorJsonData = {
+        clientId: options.jsonData.clientId || '',
+        tenantId: options.jsonData.tenantId || '',
+        subscriptionId: options.jsonData.subscriptionId || '',
+        logAnalyticsClientId: options.jsonData.logAnalyticsClientId || '',
+        logAnalyticsDefaultWorkspace: options.jsonData.logAnalyticsDefaultWorkspace || '',
+        logAnalyticsTenantId: options.jsonData.logAnalyticsTenantId || '',
+        logAnalyticsSubscriptionId: options.jsonData.logAnalyticsSubscriptionId || '',
+        appInsightsAppId: options.jsonData.appInsightsAppId || '',
+      };
+    }
+
+    if (!options.hasOwnProperty('secureJsonFields')) {
+      options.secureJsonFields = {
+        clientSecret: false,
+        logAnalyticsClientSecret: false,
+        appInsightsApiKey: false,
+      };
+    }
+
+    return options;
+  };
+
+  backendSrv: BackendSrv = null;
+  templateSrv: TemplateSrv = null;
+
+  init = async () => {
+    await this.getSubscriptions();
+
+    if (!this.state.config.jsonData.azureLogAnalyticsSameAs) {
+      await this.getLogAnalyticsSubscriptions();
+    }
+  };
+
+  updateDatasource = async (config: any) => {
+    for (const j in config.jsonData) {
+      if (config.jsonData[j].length === 0) {
+        delete config.jsonData[j];
+      }
+    }
+
+    for (const k in config.secureJsonData) {
+      if (config.secureJsonData[k].length === 0) {
+        delete config.secureJsonData[k];
+      }
+    }
+
+    for (const m in config.editorJsonData) {
+      if (!config.hasOwnProperty('jsonData')) {
+        config.jsonData = {};
+      }
+      if (config.editorJsonData[m].length === 0) {
+        if (config.hasOwnProperty('jsonData') && config.jsonData.hasOwnProperty(m)) {
+          delete config.jsonData[m];
+        }
+      } else {
+        config.jsonData[m] = config.editorJsonData[m];
+      }
+    }
+
+    for (const l in config.editorSecureJsonData) {
+      if (!config.hasOwnProperty('secureJsonData')) {
+        config.secureJsonData = {};
+      }
+      if (config.editorSecureJsonData[l].length === 0) {
+        if (config.hasOwnProperty('secureJsonData') && config.secureJsonData.hasOwnProperty(l)) {
+          delete config.secureJsonData[l];
+        }
+      } else {
+        config.secureJsonData[l] = config.editorSecureJsonData[l];
+      }
+    }
+
+    this.props.onOptionsChange({
+      ...config,
+    });
+  };
+
+  hasNecessaryCredentials = () => {
+    if (!this.state.config.secureJsonFields.clientSecret && !this.state.config.editorSecureJsonData.clientSecret) {
+      return false;
+    }
+
+    if (!this.state.config.jsonData.clientId || !this.state.config.jsonData.tenantId) {
+      return false;
+    }
+
+    return true;
+  };
+
+  logAnalyticsHasNecessaryCredentials = () => {
+    if (
+      !this.state.config.secureJsonFields.logAnalyticsClientSecret &&
+      !this.state.config.editorSecureJsonData.logAnalyticsClientSecret
+    ) {
+      return false;
+    }
+
+    if (!this.state.config.jsonData.logAnalyticsClientId || !this.state.config.jsonData.logAnalyticsTenantId) {
+      return false;
+    }
+
+    return true;
+  };
+
+  onConfigUpdate = (config: any) => {
+    this.updateDatasource(config);
+  };
+
+  onLoadSubscriptions = async (type?: string) => {
+    await this.backendSrv.put(`/api/datasources/${this.state.config.id}`, this.state.config).then(() => {
+      this.updateDatasource({
+        ...this.state.config,
+        version: this.state.config.version + 1,
+      });
+    });
+
+    if (type && type === 'workspacesloganalytics') {
+      this.getLogAnalyticsSubscriptions();
+    } else {
+      this.getSubscriptions();
+    }
+  };
+
+  getSubscriptions = async () => {
+    if (!this.hasNecessaryCredentials()) {
+      return;
+    }
+
+    const azureMonitorDatasource = new AzureMonitorDatasource(this.state.config, this.backendSrv, this.templateSrv);
+
+    let subscriptions = (await azureMonitorDatasource.getSubscriptions()) || [];
+    subscriptions = subscriptions.map((subscription: any) => {
+      return {
+        value: subscription.value,
+        label: subscription.text,
+      };
+    });
+
+    if (subscriptions && subscriptions.length > 0) {
+      this.setState({ subscriptions });
+
+      this.state.config.editorJsonData.subscriptionId =
+        this.state.config.editorJsonData.subscriptionId || subscriptions[0].value;
+    }
+
+    if (this.state.config.editorJsonData.subscriptionId && this.state.config.jsonData.azureLogAnalyticsSameAs) {
+      await this.getWorkspaces();
+    }
+  };
+
+  getLogAnalyticsSubscriptions = async () => {
+    if (!this.logAnalyticsHasNecessaryCredentials()) {
+      return;
+    }
+
+    const azureMonitorDatasource = new AzureMonitorDatasource(this.state.config, this.backendSrv, this.templateSrv);
+
+    let logAnalyticsSubscriptions = (await azureMonitorDatasource.getSubscriptions('workspacesloganalytics')) || [];
+    logAnalyticsSubscriptions = logAnalyticsSubscriptions.map((subscription: any) => {
+      return {
+        value: subscription.value,
+        label: subscription.text,
+      };
+    });
+
+    if (logAnalyticsSubscriptions && logAnalyticsSubscriptions.length > 0) {
+      this.setState({ logAnalyticsSubscriptions });
+
+      this.state.config.editorJsonData.logAnalyticsSubscriptionId =
+        this.state.config.editorJsonData.logAnalyticsSubscriptionId || logAnalyticsSubscriptions[0].value;
+    }
+
+    if (this.state.config.editorJsonData.logAnalyticsSubscriptionId) {
+      await this.getWorkspaces();
+    }
+  };
+
+  getWorkspaces = async () => {
+    const sameAs =
+      this.state.config.jsonData.azureLogAnalyticsSameAs && this.state.config.editorJsonData.subscriptionId;
+    if (!sameAs && !this.state.config.editorJsonData.logAnalyticsSubscriptionId) {
+      return;
+    }
+
+    const azureLogAnalyticsDatasource = new AzureLogAnalyticsDatasource(
+      this.state.config,
+      this.backendSrv,
+      this.templateSrv
+    );
+
+    let logAnalyticsWorkspaces = await azureLogAnalyticsDatasource.getWorkspaces(
+      sameAs
+        ? this.state.config.editorJsonData.subscriptionId
+        : this.state.config.editorJsonData.logAnalyticsSubscriptionId
+    );
+    logAnalyticsWorkspaces = logAnalyticsWorkspaces.map((workspace: any) => {
+      return {
+        value: workspace.value,
+        label: workspace.text,
+      };
+    });
+
+    if (logAnalyticsWorkspaces.length > 0) {
+      this.setState({ logAnalyticsWorkspaces });
+
+      this.state.config.editorJsonData.logAnalyticsDefaultWorkspace =
+        this.state.config.editorJsonData.logAnalyticsDefaultWorkspace || logAnalyticsWorkspaces[0].value;
+    }
+  };
+
+  render() {
+    const { config, subscriptions, logAnalyticsSubscriptions, logAnalyticsWorkspaces } = this.state;
+
+    return (
+      <>
+        <MonitorConfig
+          datasourceConfig={config}
+          subscriptions={subscriptions}
+          onLoadSubscriptions={this.onLoadSubscriptions}
+          onDatasourceUpdate={this.onConfigUpdate}
+        />
+
+        <AnalyticsConfig
+          datasourceConfig={config}
+          logAnalyticsWorkspaces={logAnalyticsWorkspaces}
+          logAnalyticsSubscriptions={logAnalyticsSubscriptions}
+          onLoadSubscriptions={this.onLoadSubscriptions}
+          onDatasourceUpdate={this.onConfigUpdate}
+          onLoadWorkspaces={this.getWorkspaces}
+        />
+
+        <InsightsConfig datasourceConfig={config} onDatasourceUpdate={this.onConfigUpdate} />
+      </>
+    );
+  }
+}
+
+export default ConfigEditor;

+ 147 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/__snapshots__/ConfigEditor.test.tsx.snap

@@ -0,0 +1,147 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Render should render component 1`] = `
+<Fragment>
+  <MonitorConfig
+    datasourceConfig={
+      Object {
+        "access": "proxy",
+        "basicAuth": false,
+        "basicAuthPassword": "",
+        "basicAuthUser": "",
+        "database": "",
+        "editorJsonData": Object {
+          "appInsightsAppId": "",
+          "clientId": "",
+          "logAnalyticsClientId": "",
+          "logAnalyticsDefaultWorkspace": "",
+          "logAnalyticsSubscriptionId": "",
+          "logAnalyticsTenantId": "",
+          "subscriptionId": "",
+          "tenantId": "",
+        },
+        "editorSecureJsonData": Object {
+          "appInsightsApiKey": "",
+          "clientSecret": "",
+          "logAnalyticsClientSecret": "",
+        },
+        "id": 21,
+        "isDefault": false,
+        "jsonData": Object {
+          "azureLogAnalyticsSameAs": true,
+          "cloudName": "azuremonitor",
+        },
+        "name": "Azure Monitor-10-10",
+        "orgId": 1,
+        "password": "",
+        "readOnly": false,
+        "secureJsonData": Object {},
+        "secureJsonFields": Object {},
+        "type": "grafana-azure-monitor-datasource",
+        "typeLogoUrl": "",
+        "url": "/api/datasources/proxy/21",
+        "user": "",
+        "version": 1,
+        "withCredentials": false,
+      }
+    }
+    onDatasourceUpdate={[Function]}
+    onLoadSubscriptions={[Function]}
+    subscriptions={Array []}
+  />
+  <AnalyticsConfig
+    datasourceConfig={
+      Object {
+        "access": "proxy",
+        "basicAuth": false,
+        "basicAuthPassword": "",
+        "basicAuthUser": "",
+        "database": "",
+        "editorJsonData": Object {
+          "appInsightsAppId": "",
+          "clientId": "",
+          "logAnalyticsClientId": "",
+          "logAnalyticsDefaultWorkspace": "",
+          "logAnalyticsSubscriptionId": "",
+          "logAnalyticsTenantId": "",
+          "subscriptionId": "",
+          "tenantId": "",
+        },
+        "editorSecureJsonData": Object {
+          "appInsightsApiKey": "",
+          "clientSecret": "",
+          "logAnalyticsClientSecret": "",
+        },
+        "id": 21,
+        "isDefault": false,
+        "jsonData": Object {
+          "azureLogAnalyticsSameAs": true,
+          "cloudName": "azuremonitor",
+        },
+        "name": "Azure Monitor-10-10",
+        "orgId": 1,
+        "password": "",
+        "readOnly": false,
+        "secureJsonData": Object {},
+        "secureJsonFields": Object {},
+        "type": "grafana-azure-monitor-datasource",
+        "typeLogoUrl": "",
+        "url": "/api/datasources/proxy/21",
+        "user": "",
+        "version": 1,
+        "withCredentials": false,
+      }
+    }
+    logAnalyticsSubscriptions={Array []}
+    logAnalyticsWorkspaces={Array []}
+    onDatasourceUpdate={[Function]}
+    onLoadSubscriptions={[Function]}
+    onLoadWorkspaces={[Function]}
+  />
+  <InsightsConfig
+    datasourceConfig={
+      Object {
+        "access": "proxy",
+        "basicAuth": false,
+        "basicAuthPassword": "",
+        "basicAuthUser": "",
+        "database": "",
+        "editorJsonData": Object {
+          "appInsightsAppId": "",
+          "clientId": "",
+          "logAnalyticsClientId": "",
+          "logAnalyticsDefaultWorkspace": "",
+          "logAnalyticsSubscriptionId": "",
+          "logAnalyticsTenantId": "",
+          "subscriptionId": "",
+          "tenantId": "",
+        },
+        "editorSecureJsonData": Object {
+          "appInsightsApiKey": "",
+          "clientSecret": "",
+          "logAnalyticsClientSecret": "",
+        },
+        "id": 21,
+        "isDefault": false,
+        "jsonData": Object {
+          "azureLogAnalyticsSameAs": true,
+          "cloudName": "azuremonitor",
+        },
+        "name": "Azure Monitor-10-10",
+        "orgId": 1,
+        "password": "",
+        "readOnly": false,
+        "secureJsonData": Object {},
+        "secureJsonFields": Object {},
+        "type": "grafana-azure-monitor-datasource",
+        "typeLogoUrl": "",
+        "url": "/api/datasources/proxy/21",
+        "user": "",
+        "version": 1,
+        "withCredentials": false,
+      }
+    }
+    onDatasourceUpdate={[Function]}
+  />
+</Fragment>
+`;

+ 5 - 8
public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.ts

@@ -5,7 +5,6 @@ import { AzureMonitorQuery, AzureDataSourceJsonData } from '../types';
 import { DataQueryRequest, DataSourceInstanceSettings } from '@grafana/ui';
 import { DataQueryRequest, DataSourceInstanceSettings } from '@grafana/ui';
 import { BackendSrv } from 'app/core/services/backend_srv';
 import { BackendSrv } from 'app/core/services/backend_srv';
 import { TemplateSrv } from 'app/features/templating/template_srv';
 import { TemplateSrv } from 'app/features/templating/template_srv';
-import { IQService } from 'angular';
 
 
 export default class AzureLogAnalyticsDatasource {
 export default class AzureLogAnalyticsDatasource {
   id: number;
   id: number;
@@ -20,8 +19,7 @@ export default class AzureLogAnalyticsDatasource {
   constructor(
   constructor(
     private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
     private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
     private backendSrv: BackendSrv,
     private backendSrv: BackendSrv,
-    private templateSrv: TemplateSrv,
-    private $q: IQService
+    private templateSrv: TemplateSrv
   ) {
   ) {
     this.id = instanceSettings.id;
     this.id = instanceSettings.id;
     this.baseUrl = this.instanceSettings.jsonData.azureLogAnalyticsSameAs
     this.baseUrl = this.instanceSettings.jsonData.azureLogAnalyticsSameAs
@@ -113,7 +111,7 @@ export default class AzureLogAnalyticsDatasource {
 
 
     const promises = this.doQueries(queries);
     const promises = this.doQueries(queries);
 
 
-    return this.$q.all(promises).then(results => {
+    return Promise.all(promises).then(results => {
       return new ResponseParser(results).parseQueryResult();
       return new ResponseParser(results).parseQueryResult();
     });
     });
   }
   }
@@ -124,8 +122,7 @@ export default class AzureLogAnalyticsDatasource {
 
 
       const promises = this.doQueries(queries);
       const promises = this.doQueries(queries);
 
 
-      return this.$q
-        .all(promises)
+      return Promise.all(promises)
         .then(results => {
         .then(results => {
           return new ResponseParser(results).parseToVariables();
           return new ResponseParser(results).parseToVariables();
         })
         })
@@ -198,7 +195,7 @@ export default class AzureLogAnalyticsDatasource {
 
 
   annotationQuery(options: any) {
   annotationQuery(options: any) {
     if (!options.annotation.rawQuery) {
     if (!options.annotation.rawQuery) {
-      return this.$q.reject({
+      return Promise.reject({
         message: 'Query missing in annotation definition',
         message: 'Query missing in annotation definition',
       });
       });
     }
     }
@@ -207,7 +204,7 @@ export default class AzureLogAnalyticsDatasource {
 
 
     const promises = this.doQueries(queries);
     const promises = this.doQueries(queries);
 
 
-    return this.$q.all(promises).then(results => {
+    return Promise.all(promises).then(results => {
       const annotations = new ResponseParser(results).transformToAnnotations(options);
       const annotations = new ResponseParser(results).transformToAnnotations(options);
       return annotations;
       return annotations;
     });
     });

+ 80 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AnalyticsConfig.test.tsx

@@ -0,0 +1,80 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import AnalyticsConfig, { Props } from './AnalyticsConfig';
+
+const setup = (propOverrides?: object) => {
+  const props: Props = {
+    datasourceConfig: {
+      id: 21,
+      orgId: 1,
+      name: 'Azure Monitor-10-10',
+      type: 'grafana-azure-monitor-datasource',
+      typeLogoUrl: '',
+      access: 'proxy',
+      url: '',
+      password: '',
+      user: '',
+      database: '',
+      basicAuth: false,
+      basicAuthUser: '',
+      basicAuthPassword: '',
+      withCredentials: false,
+      isDefault: false,
+      jsonData: {
+        azureLogAnalyticsSameAs: false,
+      },
+      secureJsonFields: {
+        logAnalyticsClientSecret: false,
+      },
+      editorJsonData: {
+        logAnalyticsDefaultWorkspace: '',
+        logAnalyticsClientSecret: '',
+        logAnalyticsTenantId: '',
+      },
+      editorSecureJsonData: {
+        logAnalyticsClientSecret: '',
+      },
+      version: 1,
+      readOnly: false,
+    },
+    logAnalyticsSubscriptions: [],
+    logAnalyticsWorkspaces: [],
+    onDatasourceUpdate: jest.fn(),
+    onLoadSubscriptions: jest.fn(),
+    onLoadWorkspaces: jest.fn(),
+  };
+
+  Object.assign(props, propOverrides);
+
+  return shallow(<AnalyticsConfig {...props} />);
+};
+
+describe('Render', () => {
+  it('should render component', () => {
+    const wrapper = setup();
+
+    expect(wrapper).toMatchSnapshot();
+  });
+
+  it('should disable log analytics credentials form', () => {
+    const wrapper = setup({
+      jsonData: {
+        azureLogAnalyticsSameAs: true,
+      },
+    });
+    expect(wrapper).toMatchSnapshot();
+  });
+
+  it('should enable azure log analytics load workspaces button', () => {
+    const wrapper = setup({
+      editorJsonData: {
+        logAnalyticsDefaultWorkspace: '',
+        logAnalyticsTenantId: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
+        logAnalyticsClientId: '44693801-6ee6-49de-9b2d-9106972f9572',
+        logAnalyticsSubscriptionId: 'e3fe4fde-ad5e-4d60-9974-e2f3562ffdf2',
+        logAnalyticsClientSecret: 'cddcc020-2c94-460a-a3d0-df3147ffa792',
+      },
+    });
+    expect(wrapper).toMatchSnapshot();
+  });
+});

+ 212 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AnalyticsConfig.tsx

@@ -0,0 +1,212 @@
+import React, { PureComponent } from 'react';
+import { SelectableValue } from '@grafana/data';
+import { AzureCredentialsForm } from './AzureCredentialsForm';
+import { Switch, FormLabel, Select, Button } from '@grafana/ui';
+
+export interface Props {
+  datasourceConfig: any;
+  logAnalyticsSubscriptions: SelectableValue[];
+  logAnalyticsWorkspaces: SelectableValue[];
+  onDatasourceUpdate: (config: any) => void;
+  onLoadSubscriptions: (type?: string) => void;
+  onLoadWorkspaces: (type?: string) => void;
+}
+
+export interface State {
+  config: any;
+  logAnalyticsSubscriptions: SelectableValue[];
+  logAnalyticsWorkspaces: SelectableValue[];
+}
+
+export class AnalyticsConfig extends PureComponent<Props, State> {
+  constructor(props: Props) {
+    super(props);
+
+    const { datasourceConfig } = this.props;
+
+    this.state = {
+      config: datasourceConfig,
+      logAnalyticsSubscriptions: [],
+      logAnalyticsWorkspaces: [],
+    };
+  }
+
+  static getDerivedStateFromProps(props: Props, state: State) {
+    return {
+      ...state,
+      config: props.datasourceConfig,
+      logAnalyticsSubscriptions: props.logAnalyticsSubscriptions,
+      logAnalyticsWorkspaces: props.logAnalyticsWorkspaces,
+    };
+  }
+
+  onLogAnalyticsTenantIdChange = (logAnalyticsTenantId: string) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      editorJsonData: {
+        ...this.state.config.editorJsonData,
+        logAnalyticsTenantId,
+      },
+    });
+  };
+
+  onLogAnalyticsClientIdChange = (logAnalyticsClientId: string) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      editorJsonData: {
+        ...this.state.config.editorJsonData,
+        logAnalyticsClientId,
+      },
+    });
+  };
+
+  onLogAnalyticsClientSecretChange = (logAnalyticsClientSecret: string) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      editorSecureJsonData: {
+        ...this.state.config.editorSecureJsonData,
+        logAnalyticsClientSecret,
+      },
+    });
+  };
+
+  onLogAnalyticsResetClientSecret = () => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      version: this.state.config.version + 1,
+      secureJsonFields: { ...this.state.config.secureJsonFields, logAnalyticsClientSecret: false },
+    });
+  };
+
+  onLogAnalyticsSubscriptionSelect = (logAnalyticsSubscription: SelectableValue<string>) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      editorJsonData: {
+        ...this.state.config.editorJsonData,
+        logAnalyticsSubscriptionId: logAnalyticsSubscription.value,
+      },
+    });
+  };
+
+  onWorkspaceSelectChange = (logAnalyticsDefaultWorkspace: SelectableValue<string>) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      editorJsonData: {
+        ...this.state.config.editorJsonData,
+        logAnalyticsDefaultWorkspace: logAnalyticsDefaultWorkspace.value,
+      },
+    });
+  };
+
+  onAzureLogAnalyticsSameAsChange = (azureLogAnalyticsSameAs: boolean) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      jsonData: {
+        ...this.state.config.jsonData,
+        azureLogAnalyticsSameAs,
+      },
+    });
+  };
+
+  hasWorkspaceRequiredFields = () => {
+    const {
+      config: { editorJsonData, editorSecureJsonData, jsonData, secureJsonFields },
+    } = this.state;
+
+    if (jsonData.azureLogAnalyticsSameAs) {
+      return (
+        editorJsonData.tenantId &&
+        editorJsonData.clientId &&
+        editorJsonData.subscriptionId &&
+        (editorSecureJsonData.clientSecret || secureJsonFields.clientSecret)
+      );
+    }
+
+    return (
+      editorJsonData.logAnalyticsTenantId.length &&
+      editorJsonData.logAnalyticsClientId.length &&
+      editorJsonData.logAnalyticsSubscriptionId &&
+      (secureJsonFields.logAnalyticsClientSecret || editorSecureJsonData.logAnalyticsClientSecret)
+    );
+  };
+
+  render() {
+    const {
+      config: { editorJsonData, editorSecureJsonData, jsonData, secureJsonFields },
+      logAnalyticsSubscriptions,
+      logAnalyticsWorkspaces,
+    } = this.state;
+
+    const addtlAttrs = {
+      ...(jsonData.azureLogAnalyticsSameAs && {
+        tooltip: 'Workspaces are pulled from default subscription selected above.',
+      }),
+    };
+    return (
+      <>
+        <h3 className="page-heading">Azure Log Analytics API Details</h3>
+        <Switch
+          label="Same details as Azure Monitor API"
+          checked={jsonData.azureLogAnalyticsSameAs}
+          onChange={event => this.onAzureLogAnalyticsSameAsChange(!jsonData.azureLogAnalyticsSameAs)}
+          {...addtlAttrs}
+        />
+        {!jsonData.azureLogAnalyticsSameAs && (
+          <AzureCredentialsForm
+            subscriptionOptions={logAnalyticsSubscriptions}
+            selectedSubscription={editorJsonData.logAnalyticsSubscriptionId}
+            tenantId={editorJsonData.logAnalyticsTenantId}
+            clientId={editorJsonData.logAnalyticsClientId}
+            clientSecret={editorSecureJsonData.logAnalyticsClientSecret}
+            clientSecretConfigured={secureJsonFields.logAnalyticsClientSecret}
+            onSubscriptionSelectChange={this.onLogAnalyticsSubscriptionSelect}
+            onTenantIdChange={this.onLogAnalyticsTenantIdChange}
+            onClientIdChange={this.onLogAnalyticsClientIdChange}
+            onClientSecretChange={this.onLogAnalyticsClientSecretChange}
+            onResetClientSecret={this.onLogAnalyticsResetClientSecret}
+            onLoadSubscriptions={() => this.props.onLoadSubscriptions('workspacesloganalytics')}
+          />
+        )}
+        <div className="gf-form-group">
+          <div className="gf-form-inline">
+            <div className="gf-form">
+              <FormLabel
+                className="width-12"
+                tooltip="Choose the default/preferred Workspace for Azure Log Analytics queries."
+              >
+                Default Workspace
+              </FormLabel>
+              <div className="width-25">
+                <Select
+                  value={logAnalyticsWorkspaces.find(
+                    workspace => workspace.value === editorJsonData.logAnalyticsDefaultWorkspace
+                  )}
+                  options={logAnalyticsWorkspaces}
+                  defaultValue={editorJsonData.logAnalyticsDefaultWorkspace}
+                  onChange={this.onWorkspaceSelectChange}
+                />
+              </div>
+            </div>
+          </div>
+          <div className="gf-form-inline">
+            <div className="gf-form">
+              <div className="max-width-30 gf-form-inline">
+                <Button
+                  variant="secondary"
+                  size="sm"
+                  type="button"
+                  onClick={() => this.props.onLoadWorkspaces()}
+                  disabled={!this.hasWorkspaceRequiredFields()}
+                >
+                  Load Workspaces
+                </Button>
+              </div>
+            </div>
+          </div>
+        </div>
+      </>
+    );
+  }
+}
+
+export default AnalyticsConfig;

+ 53 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.test.tsx

@@ -0,0 +1,53 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import AzureCredentialsForm, { Props } from './AzureCredentialsForm';
+
+const setup = (propOverrides?: object) => {
+  const props: Props = {
+    selectedAzureCloud: 'azuremonitor',
+    selectedSubscription: '44693801-6ee6-49de-9b2d-9106972f9572',
+    azureCloudOptions: [
+      { value: 'azuremonitor', label: 'Azure' },
+      { value: 'govazuremonitor', label: 'Azure US Government' },
+      { value: 'germanyazuremonitor', label: 'Azure Germany' },
+      { value: 'chinaazuremonitor', label: 'Azure China' },
+    ],
+    tenantId: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
+    clientId: '77409fad-c0a9-45df-9e25-f1ff95af6554',
+    clientSecret: '',
+    clientSecretConfigured: false,
+    subscriptionOptions: [],
+    onAzureCloudChange: jest.fn(),
+    onSubscriptionSelectChange: jest.fn(),
+    onTenantIdChange: jest.fn(),
+    onClientIdChange: jest.fn(),
+    onClientSecretChange: jest.fn(),
+    onResetClientSecret: jest.fn(),
+    onLoadSubscriptions: jest.fn(),
+  };
+
+  Object.assign(props, propOverrides);
+
+  return shallow(<AzureCredentialsForm {...props} />);
+};
+
+describe('Render', () => {
+  it('should render component', () => {
+    const wrapper = setup();
+    expect(wrapper).toMatchSnapshot();
+  });
+
+  it('should disable azure monitor secret input', () => {
+    const wrapper = setup({
+      clientSecretConfigured: true,
+    });
+    expect(wrapper).toMatchSnapshot();
+  });
+
+  it('should enable azure monitor load subscriptions button', () => {
+    const wrapper = setup({
+      clientSecret: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
+    });
+    expect(wrapper).toMatchSnapshot();
+  });
+});

+ 200 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.tsx

@@ -0,0 +1,200 @@
+import React, { ChangeEvent, PureComponent } from 'react';
+import { SelectableValue } from '@grafana/data';
+import { Input, FormLabel, Select, Button } from '@grafana/ui';
+
+export interface Props {
+  selectedAzureCloud?: string;
+  selectedSubscription?: string;
+  azureCloudOptions?: SelectableValue[];
+  tenantId: string;
+  clientId: string;
+  clientSecret: string;
+  clientSecretConfigured: boolean;
+  subscriptionOptions?: SelectableValue[];
+  onAzureCloudChange?: (value: SelectableValue<string>) => void;
+  onSubscriptionSelectChange?: (value: SelectableValue<string>) => void;
+  onTenantIdChange: (tenantId: string) => void;
+  onClientIdChange: (clientId: string) => void;
+  onClientSecretChange: (clientSecret: string) => void;
+  onResetClientSecret: () => void;
+  onLoadSubscriptions?: () => void;
+}
+
+export interface State {
+  selectedAzureCloud?: string;
+  selectedSubscription: string;
+  tenantId: string;
+  clientId: string;
+  clientSecret: string;
+  clientSecretConfigured: boolean;
+}
+
+export class AzureCredentialsForm extends PureComponent<Props, State> {
+  constructor(props: Props) {
+    super(props);
+
+    const {
+      selectedAzureCloud,
+      selectedSubscription,
+      tenantId,
+      clientId,
+      clientSecret,
+      clientSecretConfigured,
+    } = this.props;
+
+    this.state = {
+      selectedAzureCloud,
+      selectedSubscription,
+      tenantId,
+      clientId,
+      clientSecret,
+      clientSecretConfigured,
+    };
+  }
+
+  static getDerivedStateFromProps(nextProps: Props, prevState: Props) {
+    const { selectedAzureCloud, tenantId, clientId, clientSecret, clientSecretConfigured } = nextProps;
+    return {
+      selectedAzureCloud,
+      tenantId,
+      clientId,
+      clientSecret,
+      clientSecretConfigured,
+    };
+  }
+
+  render() {
+    const {
+      azureCloudOptions,
+      subscriptionOptions,
+      onAzureCloudChange,
+      onSubscriptionSelectChange,
+      onTenantIdChange,
+      onClientIdChange,
+      onClientSecretChange,
+      onResetClientSecret,
+      onLoadSubscriptions,
+    } = this.props;
+    const {
+      selectedAzureCloud,
+      selectedSubscription,
+      tenantId,
+      clientId,
+      clientSecret,
+      clientSecretConfigured,
+    } = this.state;
+    const hasRequiredFields = tenantId && clientId && (clientSecret || clientSecretConfigured);
+    const hasSubscriptions = onLoadSubscriptions && subscriptionOptions;
+    return (
+      <>
+        <div className="gf-form-group">
+          {azureCloudOptions && (
+            <div className="gf-form-inline">
+              <div className="gf-form">
+                <FormLabel className="width-12" tooltip="Choose an Azure Cloud.">
+                  Azure Cloud
+                </FormLabel>
+                <Select
+                  className="width-15"
+                  value={azureCloudOptions.find(azureCloud => azureCloud.value === selectedAzureCloud)}
+                  options={azureCloudOptions}
+                  defaultValue={selectedAzureCloud}
+                  onChange={onAzureCloudChange}
+                />
+              </div>
+            </div>
+          )}
+          <div className="gf-form-inline">
+            <div className="gf-form">
+              <FormLabel className="width-12">Directory (tenant) ID</FormLabel>
+              <div className="width-15">
+                <Input
+                  className="width-30"
+                  placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+                  value={tenantId}
+                  onChange={(event: ChangeEvent<HTMLInputElement>) => onTenantIdChange(event.target.value)}
+                />
+              </div>
+            </div>
+          </div>
+          <div className="gf-form-inline">
+            <div className="gf-form">
+              <FormLabel className="width-12">Application (client) ID</FormLabel>
+              <div className="width-15">
+                <Input
+                  className="width-30"
+                  placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+                  value={clientId}
+                  onChange={(event: ChangeEvent<HTMLInputElement>) => onClientIdChange(event.target.value)}
+                />
+              </div>
+            </div>
+          </div>
+          {clientSecretConfigured ? (
+            <div className="gf-form-inline">
+              <div className="gf-form">
+                <FormLabel className="width-12">Client Secret</FormLabel>
+                <Input className="width-25" placeholder="configured" disabled={true} />
+              </div>
+              <div className="gf-form">
+                <div className="max-width-30 gf-form-inline">
+                  <Button variant="secondary" type="button" onClick={onResetClientSecret}>
+                    reset
+                  </Button>
+                </div>
+              </div>
+            </div>
+          ) : (
+            <div className="gf-form-inline">
+              <div className="gf-form">
+                <FormLabel className="width-12">Client Secret</FormLabel>
+                <div className="width-15">
+                  <Input
+                    className="width-30"
+                    placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+                    value={clientSecret}
+                    onChange={(event: ChangeEvent<HTMLInputElement>) => onClientSecretChange(event.target.value)}
+                  />
+                </div>
+              </div>
+            </div>
+          )}
+          {hasSubscriptions && (
+            <>
+              <div className="gf-form-inline">
+                <div className="gf-form">
+                  <FormLabel className="width-12">Default Subscription</FormLabel>
+                  <div className="width-25">
+                    <Select
+                      value={subscriptionOptions.find(subscription => subscription.value === selectedSubscription)}
+                      options={subscriptionOptions}
+                      defaultValue={selectedSubscription}
+                      onChange={onSubscriptionSelectChange}
+                    />
+                  </div>
+                </div>
+              </div>
+              <div className="gf-form-inline">
+                <div className="gf-form">
+                  <div className="max-width-30 gf-form-inline">
+                    <Button
+                      variant="secondary"
+                      size="sm"
+                      type="button"
+                      onClick={onLoadSubscriptions}
+                      disabled={!hasRequiredFields}
+                    >
+                      Load Subscriptions
+                    </Button>
+                  </div>
+                </div>
+              </div>
+            </>
+          )}
+        </div>
+      </>
+    );
+  }
+}
+
+export default AzureCredentialsForm;

+ 84 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/components/InsightsConfig.test.tsx

@@ -0,0 +1,84 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import InsightsConfig, { Props } from './InsightsConfig';
+
+const setup = (propOverrides?: object) => {
+  const props: Props = {
+    datasourceConfig: {
+      id: 21,
+      orgId: 1,
+      name: 'Azure Monitor-10-10',
+      type: 'grafana-azure-monitor-datasource',
+      typeLogoUrl: '',
+      access: 'proxy',
+      url: '',
+      password: '',
+      user: '',
+      database: '',
+      basicAuth: false,
+      basicAuthUser: '',
+      basicAuthPassword: '',
+      withCredentials: false,
+      isDefault: false,
+      jsonData: {},
+      secureJsonFields: {
+        appInsightsApiKey: false,
+      },
+      editorJsonData: {
+        appInsightsAppId: 'cddcc020-2c94-460a-a3d0-df3147ffa792',
+      },
+      editorSecureJsonData: {
+        appInsightsApiKey: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
+      },
+      version: 1,
+      readOnly: false,
+    },
+    onDatasourceUpdate: jest.fn(),
+  };
+
+  Object.assign(props, propOverrides);
+
+  return shallow(<InsightsConfig {...props} />);
+};
+
+describe('Render', () => {
+  it('should render component', () => {
+    const wrapper = setup();
+
+    expect(wrapper).toMatchSnapshot();
+  });
+
+  it('should disable insights api key input', () => {
+    const wrapper = setup({
+      datasourceConfig: {
+        secureJsonFields: {
+          appInsightsApiKey: true,
+        },
+        editorJsonData: {
+          appInsightsAppId: 'cddcc020-2c94-460a-a3d0-df3147ffa792',
+        },
+        editorSecureJsonData: {
+          appInsightsApiKey: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
+        },
+      },
+    });
+    expect(wrapper).toMatchSnapshot();
+  });
+
+  it('should enable insights api key input', () => {
+    const wrapper = setup({
+      datasourceConfig: {
+        secureJsonFields: {
+          appInsightsApiKey: false,
+        },
+        editorJsonData: {
+          appInsightsAppId: 'cddcc020-2c94-460a-a3d0-df3147ffa792',
+        },
+        editorSecureJsonData: {
+          appInsightsApiKey: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
+        },
+      },
+    });
+    expect(wrapper).toMatchSnapshot();
+  });
+});

+ 115 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/components/InsightsConfig.tsx

@@ -0,0 +1,115 @@
+import React, { PureComponent } from 'react';
+import { FormLabel, Button, Input } from '@grafana/ui';
+
+export interface Props {
+  datasourceConfig: any;
+  onDatasourceUpdate: (config: any) => void;
+}
+
+export interface State {
+  config: any;
+}
+
+export class InsightsConfig extends PureComponent<Props, State> {
+  constructor(props: Props) {
+    super(props);
+
+    const { datasourceConfig } = this.props;
+
+    this.state = {
+      config: datasourceConfig,
+    };
+  }
+
+  static getDerivedStateFromProps(props: Props, state: State) {
+    return {
+      ...state,
+      config: props.datasourceConfig,
+    };
+  }
+
+  onAppInsightsAppIdChange = (appInsightsAppId: string) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      editorJsonData: {
+        ...this.state.config.editorJsonData,
+        appInsightsAppId,
+      },
+    });
+  };
+
+  onAppInsightsApiKeyChange = (appInsightsApiKey: string) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      editorSecureJsonData: {
+        ...this.state.config.editorSecureJsonData,
+        appInsightsApiKey,
+      },
+    });
+  };
+
+  onAppInsightsResetApiKey = () => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      version: this.state.config.version + 1,
+      secureJsonFields: {
+        ...this.state.config.secureJsonFields,
+        appInsightsApiKey: false,
+      },
+    });
+  };
+
+  render() {
+    const { config } = this.state;
+    return (
+      <>
+        <h3 className="page-heading">Application Insights Details</h3>
+        <div className="gf-form-group">
+          {config.secureJsonFields.appInsightsApiKey ? (
+            <div className="gf-form-inline">
+              <div className="gf-form">
+                <FormLabel className="width-12">API Key</FormLabel>
+                <Input className="width-25" placeholder="configured" disabled={true} />
+              </div>
+              <div className="gf-form">
+                <div className="max-width-30 gf-form-inline">
+                  <Button variant="secondary" type="button" onClick={this.onAppInsightsResetApiKey}>
+                    reset
+                  </Button>
+                </div>
+              </div>
+            </div>
+          ) : (
+            <div className="gf-form-inline">
+              <div className="gf-form">
+                <FormLabel className="width-12">API Key</FormLabel>
+                <div className="width-15">
+                  <Input
+                    className="width-30"
+                    placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+                    value={config.editorSecureJsonData.appInsightsApiKey}
+                    onChange={event => this.onAppInsightsApiKeyChange(event.target.value)}
+                  />
+                </div>
+              </div>
+            </div>
+          )}
+          <div className="gf-form-inline">
+            <div className="gf-form">
+              <FormLabel className="width-12">Application ID</FormLabel>
+              <div className="width-15">
+                <Input
+                  className="width-30"
+                  value={config.editorJsonData.appInsightsAppId}
+                  onChange={event => this.onAppInsightsAppIdChange(event.target.value)}
+                />
+              </div>
+            </div>
+          </div>
+        </div>
+      </>
+    );
+  }
+}
+
+export default InsightsConfig;

+ 132 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MonitorConfig.tsx

@@ -0,0 +1,132 @@
+import React, { PureComponent } from 'react';
+import { SelectableValue } from '@grafana/data';
+import { AzureCredentialsForm } from './AzureCredentialsForm';
+
+export interface Props {
+  datasourceConfig: any;
+  subscriptions: SelectableValue[];
+  onDatasourceUpdate: (config: any) => void;
+  onLoadSubscriptions: () => void;
+}
+
+export interface State {
+  config: any;
+  azureClouds: SelectableValue[];
+  subscriptions: SelectableValue[];
+}
+
+export class MonitorConfig extends PureComponent<Props, State> {
+  constructor(props: Props) {
+    super(props);
+
+    const { datasourceConfig } = this.props;
+
+    this.state = {
+      config: datasourceConfig,
+      azureClouds: [
+        { value: 'azuremonitor', label: 'Azure' },
+        { value: 'govazuremonitor', label: 'Azure US Government' },
+        { value: 'germanyazuremonitor', label: 'Azure Germany' },
+        { value: 'chinaazuremonitor', label: 'Azure China' },
+      ],
+      subscriptions: [],
+    };
+  }
+
+  static getDerivedStateFromProps(props: Props, state: State) {
+    return {
+      ...state,
+      config: props.datasourceConfig,
+      subscriptions: props.subscriptions,
+    };
+  }
+
+  onAzureCloudSelect = (cloudName: SelectableValue<string>) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      jsonData: {
+        ...this.state.config.jsonData,
+        cloudName,
+      },
+    });
+  };
+
+  onTenantIdChange = (tenantId: string) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      editorJsonData: {
+        ...this.state.config.editorJsonData,
+        tenantId,
+      },
+    });
+  };
+
+  onClientIdChange = (clientId: string) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      editorJsonData: {
+        ...this.state.config.editorJsonData,
+        clientId,
+      },
+    });
+  };
+
+  onClientSecretChange = (clientSecret: string) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      editorSecureJsonData: {
+        ...this.state.config.editorSecureJsonData,
+        clientSecret,
+      },
+    });
+  };
+
+  onResetClientSecret = () => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      version: this.state.config.version + 1,
+      secureJsonFields: {
+        ...this.state.config.secureJsonFields,
+        clientSecret: false,
+      },
+    });
+  };
+
+  onSubscriptionSelect = (subscription: SelectableValue<string>) => {
+    this.props.onDatasourceUpdate({
+      ...this.state.config,
+      editorJsonData: {
+        ...this.state.config.editorJsonData,
+        subscriptionId: subscription.value,
+      },
+    });
+  };
+
+  render() {
+    const { azureClouds, config, subscriptions } = this.state;
+    return (
+      <>
+        <h3 className="page-heading">Azure Monitor Details</h3>
+        <AzureCredentialsForm
+          selectedAzureCloud={config.jsonData.cloudName}
+          azureCloudOptions={azureClouds}
+          subscriptionOptions={subscriptions}
+          selectedSubscription={config.editorJsonData.subscriptionId}
+          tenantId={config.editorJsonData.tenantId}
+          clientId={config.editorJsonData.clientId}
+          clientSecret={config.editorSecureJsonData.clientSecret}
+          clientSecretConfigured={config.secureJsonFields.clientSecret}
+          onAzureCloudChange={this.onAzureCloudSelect}
+          onSubscriptionSelectChange={this.onSubscriptionSelect}
+          onTenantIdChange={this.onTenantIdChange}
+          onClientIdChange={this.onClientIdChange}
+          onClientSecretChange={this.onClientSecretChange}
+          onResetClientSecret={this.onResetClientSecret}
+          onLoadSubscriptions={this.props.onLoadSubscriptions}
+        />
+      </>
+    );
+  }
+}
+
+export default MonitorConfig;

+ 286 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/AnalyticsConfig.test.tsx.snap

@@ -0,0 +1,286 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Render should disable log analytics credentials form 1`] = `
+<Fragment>
+  <h3
+    className="page-heading"
+  >
+    Azure Log Analytics API Details
+  </h3>
+  <Switch
+    checked={false}
+    label="Same details as Azure Monitor API"
+    onChange={[Function]}
+  />
+  <AzureCredentialsForm
+    clientSecret=""
+    clientSecretConfigured={false}
+    onClientIdChange={[Function]}
+    onClientSecretChange={[Function]}
+    onLoadSubscriptions={[Function]}
+    onResetClientSecret={[Function]}
+    onSubscriptionSelectChange={[Function]}
+    onTenantIdChange={[Function]}
+    subscriptionOptions={Array []}
+    tenantId=""
+  />
+  <div
+    className="gf-form-group"
+  >
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+          tooltip="Choose the default/preferred Workspace for Azure Log Analytics queries."
+        >
+          Default Workspace
+        </Component>
+        <div
+          className="width-25"
+        >
+          <Select
+            autoFocus={false}
+            backspaceRemovesValue={true}
+            className=""
+            components={
+              Object {
+                "Group": [Function],
+                "IndicatorsContainer": [Function],
+                "MenuList": [Function],
+                "Option": [Function],
+                "SingleValue": [Function],
+              }
+            }
+            defaultValue=""
+            isClearable={false}
+            isDisabled={false}
+            isLoading={false}
+            isMulti={false}
+            isSearchable={true}
+            maxMenuHeight={300}
+            onChange={[Function]}
+            openMenuOnFocus={false}
+            options={Array []}
+            tabSelectsValue={true}
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <div
+          className="max-width-30 gf-form-inline"
+        >
+          <Button
+            disabled={true}
+            onClick={[Function]}
+            size="sm"
+            type="button"
+            variant="secondary"
+          >
+            Load Workspaces
+          </Button>
+        </div>
+      </div>
+    </div>
+  </div>
+</Fragment>
+`;
+
+exports[`Render should enable azure log analytics load workspaces button 1`] = `
+<Fragment>
+  <h3
+    className="page-heading"
+  >
+    Azure Log Analytics API Details
+  </h3>
+  <Switch
+    checked={false}
+    label="Same details as Azure Monitor API"
+    onChange={[Function]}
+  />
+  <AzureCredentialsForm
+    clientSecret=""
+    clientSecretConfigured={false}
+    onClientIdChange={[Function]}
+    onClientSecretChange={[Function]}
+    onLoadSubscriptions={[Function]}
+    onResetClientSecret={[Function]}
+    onSubscriptionSelectChange={[Function]}
+    onTenantIdChange={[Function]}
+    subscriptionOptions={Array []}
+    tenantId=""
+  />
+  <div
+    className="gf-form-group"
+  >
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+          tooltip="Choose the default/preferred Workspace for Azure Log Analytics queries."
+        >
+          Default Workspace
+        </Component>
+        <div
+          className="width-25"
+        >
+          <Select
+            autoFocus={false}
+            backspaceRemovesValue={true}
+            className=""
+            components={
+              Object {
+                "Group": [Function],
+                "IndicatorsContainer": [Function],
+                "MenuList": [Function],
+                "Option": [Function],
+                "SingleValue": [Function],
+              }
+            }
+            defaultValue=""
+            isClearable={false}
+            isDisabled={false}
+            isLoading={false}
+            isMulti={false}
+            isSearchable={true}
+            maxMenuHeight={300}
+            onChange={[Function]}
+            openMenuOnFocus={false}
+            options={Array []}
+            tabSelectsValue={true}
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <div
+          className="max-width-30 gf-form-inline"
+        >
+          <Button
+            disabled={true}
+            onClick={[Function]}
+            size="sm"
+            type="button"
+            variant="secondary"
+          >
+            Load Workspaces
+          </Button>
+        </div>
+      </div>
+    </div>
+  </div>
+</Fragment>
+`;
+
+exports[`Render should render component 1`] = `
+<Fragment>
+  <h3
+    className="page-heading"
+  >
+    Azure Log Analytics API Details
+  </h3>
+  <Switch
+    checked={false}
+    label="Same details as Azure Monitor API"
+    onChange={[Function]}
+  />
+  <AzureCredentialsForm
+    clientSecret=""
+    clientSecretConfigured={false}
+    onClientIdChange={[Function]}
+    onClientSecretChange={[Function]}
+    onLoadSubscriptions={[Function]}
+    onResetClientSecret={[Function]}
+    onSubscriptionSelectChange={[Function]}
+    onTenantIdChange={[Function]}
+    subscriptionOptions={Array []}
+    tenantId=""
+  />
+  <div
+    className="gf-form-group"
+  >
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+          tooltip="Choose the default/preferred Workspace for Azure Log Analytics queries."
+        >
+          Default Workspace
+        </Component>
+        <div
+          className="width-25"
+        >
+          <Select
+            autoFocus={false}
+            backspaceRemovesValue={true}
+            className=""
+            components={
+              Object {
+                "Group": [Function],
+                "IndicatorsContainer": [Function],
+                "MenuList": [Function],
+                "Option": [Function],
+                "SingleValue": [Function],
+              }
+            }
+            defaultValue=""
+            isClearable={false}
+            isDisabled={false}
+            isLoading={false}
+            isMulti={false}
+            isSearchable={true}
+            maxMenuHeight={300}
+            onChange={[Function]}
+            openMenuOnFocus={false}
+            options={Array []}
+            tabSelectsValue={true}
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <div
+          className="max-width-30 gf-form-inline"
+        >
+          <Button
+            disabled={true}
+            onClick={[Function]}
+            size="sm"
+            type="button"
+            variant="secondary"
+          >
+            Load Workspaces
+          </Button>
+        </div>
+      </div>
+    </div>
+  </div>
+</Fragment>
+`;

+ 626 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/AzureCredentialsForm.test.tsx.snap

@@ -0,0 +1,626 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Render should disable azure monitor secret input 1`] = `
+<Fragment>
+  <div
+    className="gf-form-group"
+  >
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+          tooltip="Choose an Azure Cloud."
+        >
+          Azure Cloud
+        </Component>
+        <Select
+          autoFocus={false}
+          backspaceRemovesValue={true}
+          className="width-15"
+          components={
+            Object {
+              "Group": [Function],
+              "IndicatorsContainer": [Function],
+              "MenuList": [Function],
+              "Option": [Function],
+              "SingleValue": [Function],
+            }
+          }
+          defaultValue="azuremonitor"
+          isClearable={false}
+          isDisabled={false}
+          isLoading={false}
+          isMulti={false}
+          isSearchable={true}
+          maxMenuHeight={300}
+          onChange={[MockFunction]}
+          openMenuOnFocus={false}
+          options={
+            Array [
+              Object {
+                "label": "Azure",
+                "value": "azuremonitor",
+              },
+              Object {
+                "label": "Azure US Government",
+                "value": "govazuremonitor",
+              },
+              Object {
+                "label": "Azure Germany",
+                "value": "germanyazuremonitor",
+              },
+              Object {
+                "label": "Azure China",
+                "value": "chinaazuremonitor",
+              },
+            ]
+          }
+          tabSelectsValue={true}
+          value={
+            Object {
+              "label": "Azure",
+              "value": "azuremonitor",
+            }
+          }
+        />
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Directory (tenant) ID
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+            value="e7f3f661-a933-4b3f-8176-51c4f982ec48"
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Application (client) ID
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+            value="77409fad-c0a9-45df-9e25-f1ff95af6554"
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Client Secret
+        </Component>
+        <Input
+          className="width-25"
+          disabled={true}
+          placeholder="configured"
+        />
+      </div>
+      <div
+        className="gf-form"
+      >
+        <div
+          className="max-width-30 gf-form-inline"
+        >
+          <Button
+            onClick={[MockFunction]}
+            type="button"
+            variant="secondary"
+          >
+            reset
+          </Button>
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Default Subscription
+        </Component>
+        <div
+          className="width-25"
+        >
+          <Select
+            autoFocus={false}
+            backspaceRemovesValue={true}
+            className=""
+            components={
+              Object {
+                "Group": [Function],
+                "IndicatorsContainer": [Function],
+                "MenuList": [Function],
+                "Option": [Function],
+                "SingleValue": [Function],
+              }
+            }
+            defaultValue="44693801-6ee6-49de-9b2d-9106972f9572"
+            isClearable={false}
+            isDisabled={false}
+            isLoading={false}
+            isMulti={false}
+            isSearchable={true}
+            maxMenuHeight={300}
+            onChange={[MockFunction]}
+            openMenuOnFocus={false}
+            options={Array []}
+            tabSelectsValue={true}
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <div
+          className="max-width-30 gf-form-inline"
+        >
+          <Button
+            disabled={false}
+            onClick={[MockFunction]}
+            size="sm"
+            type="button"
+            variant="secondary"
+          >
+            Load Subscriptions
+          </Button>
+        </div>
+      </div>
+    </div>
+  </div>
+</Fragment>
+`;
+
+exports[`Render should enable azure monitor load subscriptions button 1`] = `
+<Fragment>
+  <div
+    className="gf-form-group"
+  >
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+          tooltip="Choose an Azure Cloud."
+        >
+          Azure Cloud
+        </Component>
+        <Select
+          autoFocus={false}
+          backspaceRemovesValue={true}
+          className="width-15"
+          components={
+            Object {
+              "Group": [Function],
+              "IndicatorsContainer": [Function],
+              "MenuList": [Function],
+              "Option": [Function],
+              "SingleValue": [Function],
+            }
+          }
+          defaultValue="azuremonitor"
+          isClearable={false}
+          isDisabled={false}
+          isLoading={false}
+          isMulti={false}
+          isSearchable={true}
+          maxMenuHeight={300}
+          onChange={[MockFunction]}
+          openMenuOnFocus={false}
+          options={
+            Array [
+              Object {
+                "label": "Azure",
+                "value": "azuremonitor",
+              },
+              Object {
+                "label": "Azure US Government",
+                "value": "govazuremonitor",
+              },
+              Object {
+                "label": "Azure Germany",
+                "value": "germanyazuremonitor",
+              },
+              Object {
+                "label": "Azure China",
+                "value": "chinaazuremonitor",
+              },
+            ]
+          }
+          tabSelectsValue={true}
+          value={
+            Object {
+              "label": "Azure",
+              "value": "azuremonitor",
+            }
+          }
+        />
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Directory (tenant) ID
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+            value="e7f3f661-a933-4b3f-8176-51c4f982ec48"
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Application (client) ID
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+            value="77409fad-c0a9-45df-9e25-f1ff95af6554"
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Client Secret
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+            value="e7f3f661-a933-4b3f-8176-51c4f982ec48"
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Default Subscription
+        </Component>
+        <div
+          className="width-25"
+        >
+          <Select
+            autoFocus={false}
+            backspaceRemovesValue={true}
+            className=""
+            components={
+              Object {
+                "Group": [Function],
+                "IndicatorsContainer": [Function],
+                "MenuList": [Function],
+                "Option": [Function],
+                "SingleValue": [Function],
+              }
+            }
+            defaultValue="44693801-6ee6-49de-9b2d-9106972f9572"
+            isClearable={false}
+            isDisabled={false}
+            isLoading={false}
+            isMulti={false}
+            isSearchable={true}
+            maxMenuHeight={300}
+            onChange={[MockFunction]}
+            openMenuOnFocus={false}
+            options={Array []}
+            tabSelectsValue={true}
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <div
+          className="max-width-30 gf-form-inline"
+        >
+          <Button
+            disabled={false}
+            onClick={[MockFunction]}
+            size="sm"
+            type="button"
+            variant="secondary"
+          >
+            Load Subscriptions
+          </Button>
+        </div>
+      </div>
+    </div>
+  </div>
+</Fragment>
+`;
+
+exports[`Render should render component 1`] = `
+<Fragment>
+  <div
+    className="gf-form-group"
+  >
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+          tooltip="Choose an Azure Cloud."
+        >
+          Azure Cloud
+        </Component>
+        <Select
+          autoFocus={false}
+          backspaceRemovesValue={true}
+          className="width-15"
+          components={
+            Object {
+              "Group": [Function],
+              "IndicatorsContainer": [Function],
+              "MenuList": [Function],
+              "Option": [Function],
+              "SingleValue": [Function],
+            }
+          }
+          defaultValue="azuremonitor"
+          isClearable={false}
+          isDisabled={false}
+          isLoading={false}
+          isMulti={false}
+          isSearchable={true}
+          maxMenuHeight={300}
+          onChange={[MockFunction]}
+          openMenuOnFocus={false}
+          options={
+            Array [
+              Object {
+                "label": "Azure",
+                "value": "azuremonitor",
+              },
+              Object {
+                "label": "Azure US Government",
+                "value": "govazuremonitor",
+              },
+              Object {
+                "label": "Azure Germany",
+                "value": "germanyazuremonitor",
+              },
+              Object {
+                "label": "Azure China",
+                "value": "chinaazuremonitor",
+              },
+            ]
+          }
+          tabSelectsValue={true}
+          value={
+            Object {
+              "label": "Azure",
+              "value": "azuremonitor",
+            }
+          }
+        />
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Directory (tenant) ID
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+            value="e7f3f661-a933-4b3f-8176-51c4f982ec48"
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Application (client) ID
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+            value="77409fad-c0a9-45df-9e25-f1ff95af6554"
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Client Secret
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+            value=""
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Default Subscription
+        </Component>
+        <div
+          className="width-25"
+        >
+          <Select
+            autoFocus={false}
+            backspaceRemovesValue={true}
+            className=""
+            components={
+              Object {
+                "Group": [Function],
+                "IndicatorsContainer": [Function],
+                "MenuList": [Function],
+                "Option": [Function],
+                "SingleValue": [Function],
+              }
+            }
+            defaultValue="44693801-6ee6-49de-9b2d-9106972f9572"
+            isClearable={false}
+            isDisabled={false}
+            isLoading={false}
+            isMulti={false}
+            isSearchable={true}
+            maxMenuHeight={300}
+            onChange={[MockFunction]}
+            openMenuOnFocus={false}
+            options={Array []}
+            tabSelectsValue={true}
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <div
+          className="max-width-30 gf-form-inline"
+        >
+          <Button
+            disabled={true}
+            onClick={[MockFunction]}
+            size="sm"
+            type="button"
+            variant="secondary"
+          >
+            Load Subscriptions
+          </Button>
+        </div>
+      </div>
+    </div>
+  </div>
+</Fragment>
+`;

+ 188 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/InsightsConfig.test.tsx.snap

@@ -0,0 +1,188 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Render should disable insights api key input 1`] = `
+<Fragment>
+  <h3
+    className="page-heading"
+  >
+    Application Insights Details
+  </h3>
+  <div
+    className="gf-form-group"
+  >
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          API Key
+        </Component>
+        <Input
+          className="width-25"
+          disabled={true}
+          placeholder="configured"
+        />
+      </div>
+      <div
+        className="gf-form"
+      >
+        <div
+          className="max-width-30 gf-form-inline"
+        >
+          <Button
+            onClick={[Function]}
+            type="button"
+            variant="secondary"
+          >
+            reset
+          </Button>
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Application ID
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            value="cddcc020-2c94-460a-a3d0-df3147ffa792"
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+</Fragment>
+`;
+
+exports[`Render should enable insights api key input 1`] = `
+<Fragment>
+  <h3
+    className="page-heading"
+  >
+    Application Insights Details
+  </h3>
+  <div
+    className="gf-form-group"
+  >
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          API Key
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+            value="e7f3f661-a933-4b3f-8176-51c4f982ec48"
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Application ID
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            value="cddcc020-2c94-460a-a3d0-df3147ffa792"
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+</Fragment>
+`;
+
+exports[`Render should render component 1`] = `
+<Fragment>
+  <h3
+    className="page-heading"
+  >
+    Application Insights Details
+  </h3>
+  <div
+    className="gf-form-group"
+  >
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          API Key
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
+            value="e7f3f661-a933-4b3f-8176-51c4f982ec48"
+          />
+        </div>
+      </div>
+    </div>
+    <div
+      className="gf-form-inline"
+    >
+      <div
+        className="gf-form"
+      >
+        <Component
+          className="width-12"
+        >
+          Application ID
+        </Component>
+        <div
+          className="width-15"
+        >
+          <Input
+            className="width-30"
+            onChange={[Function]}
+            value="cddcc020-2c94-460a-a3d0-df3147ffa792"
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+</Fragment>
+`;

+ 0 - 127
public/app/plugins/datasource/grafana-azure-monitor-datasource/config_ctrl.ts

@@ -1,127 +0,0 @@
-import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource';
-import config from 'app/core/config';
-import { isVersionGtOrEq } from 'app/core/utils/version';
-import AzureMonitorDatasource from './azure_monitor/azure_monitor_datasource';
-import { BackendSrv } from 'app/core/services/backend_srv';
-import { IQService } from 'angular';
-import { TemplateSrv } from 'app/features/templating/template_srv';
-
-interface AzureCloud {
-  key: string;
-  url: string;
-  loginUrl: string;
-}
-
-export class AzureMonitorConfigCtrl {
-  static templateUrl = 'public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/config.html';
-  current: any;
-  azureLogAnalyticsDatasource: any;
-  azureMonitorDatasource: any;
-  workspaces: any[];
-  subscriptions: Array<{ text: string; value: string }>;
-  subscriptionsForLogAnalytics: Array<{ text: string; value: string }>;
-  hasRequiredGrafanaVersion: boolean;
-  azureClouds: AzureCloud[];
-  token: string;
-
-  /** @ngInject */
-  constructor(private backendSrv: BackendSrv, private $q: IQService, private templateSrv: TemplateSrv) {
-    this.hasRequiredGrafanaVersion = this.hasMinVersion();
-    this.current.jsonData.cloudName = this.current.jsonData.cloudName || 'azuremonitor';
-    this.current.jsonData.azureLogAnalyticsSameAs = this.current.jsonData.azureLogAnalyticsSameAs || false;
-    this.current.secureJsonData = this.current.secureJsonData || {};
-    this.current.secureJsonFields = this.current.secureJsonFields || {};
-    this.subscriptions = [];
-    this.subscriptionsForLogAnalytics = [];
-    this.azureClouds = [
-      {
-        key: 'azuremonitor',
-        url: 'https://management.azure.com/',
-        loginUrl: 'https://login.microsoftonline.com/',
-      },
-      {
-        key: 'govazuremonitor',
-        url: 'https://management.usgovcloudapi.net/',
-        loginUrl: 'https://login.microsoftonline.us/',
-      },
-      {
-        key: 'germanyazuremonitor',
-        url: 'https://management.microsoftazure.de',
-        loginUrl: 'https://management.microsoftazure.de/',
-      },
-      {
-        key: 'chinaazuremonitor',
-        url: 'https://management.chinacloudapi.cn',
-        loginUrl: 'https://login.chinacloudapi.cn',
-      },
-    ];
-
-    if (this.current.id) {
-      this.current.url = '/api/datasources/proxy/' + this.current.id;
-      this.init();
-    }
-  }
-
-  async init() {
-    this.azureMonitorDatasource = new AzureMonitorDatasource(this.current, this.backendSrv, this.templateSrv);
-    await this.getSubscriptions();
-    await this.getSubscriptionsForLogsAnalytics();
-
-    this.azureLogAnalyticsDatasource = new AzureLogAnalyticsDatasource(
-      this.current,
-      this.backendSrv,
-      this.templateSrv,
-      this.$q
-    );
-    await this.getWorkspaces();
-  }
-
-  hasMinVersion(): boolean {
-    return isVersionGtOrEq(config.buildInfo.version, '5.2');
-  }
-
-  showMinVersionWarning() {
-    return !this.hasRequiredGrafanaVersion && this.current.secureJsonFields.logAnalyticsClientSecret;
-  }
-
-  async getWorkspaces() {
-    const sameAs = this.current.jsonData.azureLogAnalyticsSameAs && this.subscriptions.length > 0;
-    if (!sameAs && this.subscriptionsForLogAnalytics.length === 0) {
-      return;
-    }
-
-    this.workspaces = await this.azureLogAnalyticsDatasource.getWorkspaces();
-    if (this.workspaces.length > 0) {
-      this.current.jsonData.logAnalyticsDefaultWorkspace =
-        this.current.jsonData.logAnalyticsDefaultWorkspace || this.workspaces[0].value;
-    }
-  }
-
-  async getSubscriptions() {
-    if (!this.current.secureJsonFields.clientSecret && !this.current.secureJsonData.clientSecret) {
-      return;
-    }
-
-    this.subscriptions = (await this.azureMonitorDatasource.getSubscriptions()) || [];
-    if (this.subscriptions && this.subscriptions.length > 0) {
-      this.current.jsonData.subscriptionId = this.current.jsonData.subscriptionId || this.subscriptions[0].value;
-    }
-  }
-
-  async getSubscriptionsForLogsAnalytics() {
-    if (
-      !this.current.secureJsonFields.logAnalyticsClientSecret &&
-      !this.current.secureJsonData.logAnalyticsClientSecret
-    ) {
-      return;
-    }
-
-    this.subscriptionsForLogAnalytics =
-      (await this.azureMonitorDatasource.getSubscriptions('workspacesloganalytics')) || [];
-
-    if (this.subscriptionsForLogAnalytics && this.subscriptionsForLogAnalytics.length > 0) {
-      this.current.jsonData.logAnalyticsSubscriptionId =
-        this.current.jsonData.logAnalyticsSubscriptionId || this.subscriptionsForLogAnalytics[0].value;
-    }
-  }
-}

+ 1 - 2
public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts

@@ -32,8 +32,7 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
     this.azureLogAnalyticsDatasource = new AzureLogAnalyticsDatasource(
     this.azureLogAnalyticsDatasource = new AzureLogAnalyticsDatasource(
       instanceSettings,
       instanceSettings,
       this.backendSrv,
       this.backendSrv,
-      this.templateSrv,
-      this.$q
+      this.templateSrv
     );
     );
   }
   }
 
 

+ 0 - 11
public/app/plugins/datasource/grafana-azure-monitor-datasource/module.ts

@@ -1,11 +0,0 @@
-import Datasource from './datasource';
-import { AzureMonitorQueryCtrl } from './query_ctrl';
-import { AzureMonitorAnnotationsQueryCtrl } from './annotations_query_ctrl';
-import { AzureMonitorConfigCtrl } from './config_ctrl';
-
-export {
-  Datasource,
-  AzureMonitorQueryCtrl as QueryCtrl,
-  AzureMonitorConfigCtrl as ConfigCtrl,
-  AzureMonitorAnnotationsQueryCtrl as AnnotationsQueryCtrl,
-};

+ 10 - 0
public/app/plugins/datasource/grafana-azure-monitor-datasource/module.tsx

@@ -0,0 +1,10 @@
+import { DataSourcePlugin } from '@grafana/ui';
+import { AzureMonitorQueryCtrl } from './query_ctrl';
+import Datasource from './datasource';
+import { ConfigEditor } from './ConfigEditor';
+import { AzureMonitorAnnotationsQueryCtrl } from './annotations_query_ctrl';
+
+export const plugin = new DataSourcePlugin(Datasource)
+  .setConfigEditor(ConfigEditor)
+  .setQueryCtrl(AzureMonitorQueryCtrl)
+  .setAnnotationQueryCtrl(AzureMonitorAnnotationsQueryCtrl);

+ 0 - 203
public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/config.html

@@ -1,203 +0,0 @@
-<h3 class="page-heading">Azure Monitor Details</h3>
-
-<div class="gf-form-group">
-  <div class="gf-form-inline">
-    <div class="gf-form">
-      <span class="gf-form-label width-10">Azure Cloud</span>
-      <div class="gf-form-select-wrapper width-15 gf-form-select-wrapper--has-help-icon">
-        <select class="gf-form-input" ng-model="ctrl.current.jsonData.cloudName" ng-options="f.value as f.text for f in [{value: 'azuremonitor', text: 'Azure'}, {value: 'govazuremonitor', text: 'Azure US Government'}, {value: 'germanyazuremonitor', text: 'Azure Germany'}, {value: 'chinaazuremonitor', text: 'Azure China'}]"
-          ng-change="ctrl.refresh()"></select>
-      </div>
-      <info-popover mode="right-absolute">
-        <p>Choose an Azure Cloud.</p>
-      </info-popover>
-    </div>
-  </div>
-  <div class="gf-form-inline">
-    <div class="gf-form">
-      <span class="gf-form-label width-10">Tenant Id</span>
-      <input class="gf-form-input width-30 gf-form-input--has-help-icon" type="text" ng-model="ctrl.current.jsonData.tenantId" placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"></input>
-      <info-popover mode="right-absolute">
-        <p>In the Azure Portal, navigate to Azure Active Directory -> Properties -> Directory ID.</p>
-        <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal">**Click
-          here for detailed instructions on setting up an Azure Active Directory (AD) application.**</a>
-      </info-popover>
-    </div>
-  </div>
-  <div class="gf-form-inline">
-    <div class="gf-form">
-      <span class="gf-form-label width-10">Client Id</span>
-      <input class="gf-form-input width-30 gf-form-input--has-help-icon" type="text" ng-model="ctrl.current.jsonData.clientId" placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"></input>
-      <info-popover mode="right-absolute">
-        <p>In the Azure Portal, navigate to Azure Active Directory -> App Registrations -> Choose your app ->
-          Application ID.</p>
-        <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal">**Click
-          here for detailed instructions on setting up an Azure Active Directory (AD) application.**</a>
-      </info-popover>
-    </div>
-  </div>
-  <div class="gf-form-inline" ng-if="!ctrl.current.secureJsonFields.clientSecret">
-    <div class="gf-form">
-      <span class="gf-form-label width-10">Client Secret</span>
-      <input class="gf-form-input width-30 gf-form-input--has-help-icon" type="text" ng-model="ctrl.current.secureJsonData.clientSecret" placeholder=""></input>
-      <info-popover mode="right-absolute">
-        <p>To create a new key, log in to Azure Portal, navigate to Azure Active Directory -> App Registrations ->
-          Choose your
-          app -> Keys.</p>
-        <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal">**Click
-          here for detailed instructions on setting up an Azure Active Directory (AD) application.**</a>
-      </info-popover>
-    </div>
-  </div>
-  <div class="gf-form" ng-if="ctrl.current.secureJsonFields.clientSecret">
-    <span class="gf-form-label width-10">Client Secret</span>
-    <input type="text" class="gf-form-input max-width-12" disabled="disabled" value="configured">
-    <a class="btn btn-secondary gf-form-btn" href="#" ng-click="ctrl.current.secureJsonFields.clientSecret = false">reset</a>
-  </div>
-  <div class="gf-form-group">
-    <div class="gf-form-inline">
-      <div class="gf-form">
-        <span class="gf-form-label width-10">Default Subscription</span>
-        <gf-form-dropdown model="ctrl.current.jsonData.subscriptionId" allow-custom="true" lookup-text="false"
-          get-options="ctrl.subscriptions" css-class="width-30 gf-form-input">
-        </gf-form-dropdown>
-        <info-popover mode="right-absolute">
-          <p>Choose the default/preferred Subscription for Azure Metrics.</p>
-          <p>In the Azure Portal, navigate to Subscriptions -> Choose subscription -> Overview -> Subscription ID.</p>
-          <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal">**Click
-          here for detailed instructions on setting up an Azure Active Directory (AD) application.**</a>
-        </info-popover>
-      </div>
-    </div>
-  </div>
-</div>
-
-<h3 class="page-heading">Azure Log Analytics API Details</h3>
-
-<div class="grafana-info-box ng-scope">
-  The Azure Log Analytics support is marked as being in a preview development state. This means it is in currently in active development and major changes might be made - depending on feedback from users.
-</div>
-
-<div class="gf-form-group">
-  <gf-form-switch class="gf-form" label="Same details as Azure Monitor API" label-class="width-19" switch-class="max-width-6"
-    checked="ctrl.current.jsonData.azureLogAnalyticsSameAs" on-change="ctrl.onSameAsToggle()">
-  </gf-form-switch>
-</div>
-
-<div class="gf-form-group" ng-show="!ctrl.current.jsonData.azureLogAnalyticsSameAs">
-  <div class="gf-form-inline">
-    <div class="gf-form">
-      <span class="gf-form-label width-10">Tenant Id</span>
-      <input class="gf-form-input width-30 gf-form-input--has-help-icon" type="text" ng-model="ctrl.current.jsonData.logAnalyticsTenantId"
-        placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" />
-      <info-popover mode="right-absolute">
-        <p>In the Azure Portal, navigate to Azure Active Directory -> Properties -> Directory ID.</p>
-        <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal">**Click
-          here for detailed instructions on setting up an Azure Active Directory (AD) application.**</a>
-      </info-popover>
-    </div>
-  </div>
-  <div class="gf-form-inline">
-    <div class="gf-form">
-      <span class="gf-form-label width-10">Client Id</span>
-      <input class="gf-form-input width-30 gf-form-input--has-help-icon" type="text" ng-model="ctrl.current.jsonData.logAnalyticsClientId"
-        placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"></input>
-      <info-popover mode="right-absolute">
-        <p>In the Azure Portal, navigate to Azure Active Directory -> App Registrations -> Choose your app ->
-          Application ID.
-        </p>
-        <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal">**Click
-          here for detailed instructions on setting up an Azure Active Directory (AD) application.**</a>
-      </info-popover>
-    </div>
-  </div>
-  <div class="gf-form-inline" ng-if="!ctrl.current.secureJsonFields.logAnalyticsClientSecret">
-    <div class="gf-form">
-      <span class="gf-form-label width-10">Client Secret</span>
-      <input class="gf-form-input width-30 gf-form-input--has-help-icon" type="text" ng-model="ctrl.current.secureJsonData.logAnalyticsClientSecret"
-        placeholder="" />
-      <info-popover mode="right-absolute">
-        <p>To create a new key, log in to Azure Portal, navigate to Azure Active Directory -> App Registrations ->
-          Choose your
-          app -> Keys.</p>
-        <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal">**Click
-          here for detailed instructions on setting up an Azure Active Directory (AD) application.**</a>
-      </info-popover>
-    </div>
-  </div>
-  <div class="gf-form" ng-if="ctrl.current.secureJsonFields.logAnalyticsClientSecret">
-    <span class="gf-form-label width-10">Client Secret</span>
-    <input type="text" class="gf-form-input max-width-12" disabled="disabled" value="configured">
-    <a class="btn btn-secondary gf-form-btn" href="#" ng-click="ctrl.current.secureJsonFields.logAnalyticsClientSecret = false">reset</a>
-  </div>
-  <div class="gf-form-inline">
-    <div class="gf-form">
-      <span class="gf-form-label width-10">Default Subscription</span>
-      <gf-form-dropdown model="ctrl.current.jsonData.logAnalyticsSubscriptionId" allow-custom="true" lookup-text="true"
-        get-options="ctrl.subscriptionsForLogAnalytics" css-class="min-width-30">
-      </gf-form-dropdown>
-      <info-popover mode="right-absolute">
-        <p>Choose the default/preferred Subscription for Azure Metrics.</p>
-        <p>In the Azure Portal, navigate to Subscriptions -> Choose subscription -> Overview -> Subscription ID.</p>
-        <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal">**Click
-        here for detailed instructions on setting up an Azure Active Directory (AD) application.**</a>
-      </info-popover>
-    </div>
-  </div>
-</div>
-<div class="gf-form-group">
-  <div class="gf-form-inline">
-    <div class="gf-form">
-      <span class="gf-form-label width-10">Default Workspace</span>
-      <div class="gf-form-select-wrapper min-width-30 gf-form-select-wrapper--has-help-icon">
-        <select class="gf-form-input" ng-model="ctrl.current.jsonData.logAnalyticsDefaultWorkspace" ng-options="f.value as f.text for f in ctrl.workspaces"
-          ng-disabled="!ctrl.workspaces"></select>
-      </div>
-      <info-popover mode="right-absolute">
-        <p>Choose the default/preferred Workspace for Azure Log Analytics queries.</p>
-      </info-popover>
-    </div>
-  </div>
-</div>
-<div class="gf-form-group" ng-if="ctrl.showMinVersionWarning()">
-  <div class=" alert alert-error">
-    <p>
-      The Azure Log Analytics feature requires Grafana 5.2.0 or greater. Download a new version of
-      Grafana
-      <a class="external-link" target="_blank" href="https://grafana.com/get">here</a>.
-    </p>
-  </div>
-</div>
-
-<h3 class="page-heading">Application Insights Details</h3>
-
-<div class="gf-form-group">
-  <div class="gf-form-inline" ng-if="!ctrl.current.secureJsonFields.appInsightsApiKey">
-    <div class="gf-form">
-      <span class="gf-form-label width-9">API Key</span>
-      <input class="gf-form-input width-30 gf-form-input--has-help-icon" type="text" ng-model="ctrl.current.secureJsonData.appInsightsApiKey"
-        placeholder="" />
-      <info-popover mode="right-absolute">
-        <p>Section 2 of the Quickstart guide shows where to find/create the API Key:</p>
-        <a target="_blank" href="https://dev.applicationinsights.io/quickstart/">**Click here to open the Application
-          Insights Quickstart.**</a>
-      </info-popover>
-    </div>
-  </div>
-  <div class="gf-form" ng-if="ctrl.current.secureJsonFields.appInsightsApiKey">
-    <span class="gf-form-label width-9">API Key</span>
-    <input type="text" class="gf-form-input max-width-12" disabled="disabled" value="configured">
-    <a class="btn btn-secondary gf-form-btn" href="#" ng-click="ctrl.current.secureJsonFields.appInsightsApiKey = false">reset</a>
-  </div>
-  <div class="gf-form-inline">
-    <div class="gf-form">
-      <span class="gf-form-label width-9">Application Id</span>
-      <input class="gf-form-input width-30 gf-form-input--has-help-icon" type="text" ng-model="ctrl.current.jsonData.appInsightsAppId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"></input>
-      <info-popover mode="right-absolute">
-        <p>Section 2 of the Quickstart guide shows where to find the Application ID:</p>
-        <a target="_blank" href="https://dev.applicationinsights.io/quickstart/">**Click here to open the Application
-          Insights Quickstart.**</a>
-      </info-popover>
-    </div>
-  </div>
-</div>