浏览代码

Merge branch 'grafana-lib'

Torkel Ödegaard 7 年之前
父节点
当前提交
66a13b281c
共有 78 个文件被更改,包括 612 次插入317 次删除
  1. 3 1
      jest.config.js
  2. 32 10
      package.json
  3. 4 0
      packages/grafana-build/README.md
  4. 13 0
      packages/grafana-build/package.json
  5. 3 0
      packages/grafana-ui/README.md
  6. 33 0
      packages/grafana-ui/package.json
  7. 5 4
      packages/grafana-ui/src/components/DeleteButton/DeleteButton.test.tsx
  8. 10 10
      packages/grafana-ui/src/components/DeleteButton/DeleteButton.tsx
  9. 0 0
      packages/grafana-ui/src/components/DeleteButton/_DeleteButton.scss
  10. 1 0
      packages/grafana-ui/src/components/index.scss
  11. 1 0
      packages/grafana-ui/src/components/index.ts
  12. 23 0
      packages/grafana-ui/src/forms/GfFormLabel/GfFormLabel.tsx
  13. 1 0
      packages/grafana-ui/src/forms/index.ts
  14. 1 0
      packages/grafana-ui/src/index.scss
  15. 5 0
      packages/grafana-ui/src/index.ts
  16. 3 0
      packages/grafana-ui/src/types/index.ts
  17. 17 0
      packages/grafana-ui/src/types/jquery.d.ts
  18. 31 0
      packages/grafana-ui/src/types/panel.ts
  19. 53 0
      packages/grafana-ui/src/types/series.ts
  20. 17 0
      packages/grafana-ui/src/types/time.ts
  21. 1 0
      packages/grafana-ui/src/utils/index.ts
  22. 174 0
      packages/grafana-ui/src/utils/processTimeSeries.ts
  23. 8 6
      packages/grafana-ui/src/visualizations/Graph/Graph.tsx
  24. 1 0
      packages/grafana-ui/src/visualizations/index.ts
  25. 18 0
      packages/grafana-ui/tsconfig.json
  26. 3 0
      packages/grafana-ui/tslint.json
  27. 0 43
      public/app/core/components/Form/Element.tsx
  28. 0 19
      public/app/core/components/Form/Label.tsx
  29. 0 2
      public/app/core/components/Form/index.ts
  30. 2 2
      public/app/core/directives/tags.ts
  31. 2 1
      public/app/core/utils/explore.ts
  32. 1 1
      public/app/core/utils/rangeutil.ts
  33. 3 3
      public/app/features/alerting/AlertRuleItem.tsx
  34. 2 2
      public/app/features/api-keys/ApiKeysPage.tsx
  35. 2 1
      public/app/features/dashboard/dashgrid/DataPanel.tsx
  36. 2 1
      public/app/features/dashboard/dashgrid/PanelChrome.tsx
  37. 1 1
      public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx
  38. 1 1
      public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx
  39. 5 1
      public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx
  40. 2 1
      public/app/features/dashboard/dashgrid/QueryOptions.tsx
  41. 2 2
      public/app/features/dashboard/time_srv.ts
  42. 1 1
      public/app/features/dashboard/utils/getPanelMenu.ts
  43. 1 1
      public/app/features/dashboard/utils/panel.ts
  44. 1 1
      public/app/features/datasources/DataSourcesList.tsx
  45. 2 1
      public/app/features/explore/Explore.tsx
  46. 1 1
      public/app/features/explore/Graph.tsx
  47. 1 1
      public/app/features/explore/Logs.tsx
  48. 1 1
      public/app/features/explore/QueryEditor.tsx
  49. 1 1
      public/app/features/explore/QueryRows.tsx
  50. 1 1
      public/app/features/explore/TimePicker.tsx
  51. 1 1
      public/app/features/plugins/PluginList.tsx
  52. 2 0
      public/app/features/plugins/plugin_loader.ts
  53. 2 2
      public/app/features/teams/TeamList.tsx
  54. 2 2
      public/app/features/teams/TeamMembers.tsx
  55. 5 5
      public/app/features/teams/__snapshots__/TeamList.test.tsx.snap
  56. 10 10
      public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap
  57. 1 1
      public/app/features/users/UsersActionBar.tsx
  58. 2 1
      public/app/plugins/panel/gauge/Threshold.test.tsx
  59. 2 9
      public/app/plugins/panel/gauge/module.tsx
  60. 1 0
      public/app/plugins/panel/graph/specs/graph.test.ts
  61. 1 1
      public/app/plugins/panel/graph2/GraphOptions.tsx
  62. 5 9
      public/app/plugins/panel/graph2/GraphPanel.tsx
  63. 1 1
      public/app/plugins/panel/text2/module.tsx
  64. 2 1
      public/app/types/explore.ts
  65. 2 24
      public/app/types/index.ts
  66. 17 0
      public/app/types/jquery.d.ts
  67. 0 31
      public/app/types/panel.ts
  68. 1 1
      public/app/types/plugins.ts
  69. 1 71
      public/app/types/series.ts
  70. 2 1
      public/app/viz/Gauge.tsx
  71. 1 1
      public/app/viz/state/timeSeries.ts
  72. 3 1
      public/sass/_grafana.scss
  73. 16 6
      scripts/grunt/default_task.js
  74. 12 4
      scripts/grunt/options/exec.js
  75. 1 0
      scripts/grunt/options/sasslint.js
  76. 0 11
      scripts/grunt/options/tslint.js
  77. 1 0
      tsconfig.json
  78. 22 2
      yarn.lock

+ 3 - 1
jest.config.js

@@ -6,7 +6,9 @@ module.exports = {
   },
   "moduleDirectories": ["node_modules", "public"],
   "roots": [
-    "<rootDir>/public"
+    "<rootDir>/public/app",
+    "<rootDir>/public/test",
+    "<rootDir>/packages"
   ],
   "testRegex": "(\\.|/)(test)\\.(jsx?|tsx?)$",
   "moduleFileExtensions": [

+ 32 - 10
package.json

@@ -1,4 +1,5 @@
 {
+  "private": true,
   "author": {
     "name": "Torkel Ödegaard",
     "company": "Grafana Labs"
@@ -11,19 +12,21 @@
   },
   "devDependencies": {
     "@babel/core": "^7.1.2",
-    "@rtsao/plugin-proposal-class-properties": "^7.0.1-patch.1",
     "@babel/plugin-syntax-dynamic-import": "^7.0.0",
     "@babel/preset-env": "^7.1.0",
     "@babel/preset-react": "^7.0.0",
     "@babel/preset-typescript": "^7.1.0",
+    "@rtsao/plugin-proposal-class-properties": "^7.0.1-patch.1",
     "@types/d3": "^4.10.1",
     "@types/enzyme": "^3.1.13",
     "@types/jest": "^23.3.2",
+    "@types/jquery": "^1.10.35",
     "@types/node": "^8.0.31",
     "@types/react": "^16.7.6",
     "@types/react-custom-scrollbars": "^4.0.5",
     "@types/react-dom": "^16.0.9",
     "@types/react-select": "^2.0.4",
+    "@types/classnames": "^2.2.6",
     "angular-mocks": "1.6.6",
     "autoprefixer": "^6.4.0",
     "axios": "^0.17.1",
@@ -92,6 +95,7 @@
     "tslib": "^1.9.3",
     "tslint": "^5.8.0",
     "tslint-loader": "^3.5.3",
+    "tslint-react": "^3.6.0",
     "typescript": "^3.0.3",
     "uglifyjs-webpack-plugin": "^1.2.7",
     "webpack": "4.19.1",
@@ -108,15 +112,25 @@
     "watch": "webpack --progress --colors --watch --mode development --config scripts/webpack/webpack.dev.js",
     "build": "grunt build",
     "test": "grunt test",
-    "lint": "tslint -c tslint.json --project tsconfig.json",
+    "tslint": "tslint -c tslint.json --project tsconfig.json",
+    "typecheck": "tsc --noEmit",
     "jest": "jest --notify --watch",
     "api-tests": "jest --notify --watch --config=tests/api/jest.js",
     "precommit": "lint-staged && grunt precommit"
   },
   "lint-staged": {
-    "*.{ts,tsx}": ["prettier --write", "git add"],
-    "*.scss": ["prettier --write", "git add"],
-    "*pkg/**/*.go": ["gofmt -w -s", "git add"]
+    "*.{ts,tsx}": [
+      "prettier --write",
+      "git add"
+    ],
+    "*.scss": [
+      "prettier --write",
+      "git add"
+    ],
+    "*pkg/**/*.go": [
+      "gofmt -w -s",
+      "git add"
+    ]
   },
   "prettier": {
     "trailingComma": "es5",
@@ -126,6 +140,7 @@
   "license": "Apache-2.0",
   "dependencies": {
     "@babel/polyfill": "^7.0.0",
+    "@torkelo/react-select": "2.1.1",
     "angular": "1.6.6",
     "angular-bindonce": "0.3.1",
     "angular-native-dragdrop": "1.2.2",
@@ -133,7 +148,7 @@
     "angular-sanitize": "1.6.6",
     "baron": "^3.0.3",
     "brace": "^0.10.0",
-    "classnames": "^2.2.5",
+    "classnames": "^2.2.6",
     "clipboard": "^1.7.1",
     "d3": "^4.11.0",
     "d3-scale-chromatic": "^1.3.0",
@@ -152,10 +167,9 @@
     "react-custom-scrollbars": "^4.2.1",
     "react-dom": "^16.6.3",
     "react-grid-layout": "0.16.6",
-    "react-popper": "^1.3.0",
     "react-highlight-words": "0.11.0",
+    "react-popper": "^1.3.0",
     "react-redux": "^5.0.7",
-    "@torkelo/react-select": "2.1.1",
     "react-sizeme": "^2.3.6",
     "react-table": "^6.8.6",
     "react-transition-group": "^2.2.1",
@@ -172,11 +186,19 @@
     "slate-react": "^0.12.4",
     "tether": "^1.4.0",
     "tether-drop": "https://github.com/torkelo/drop/tarball/master",
-    "tinycolor2": "^1.4.1",
-    "tslint-react": "^3.6.0"
+    "tinycolor2": "^1.4.1"
   },
   "resolutions": {
     "caniuse-db": "1.0.30000772",
     "**/@types/react": "16.7.6"
+  },
+  "workspaces": {
+    "packages": [
+      "packages/*"
+    ],
+    "nohoist": [
+      "**/@types/*",
+      "**/@types/*/**"
+    ]
   }
 }

+ 4 - 0
packages/grafana-build/README.md

@@ -0,0 +1,4 @@
+# Shared build scripts
+
+Shared build scripts for plugins & internal packages.
+

+ 13 - 0
packages/grafana-build/package.json

@@ -0,0 +1,13 @@
+{
+  "name": "@grafana/build",
+  "private": true,
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "tslint": "echo \"Nothing to do\"",
+    "typecheck": "echo \"Nothing to do\""
+  },
+  "author": "",
+  "license": "ISC"
+}

+ 3 - 0
packages/grafana-ui/README.md

@@ -0,0 +1,3 @@
+# Grafana (WIP) shared component library
+
+Used by internal & external plugins.

+ 33 - 0
packages/grafana-ui/package.json

@@ -0,0 +1,33 @@
+{
+  "name": "@grafana/ui",
+  "version": "1.0.0",
+  "description": "",
+  "main": "src/index.ts",
+  "scripts": {
+    "tslint": "tslint -c tslint.json --project tsconfig.json",
+    "typecheck": "tsc --noEmit"
+  },
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "@torkelo/react-select": "2.1.1",
+    "classnames": "^2.2.5",
+    "jquery": "^3.2.1",
+    "lodash": "^4.17.10",
+    "moment": "^2.22.2",
+    "react": "^16.6.3",
+    "react-dom": "^16.6.3",
+    "react-highlight-words": "0.11.0",
+    "react-popper": "^1.3.0",
+    "react-transition-group": "^2.2.1",
+    "react-virtualized": "^9.21.0"
+  },
+  "devDependencies": {
+    "@types/jest": "^23.3.2",
+    "@types/lodash": "^4.14.119",
+    "@types/react": "^16.7.6",
+    "@types/classnames": "^2.2.6",
+    "@types/jquery": "^1.10.35",
+    "typescript": "^3.2.2"
+  }
+}

+ 5 - 4
public/app/core/components/DeleteButton/DeleteButton.test.tsx → packages/grafana-ui/src/components/DeleteButton/DeleteButton.test.tsx

@@ -1,10 +1,10 @@
 import React from 'react';
-import DeleteButton from './DeleteButton';
+import { DeleteButton } from './DeleteButton';
 import { shallow } from 'enzyme';
 
 describe('DeleteButton', () => {
-  let wrapper;
-  let deleted;
+  let wrapper: any;
+  let deleted: any;
 
   beforeAll(() => {
     deleted = false;
@@ -12,7 +12,8 @@ describe('DeleteButton', () => {
     function deleteItem() {
       deleted = true;
     }
-    wrapper = shallow(<DeleteButton onConfirmDelete={() => deleteItem()} />);
+
+    wrapper = shallow(<DeleteButton onConfirm={() => deleteItem()} />);
   });
 
   it('should show confirm delete when clicked', () => {

+ 10 - 10
public/app/core/components/DeleteButton/DeleteButton.tsx → packages/grafana-ui/src/components/DeleteButton/DeleteButton.tsx

@@ -1,19 +1,19 @@
-import React, { PureComponent } from 'react';
+import React, { PureComponent, SyntheticEvent } from 'react';
 
-export interface DeleteButtonProps {
-  onConfirmDelete();
+interface Props {
+  onConfirm(): void;
 }
 
-export interface DeleteButtonStates {
+interface State {
   showConfirm: boolean;
 }
 
-export default class DeleteButton extends PureComponent<DeleteButtonProps, DeleteButtonStates> {
-  state: DeleteButtonStates = {
+export class DeleteButton extends PureComponent<Props, State> {
+  state: State = {
     showConfirm: false,
   };
 
-  onClickDelete = event => {
+  onClickDelete = (event: SyntheticEvent) => {
     if (event) {
       event.preventDefault();
     }
@@ -23,7 +23,7 @@ export default class DeleteButton extends PureComponent<DeleteButtonProps, Delet
     });
   };
 
-  onClickCancel = event => {
+  onClickCancel = (event: SyntheticEvent) => {
     if (event) {
       event.preventDefault();
     }
@@ -33,7 +33,7 @@ export default class DeleteButton extends PureComponent<DeleteButtonProps, Delet
   };
 
   render() {
-    const onClickConfirm = this.props.onConfirmDelete;
+    const { onConfirm } = this.props;
     let showConfirm;
     let showDeleteButton;
 
@@ -55,7 +55,7 @@ export default class DeleteButton extends PureComponent<DeleteButtonProps, Delet
             <a className="btn btn-small" onClick={this.onClickCancel}>
               Cancel
             </a>
-            <a className="btn btn-danger btn-small" onClick={onClickConfirm}>
+            <a className="btn btn-danger btn-small" onClick={onConfirm}>
               Confirm Delete
             </a>
           </span>

+ 0 - 0
public/sass/components/_delete_button.scss → packages/grafana-ui/src/components/DeleteButton/_DeleteButton.scss


+ 1 - 0
packages/grafana-ui/src/components/index.scss

@@ -0,0 +1 @@
+@import 'DeleteButton/DeleteButton';

+ 1 - 0
packages/grafana-ui/src/components/index.ts

@@ -0,0 +1 @@
+export { DeleteButton } from './DeleteButton/DeleteButton';

+ 23 - 0
packages/grafana-ui/src/forms/GfFormLabel/GfFormLabel.tsx

@@ -0,0 +1,23 @@
+import React, { SFC, ReactNode } from 'react';
+import classNames from 'classnames';
+
+interface Props {
+  children: ReactNode;
+  htmlFor?: string;
+  className?: string;
+  isFocused?: boolean;
+  isInvalid?: boolean;
+}
+
+export const GfFormLabel: SFC<Props> = ({ children, isFocused, isInvalid, className, htmlFor, ...rest }) => {
+  const classes = classNames('gf-form-label', className, {
+    'gf-form-label--is-focused': isFocused,
+    'gf-form-label--is-invalid': isInvalid,
+  });
+
+  return (
+    <label className={classes} {...rest} htmlFor={htmlFor}>
+      {children}
+    </label>
+  );
+};

+ 1 - 0
packages/grafana-ui/src/forms/index.ts

@@ -0,0 +1 @@
+export { GfFormLabel } from './GfFormLabel/GfFormLabel';

+ 1 - 0
packages/grafana-ui/src/index.scss

@@ -0,0 +1 @@
+@import 'components/index';

+ 5 - 0
packages/grafana-ui/src/index.ts

@@ -0,0 +1,5 @@
+export * from './components';
+export * from './visualizations';
+export * from './types';
+export * from './utils';
+export * from './forms';

+ 3 - 0
packages/grafana-ui/src/types/index.ts

@@ -0,0 +1,3 @@
+export * from './series';
+export * from './time';
+export * from './panel';

+ 17 - 0
packages/grafana-ui/src/types/jquery.d.ts

@@ -0,0 +1,17 @@
+interface JQueryPlot {
+  (element: HTMLElement | JQuery, data: any, options: any): void;
+  plugins: any[];
+}
+
+interface JQueryStatic {
+  plot: JQueryPlot;
+}
+
+interface JQuery {
+  place_tt: any;
+  modal: any;
+  tagsinput: any;
+  typeahead: any;
+  accessKey: any;
+  tooltip: any;
+}

+ 31 - 0
packages/grafana-ui/src/types/panel.ts

@@ -0,0 +1,31 @@
+import { TimeSeries, LoadingState } from './series';
+import { TimeRange } from './time';
+
+export interface PanelProps<T = any> {
+  timeSeries: TimeSeries[];
+  timeRange: TimeRange;
+  loading: LoadingState;
+  options: T;
+  renderCounter: number;
+  width: number;
+  height: number;
+}
+
+export interface PanelOptionsProps<T = any> {
+  options: T;
+  onChange: (options: T) => void;
+}
+
+export interface PanelSize {
+  width: number;
+  height: number;
+}
+
+export interface PanelMenuItem {
+  type?: 'submenu' | 'divider';
+  text?: string;
+  iconClassName?: string;
+  onClick?: () => void;
+  shortcut?: string;
+  subMenu?: PanelMenuItem[];
+}

+ 53 - 0
packages/grafana-ui/src/types/series.ts

@@ -0,0 +1,53 @@
+export enum LoadingState {
+  NotStarted = 'NotStarted',
+  Loading = 'Loading',
+  Done = 'Done',
+  Error = 'Error',
+}
+
+export type TimeSeriesValue = number | null;
+
+export type TimeSeriesPoints = TimeSeriesValue[][];
+
+export interface TimeSeries {
+  target: string;
+  datapoints: TimeSeriesPoints;
+  unit?: string;
+}
+
+/** View model projection of a time series */
+export interface TimeSeriesVM {
+  label: string;
+  color: string;
+  data: TimeSeriesValue[][];
+  stats: TimeSeriesStats;
+}
+
+export interface TimeSeriesStats {
+  total: number | null;
+  max: number | null;
+  min: number | null;
+  logmin: number;
+  avg: number | null;
+  current: number | null;
+  first: number | null;
+  delta: number;
+  diff: number | null;
+  range: number | null;
+  timeStep: number;
+  count: number;
+  allIsNull: boolean;
+  allIsZero: boolean;
+}
+
+export enum NullValueMode {
+  Null = 'null',
+  Ignore = 'connected',
+  AsZero = 'null as zero',
+}
+
+/** View model projection of many time series */
+export interface TimeSeriesVMs {
+  [index: number]: TimeSeriesVM;
+  length: number;
+}

+ 17 - 0
packages/grafana-ui/src/types/time.ts

@@ -0,0 +1,17 @@
+import { Moment } from 'moment';
+
+export interface RawTimeRange {
+  from: Moment | string;
+  to: Moment | string;
+}
+
+export interface TimeRange {
+  from: Moment;
+  to: Moment;
+  raw: RawTimeRange;
+}
+
+export interface IntervalValues {
+  interval: string; // 10s,5m
+  intervalMs: number;
+}

+ 1 - 0
packages/grafana-ui/src/utils/index.ts

@@ -0,0 +1 @@
+export * from './processTimeSeries';

+ 174 - 0
packages/grafana-ui/src/utils/processTimeSeries.ts

@@ -0,0 +1,174 @@
+// Libraries
+import _ from 'lodash';
+
+// Types
+import { TimeSeries, TimeSeriesVMs, NullValueMode, TimeSeriesValue } from '../types';
+
+interface Options {
+  timeSeries: TimeSeries[];
+  nullValueMode: NullValueMode;
+  colorPalette: string[];
+}
+
+export function processTimeSeries({ timeSeries, nullValueMode, colorPalette }: Options): TimeSeriesVMs {
+  const vmSeries = timeSeries.map((item, index) => {
+    const colorIndex = index % colorPalette.length;
+    const label = item.target;
+    const result = [];
+
+    // stat defaults
+    let total = 0;
+    let max: TimeSeriesValue = -Number.MAX_VALUE;
+    let min: TimeSeriesValue = Number.MAX_VALUE;
+    let logmin = Number.MAX_VALUE;
+    let avg: TimeSeriesValue = null;
+    let current: TimeSeriesValue = null;
+    let first: TimeSeriesValue = null;
+    let delta: TimeSeriesValue = 0;
+    let diff: TimeSeriesValue = null;
+    let range: TimeSeriesValue = null;
+    let timeStep = Number.MAX_VALUE;
+    let allIsNull = true;
+    let allIsZero = true;
+
+    const ignoreNulls = nullValueMode === NullValueMode.Ignore;
+    const nullAsZero = nullValueMode === NullValueMode.AsZero;
+
+    let currentTime: TimeSeriesValue = null;
+    let currentValue: TimeSeriesValue = null;
+    let nonNulls = 0;
+    let previousTime: TimeSeriesValue = null;
+    let previousValue = 0;
+    let previousDeltaUp = true;
+
+    for (let i = 0; i < item.datapoints.length; i++) {
+      currentValue = item.datapoints[i][0];
+      currentTime = item.datapoints[i][1];
+
+      if (typeof currentTime !== 'number') {
+        continue;
+      }
+
+      if (typeof currentValue !== 'number') {
+        continue;
+      }
+
+      // Due to missing values we could have different timeStep all along the series
+      // so we have to find the minimum one (could occur with aggregators such as ZimSum)
+      if (previousTime !== null && currentTime !== null) {
+        const currentStep = currentTime - previousTime;
+        if (currentStep < timeStep) {
+          timeStep = currentStep;
+        }
+      }
+
+      previousTime = currentTime;
+
+      if (currentValue === null) {
+        if (ignoreNulls) {
+          continue;
+        }
+        if (nullAsZero) {
+          currentValue = 0;
+        }
+      }
+
+      if (currentValue !== null) {
+        if (_.isNumber(currentValue)) {
+          total += currentValue;
+          allIsNull = false;
+          nonNulls++;
+        }
+
+        if (currentValue > max) {
+          max = currentValue;
+        }
+
+        if (currentValue < min) {
+          min = currentValue;
+        }
+
+        if (first === null) {
+          first = currentValue;
+        } else {
+          if (previousValue > currentValue) {
+            // counter reset
+            previousDeltaUp = false;
+            if (i === item.datapoints.length - 1) {
+              // reset on last
+              delta += currentValue;
+            }
+          } else {
+            if (previousDeltaUp) {
+              delta += currentValue - previousValue; // normal increment
+            } else {
+              delta += currentValue; // account for counter reset
+            }
+            previousDeltaUp = true;
+          }
+        }
+        previousValue = currentValue;
+
+        if (currentValue < logmin && currentValue > 0) {
+          logmin = currentValue;
+        }
+
+        if (currentValue !== 0) {
+          allIsZero = false;
+        }
+      }
+
+      result.push([currentTime, currentValue]);
+    }
+
+    if (max === -Number.MAX_VALUE) {
+      max = null;
+    }
+
+    if (min === Number.MAX_VALUE) {
+      min = null;
+    }
+
+    if (result.length && !allIsNull) {
+      avg = total / nonNulls;
+      current = result[result.length - 1][1];
+      if (current === null && result.length > 1) {
+        current = result[result.length - 2][1];
+      }
+    }
+
+    if (max !== null && min !== null) {
+      range = max - min;
+    }
+
+    if (current !== null && first !== null) {
+      diff = current - first;
+    }
+
+    const count = result.length;
+
+    return {
+      data: result,
+      label: label,
+      color: colorPalette[colorIndex],
+      stats: {
+        total,
+        min,
+        max,
+        current,
+        logmin,
+        avg,
+        diff,
+        delta,
+        timeStep,
+        range,
+        count,
+        first,
+        allIsZero,
+        allIsNull,
+      },
+    };
+  });
+
+  return vmSeries;
+}

+ 8 - 6
public/app/viz/Graph.tsx → packages/grafana-ui/src/visualizations/Graph/Graph.tsx

@@ -1,11 +1,9 @@
 // Libraries
 import $ from 'jquery';
 import React, { PureComponent } from 'react';
-import 'vendor/flot/jquery.flot';
-import 'vendor/flot/jquery.flot.time';
 
 // Types
-import { TimeRange, TimeSeriesVMs } from 'app/types';
+import { TimeRange, TimeSeriesVMs } from '../../types';
 
 interface GraphProps {
   timeSeries: TimeSeriesVMs;
@@ -24,7 +22,7 @@ export class Graph extends PureComponent<GraphProps> {
     showBars: false,
   };
 
-  element: HTMLElement;
+  element: HTMLElement | null;
 
   componentDidUpdate() {
     this.draw();
@@ -35,6 +33,10 @@ export class Graph extends PureComponent<GraphProps> {
   }
 
   draw() {
+    if (this.element === null) {
+      return;
+    }
+
     const { width, timeSeries, timeRange, showLines, showBars, showPoints } = this.props;
 
     if (!width) {
@@ -76,7 +78,7 @@ export class Graph extends PureComponent<GraphProps> {
         max: max,
         label: 'Datetime',
         ticks: ticks,
-        timeformat: time_format(ticks, min, max),
+        timeformat: timeFormat(ticks, min, max),
       },
       grid: {
         minBorderMargin: 0,
@@ -109,7 +111,7 @@ export class Graph extends PureComponent<GraphProps> {
 }
 
 // Copied from graph.ts
-function time_format(ticks, min, max) {
+function timeFormat(ticks: number, min: number, max: number): string {
   if (min && max && ticks) {
     const range = max - min;
     const secPerTick = range / ticks / 1000;

+ 1 - 0
packages/grafana-ui/src/visualizations/index.ts

@@ -0,0 +1 @@
+export { Graph } from './Graph/Graph';

+ 18 - 0
packages/grafana-ui/tsconfig.json

@@ -0,0 +1,18 @@
+{
+  "extends": "../../tsconfig.json",
+  "include": [
+    "src/**/*.ts",
+    "src/**/*.tsx"
+  ],
+  "exclude": [
+    "dist"
+  ],
+  "compilerOptions": {
+    "rootDir": ".",
+    "module": "esnext",
+    "outDir": "dist",
+    "declaration": true,
+    "noImplicitAny": true,
+    "strictNullChecks": true
+  }
+}

+ 3 - 0
packages/grafana-ui/tslint.json

@@ -0,0 +1,3 @@
+{
+  "extends": "../../tslint.json"
+}

+ 0 - 43
public/app/core/components/Form/Element.tsx

@@ -1,43 +0,0 @@
-import React, { PureComponent, ReactNode, ReactElement } from 'react';
-import { Label } from './Label';
-import { uniqueId } from 'lodash';
-
-interface Props {
-  label?: ReactNode;
-  labelClassName?: string;
-  id?: string;
-  children: ReactElement<any>;
-}
-
-export class Element extends PureComponent<Props> {
-  elementId: string = this.props.id || uniqueId('form-element-');
-
-  get elementLabel() {
-    const { label, labelClassName } = this.props;
-
-    if (label) {
-      return (
-        <Label htmlFor={this.elementId} className={labelClassName}>
-          {label}
-        </Label>
-      );
-    }
-
-    return null;
-  }
-
-  get children() {
-    const { children } = this.props;
-
-    return React.cloneElement(children, { id: this.elementId });
-  }
-
-  render() {
-    return (
-      <div className="our-custom-wrapper-class">
-        {this.elementLabel}
-        {this.children}
-      </div>
-    );
-  }
-}

+ 0 - 19
public/app/core/components/Form/Label.tsx

@@ -1,19 +0,0 @@
-import React, { PureComponent, ReactNode } from 'react';
-
-interface Props {
-  children: ReactNode;
-  htmlFor?: string;
-  className?: string;
-}
-
-export class Label extends PureComponent<Props> {
-  render() {
-    const { children, htmlFor, className } = this.props;
-
-    return (
-      <label className={`custom-label-class ${className || ''}`} htmlFor={htmlFor}>
-        {children}
-      </label>
-    );
-  }
-}

+ 0 - 2
public/app/core/components/Form/index.ts

@@ -1,3 +1 @@
-export { Element } from './Element';
 export { Input } from './Input';
-export { Label } from './Label';

+ 2 - 2
public/app/core/directives/tags.ts

@@ -69,7 +69,7 @@ function bootstrapTagsinput() {
             },
       });
 
-      select.on('itemAdded', event => {
+      select.on('itemAdded', (event: any) => {
         if (scope.model.indexOf(event.item) === -1) {
           scope.model.push(event.item);
           if (scope.onTagsUpdated) {
@@ -85,7 +85,7 @@ function bootstrapTagsinput() {
         setColor(event.item, tagElement);
       });
 
-      select.on('itemRemoved', event => {
+      select.on('itemRemoved', (event: any) => {
         const idx = scope.model.indexOf(event.item);
         if (idx !== -1) {
           scope.model.splice(idx, 1);

+ 2 - 1
public/app/core/utils/explore.ts

@@ -9,7 +9,8 @@ import { parse as parseDate } from 'app/core/utils/datemath';
 import TimeSeries from 'app/core/time_series2';
 import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
 import { ExploreState, ExploreUrlState, HistoryItem, QueryTransaction } from 'app/types/explore';
-import { DataQuery, RawTimeRange, IntervalValues, DataSourceApi } from 'app/types/series';
+import { DataQuery, DataSourceApi } from 'app/types/series';
+import { RawTimeRange, IntervalValues } from '@grafana/ui';
 
 export const DEFAULT_RANGE = {
   from: 'now-6h',

+ 1 - 1
public/app/core/utils/rangeutil.ts

@@ -1,7 +1,7 @@
 import _ from 'lodash';
 import moment from 'moment';
 
-import { RawTimeRange } from 'app/types/series';
+import { RawTimeRange } from '@grafana/ui';
 
 import * as dateMath from './datemath';
 

+ 3 - 3
public/app/features/alerting/AlertRuleItem.tsx

@@ -1,6 +1,6 @@
 import React, { PureComponent } from 'react';
 import Highlighter from 'react-highlight-words';
-import classNames from 'classnames/bind';
+import classNames from 'classnames';
 import { AlertRule } from '../../types';
 
 export interface Props {
@@ -23,7 +23,7 @@ class AlertRuleItem extends PureComponent<Props> {
   render() {
     const { rule, onTogglePause } = this.props;
 
-    const stateClass = classNames({
+    const iconClassName = classNames({
       fa: true,
       'fa-play': rule.state === 'paused',
       'fa-pause': rule.state !== 'paused',
@@ -55,7 +55,7 @@ class AlertRuleItem extends PureComponent<Props> {
             title="Pausing an alert rule prevents it from executing"
             onClick={onTogglePause}
           >
-            <i className={stateClass} />
+            <i className={iconClassName} />
           </button>
           <a className="btn btn-small btn-inverse alert-list__btn width-2" href={ruleUrl} title="Edit alert rule">
             <i className="icon-gf icon-gf-settings" />

+ 2 - 2
public/app/features/api-keys/ApiKeysPage.tsx

@@ -13,7 +13,7 @@ import ApiKeysAddedModal from './ApiKeysAddedModal';
 import config from 'app/core/config';
 import appEvents from 'app/core/app_events';
 import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
-import DeleteButton from 'app/core/components/DeleteButton/DeleteButton';
+import { DeleteButton } from '@grafana/ui';
 
 export interface Props {
   navModel: NavModel;
@@ -224,7 +224,7 @@ export class ApiKeysPage extends PureComponent<Props, any> {
                     <td>{key.name}</td>
                     <td>{key.role}</td>
                     <td>
-                      <DeleteButton onConfirmDelete={() => this.onDeleteApiKey(key)} />
+                      <DeleteButton onConfirm={() => this.onDeleteApiKey(key)} />
                     </td>
                   </tr>
                 );

+ 2 - 1
public/app/features/dashboard/dashgrid/DataPanel.tsx

@@ -8,7 +8,8 @@ import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource
 import kbn from 'app/core/utils/kbn';
 
 // Types
-import { TimeRange, LoadingState, DataQueryOptions, DataQueryResponse, TimeSeries } from 'app/types';
+import { DataQueryOptions, DataQueryResponse } from 'app/types';
+import { TimeRange, TimeSeries, LoadingState } from '@grafana/ui';
 
 interface RenderProps {
   loading: LoadingState;

+ 2 - 1
public/app/features/dashboard/dashgrid/PanelChrome.tsx

@@ -16,7 +16,8 @@ import { PANEL_HEADER_HEIGHT } from 'app/core/constants';
 // Types
 import { PanelModel } from '../panel_model';
 import { DashboardModel } from '../dashboard_model';
-import { PanelPlugin, TimeRange } from 'app/types';
+import { PanelPlugin } from 'app/types';
+import { TimeRange } from '@grafana/ui';
 
 export interface Props {
   panel: PanelModel;

+ 1 - 1
public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx

@@ -3,7 +3,7 @@ import { DashboardModel } from 'app/features/dashboard/dashboard_model';
 import { PanelModel } from 'app/features/dashboard/panel_model';
 import { PanelHeaderMenuItem } from './PanelHeaderMenuItem';
 import { getPanelMenu } from 'app/features/dashboard/utils/getPanelMenu';
-import { PanelMenuItem } from 'app/types/panel';
+import { PanelMenuItem } from '@grafana/ui';
 
 export interface Props {
   panel: PanelModel;

+ 1 - 1
public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx

@@ -1,5 +1,5 @@
 import React, { SFC } from 'react';
-import { PanelMenuItem } from 'app/types/panel';
+import { PanelMenuItem } from '@grafana/ui';
 
 interface Props {
   children: any;

+ 5 - 1
public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx

@@ -1,6 +1,10 @@
+// Libraries
 import _ from 'lodash';
 import React, { PureComponent } from 'react';
-import { PanelPlugin, PanelProps } from 'app/types';
+
+// Types
+import { PanelProps } from '@grafana/ui';
+import { PanelPlugin } from 'app/types';
 
 interface Props {
   pluginId: string;

+ 2 - 1
public/app/features/dashboard/dashgrid/QueryOptions.tsx

@@ -10,6 +10,7 @@ import { Input } from 'app/core/components/Form';
 import { EventsWithValidation } from 'app/core/components/Form/Input';
 import { InputStatus } from 'app/core/components/Form/Input';
 import DataSourceOption from './DataSourceOption';
+import { GfFormLabel } from '@grafana/ui';
 
 // Types
 import { PanelModel } from '../panel_model';
@@ -163,7 +164,7 @@ export class QueryOptions extends PureComponent<Props, State> {
         {this.renderOptions()}
 
         <div className="gf-form">
-          <span className="gf-form-label">Relative time</span>
+          <GfFormLabel>Relative time</GfFormLabel>
           <Input
             type="text"
             className="width-6"

+ 2 - 2
public/app/features/dashboard/time_srv.ts

@@ -6,9 +6,9 @@ import _ from 'lodash';
 import kbn from 'app/core/utils/kbn';
 import coreModule from 'app/core/core_module';
 import * as dateMath from 'app/core/utils/datemath';
-// Types
 
-import { TimeRange } from 'app/types';
+// Types
+import { TimeRange } from '@grafana/ui';
 
 export class TimeSrv {
   time: any;

+ 1 - 1
public/app/features/dashboard/utils/getPanelMenu.ts

@@ -4,7 +4,7 @@ import { store } from 'app/store/store';
 import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel';
 import { PanelModel } from 'app/features/dashboard/panel_model';
 import { DashboardModel } from 'app/features/dashboard/dashboard_model';
-import { PanelMenuItem } from 'app/types/panel';
+import { PanelMenuItem } from '@grafana/ui';
 
 export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => {
   const onViewPanel = () => {

+ 1 - 1
public/app/features/dashboard/utils/panel.ts

@@ -4,7 +4,7 @@ import store from 'app/core/store';
 // Models
 import { DashboardModel } from 'app/features/dashboard/dashboard_model';
 import { PanelModel } from 'app/features/dashboard/panel_model';
-import { TimeRange } from 'app/types/series';
+import { TimeRange } from '@grafana/ui';
 
 // Utils
 import { isString as _isString } from 'lodash';

+ 1 - 1
public/app/features/datasources/DataSourcesList.tsx

@@ -1,5 +1,5 @@
 import React, { PureComponent } from 'react';
-import classNames from 'classnames/bind';
+import classNames from 'classnames';
 import DataSourcesListItem from './DataSourcesListItem';
 import { DataSource } from 'app/types';
 import { LayoutMode, LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';

+ 2 - 1
public/app/features/explore/Explore.tsx

@@ -11,7 +11,8 @@ import {
   QueryHintGetter,
   QueryHint,
 } from 'app/types/explore';
-import { TimeRange, DataQuery } from 'app/types/series';
+import { TimeRange } from '@grafana/ui';
+import { DataQuery } from 'app/types/series';
 import store from 'app/core/store';
 import {
   DEFAULT_RANGE,

+ 1 - 1
public/app/features/explore/Graph.tsx

@@ -8,7 +8,7 @@ import 'vendor/flot/jquery.flot.time';
 import 'vendor/flot/jquery.flot.selection';
 import 'vendor/flot/jquery.flot.stack';
 
-import { RawTimeRange } from 'app/types/series';
+import { RawTimeRange } from '@grafana/ui';
 import * as dateMath from 'app/core/utils/datemath';
 import TimeSeries from 'app/core/time_series2';
 

+ 1 - 1
public/app/features/explore/Logs.tsx

@@ -4,7 +4,7 @@ import Highlighter from 'react-highlight-words';
 import classnames from 'classnames';
 
 import * as rangeUtil from 'app/core/utils/rangeutil';
-import { RawTimeRange } from 'app/types/series';
+import { RawTimeRange } from '@grafana/ui';
 import {
   LogsDedupDescription,
   LogsDedupStrategy,

+ 1 - 1
public/app/features/explore/QueryEditor.tsx

@@ -3,7 +3,7 @@ import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoa
 import { Emitter } from 'app/core/utils/emitter';
 import { getIntervals } from 'app/core/utils/explore';
 import { DataQuery } from 'app/types';
-import { RawTimeRange } from 'app/types/series';
+import { RawTimeRange } from '@grafana/ui';
 import { getTimeSrv } from 'app/features/dashboard/time_srv';
 import 'app/features/plugins/plugin_loader';
 

+ 1 - 1
public/app/features/explore/QueryRows.tsx

@@ -7,7 +7,7 @@ import { Emitter } from 'app/core/utils/emitter';
 import QueryEditor from './QueryEditor';
 import QueryTransactionStatus from './QueryTransactionStatus';
 import { DataSource, DataQuery } from 'app/types';
-import { RawTimeRange } from 'app/types/series';
+import { RawTimeRange } from '@grafana/ui';
 
 function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint {
   const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0);

+ 1 - 1
public/app/features/explore/TimePicker.tsx

@@ -3,7 +3,7 @@ import moment from 'moment';
 
 import * as dateMath from 'app/core/utils/datemath';
 import * as rangeUtil from 'app/core/utils/rangeutil';
-import { RawTimeRange, TimeRange } from 'app/types/series';
+import { RawTimeRange, TimeRange } from '@grafana/ui';
 
 const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
 export const DEFAULT_RANGE = {

+ 1 - 1
public/app/features/plugins/PluginList.tsx

@@ -1,5 +1,5 @@
 import React, { SFC } from 'react';
-import classNames from 'classnames/bind';
+import classNames from 'classnames';
 import PluginListItem from './PluginListItem';
 import { Plugin } from 'app/types';
 import { LayoutMode, LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';

+ 2 - 0
public/app/features/plugins/plugin_loader.ts

@@ -26,6 +26,7 @@ import * as ticks from 'app/core/utils/ticks';
 import impressionSrv from 'app/core/services/impression_srv';
 import builtInPlugins from './built_in_plugins';
 import * as d3 from 'd3';
+import * as grafanaUI from '@grafana/ui';
 
 // rxjs
 import { Observable } from 'rxjs/Observable';
@@ -71,6 +72,7 @@ function exposeToPlugin(name: string, component: any) {
   });
 }
 
+exposeToPlugin('@grafana/ui', grafanaUI);
 exposeToPlugin('lodash', _);
 exposeToPlugin('moment', moment);
 exposeToPlugin('jquery', jquery);

+ 2 - 2
public/app/features/teams/TeamList.tsx

@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
 import { connect } from 'react-redux';
 import { hot } from 'react-hot-loader';
 import PageHeader from 'app/core/components/PageHeader/PageHeader';
-import DeleteButton from 'app/core/components/DeleteButton/DeleteButton';
+import { DeleteButton } from '@grafana/ui';
 import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
 import PageLoader from 'app/core/components/PageLoader/PageLoader';
 import { NavModel, Team } from '../../types';
@@ -58,7 +58,7 @@ export class TeamList extends PureComponent<Props, any> {
           <a href={teamUrl}>{team.memberCount}</a>
         </td>
         <td className="text-right">
-          <DeleteButton onConfirmDelete={() => this.deleteTeam(team)} />
+          <DeleteButton onConfirm={() => this.deleteTeam(team)} />
         </td>
       </tr>
     );

+ 2 - 2
public/app/features/teams/TeamMembers.tsx

@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
 import { connect } from 'react-redux';
 import SlideDown from 'app/core/components/Animations/SlideDown';
 import { UserPicker } from 'app/core/components/Select/UserPicker';
-import DeleteButton from 'app/core/components/DeleteButton/DeleteButton';
+import { DeleteButton } from '@grafana/ui';
 import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
 import { TeamMember, User } from 'app/types';
 import { loadTeamMembers, addTeamMember, removeTeamMember, setSearchMemberQuery } from './state/actions';
@@ -76,7 +76,7 @@ export class TeamMembers extends PureComponent<Props, State> {
         <td>{member.email}</td>
         {syncEnabled && this.renderLabels(member.labels)}
         <td className="text-right">
-          <DeleteButton onConfirmDelete={() => this.onRemoveMember(member)} />
+          <DeleteButton onConfirm={() => this.onRemoveMember(member)} />
         </td>
       </tr>
     );

+ 5 - 5
public/app/features/teams/__snapshots__/TeamList.test.tsx.snap

@@ -124,7 +124,7 @@ exports[`Render should render teams table 1`] = `
               className="text-right"
             >
               <DeleteButton
-                onConfirmDelete={[Function]}
+                onConfirm={[Function]}
               />
             </td>
           </tr>
@@ -174,7 +174,7 @@ exports[`Render should render teams table 1`] = `
               className="text-right"
             >
               <DeleteButton
-                onConfirmDelete={[Function]}
+                onConfirm={[Function]}
               />
             </td>
           </tr>
@@ -224,7 +224,7 @@ exports[`Render should render teams table 1`] = `
               className="text-right"
             >
               <DeleteButton
-                onConfirmDelete={[Function]}
+                onConfirm={[Function]}
               />
             </td>
           </tr>
@@ -274,7 +274,7 @@ exports[`Render should render teams table 1`] = `
               className="text-right"
             >
               <DeleteButton
-                onConfirmDelete={[Function]}
+                onConfirm={[Function]}
               />
             </td>
           </tr>
@@ -324,7 +324,7 @@ exports[`Render should render teams table 1`] = `
               className="text-right"
             >
               <DeleteButton
-                onConfirmDelete={[Function]}
+                onConfirm={[Function]}
               />
             </td>
           </tr>

+ 10 - 10
public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap

@@ -204,7 +204,7 @@ exports[`Render should render team members 1`] = `
             className="text-right"
           >
             <DeleteButton
-              onConfirmDelete={[Function]}
+              onConfirm={[Function]}
             />
           </td>
         </tr>
@@ -229,7 +229,7 @@ exports[`Render should render team members 1`] = `
             className="text-right"
           >
             <DeleteButton
-              onConfirmDelete={[Function]}
+              onConfirm={[Function]}
             />
           </td>
         </tr>
@@ -254,7 +254,7 @@ exports[`Render should render team members 1`] = `
             className="text-right"
           >
             <DeleteButton
-              onConfirmDelete={[Function]}
+              onConfirm={[Function]}
             />
           </td>
         </tr>
@@ -279,7 +279,7 @@ exports[`Render should render team members 1`] = `
             className="text-right"
           >
             <DeleteButton
-              onConfirmDelete={[Function]}
+              onConfirm={[Function]}
             />
           </td>
         </tr>
@@ -304,7 +304,7 @@ exports[`Render should render team members 1`] = `
             className="text-right"
           >
             <DeleteButton
-              onConfirmDelete={[Function]}
+              onConfirm={[Function]}
             />
           </td>
         </tr>
@@ -441,7 +441,7 @@ exports[`Render should render team members when sync enabled 1`] = `
             className="text-right"
           >
             <DeleteButton
-              onConfirmDelete={[Function]}
+              onConfirm={[Function]}
             />
           </td>
         </tr>
@@ -482,7 +482,7 @@ exports[`Render should render team members when sync enabled 1`] = `
             className="text-right"
           >
             <DeleteButton
-              onConfirmDelete={[Function]}
+              onConfirm={[Function]}
             />
           </td>
         </tr>
@@ -523,7 +523,7 @@ exports[`Render should render team members when sync enabled 1`] = `
             className="text-right"
           >
             <DeleteButton
-              onConfirmDelete={[Function]}
+              onConfirm={[Function]}
             />
           </td>
         </tr>
@@ -564,7 +564,7 @@ exports[`Render should render team members when sync enabled 1`] = `
             className="text-right"
           >
             <DeleteButton
-              onConfirmDelete={[Function]}
+              onConfirm={[Function]}
             />
           </td>
         </tr>
@@ -605,7 +605,7 @@ exports[`Render should render team members when sync enabled 1`] = `
             className="text-right"
           >
             <DeleteButton
-              onConfirmDelete={[Function]}
+              onConfirm={[Function]}
             />
           </td>
         </tr>

+ 1 - 1
public/app/features/users/UsersActionBar.tsx

@@ -1,6 +1,6 @@
 import React, { PureComponent } from 'react';
 import { connect } from 'react-redux';
-import classNames from 'classnames/bind';
+import classNames from 'classnames';
 import { setUsersSearchQuery } from './state/actions';
 import { getInviteesCount, getUsersSearchQuery } from './state/selectors';
 

+ 2 - 1
public/app/plugins/panel/gauge/Threshold.test.tsx

@@ -2,7 +2,8 @@ import React from 'react';
 import { shallow } from 'enzyme';
 import Thresholds from './Thresholds';
 import { defaultProps, OptionsProps } from './module';
-import { BasicGaugeColor, PanelOptionsProps } from 'app/types';
+import { BasicGaugeColor } from 'app/types';
+import { PanelOptionsProps } from '@grafana/ui';
 
 const setup = (propOverrides?: object) => {
   const props: PanelOptionsProps<OptionsProps> = {

+ 2 - 9
public/app/plugins/panel/gauge/module.tsx

@@ -5,15 +5,8 @@ import ValueOptions from './ValueOptions';
 import GaugeOptions from './GaugeOptions';
 import Thresholds from './Thresholds';
 import ValueMappings from './ValueMappings';
-import {
-  BasicGaugeColor,
-  NullValueMode,
-  PanelOptionsProps,
-  PanelProps,
-  RangeMap,
-  Threshold,
-  ValueMap,
-} from 'app/types';
+import { PanelOptionsProps, PanelProps, NullValueMode } from '@grafana/ui';
+import { BasicGaugeColor, RangeMap, Threshold, ValueMap } from 'app/types';
 
 export interface OptionsProps {
   baseColor: string;

+ 1 - 0
public/app/plugins/panel/graph/specs/graph.test.ts

@@ -114,6 +114,7 @@ describe('grafanaGraph', () => {
       {}
     );
 
+    // @ts-ignore
     $.plot = ctrl.plot = jest.fn();
     scope.ctrl = ctrl;
 

+ 1 - 1
public/app/plugins/panel/graph2/GraphOptions.tsx

@@ -6,7 +6,7 @@ import React, { PureComponent } from 'react';
 import { Switch } from 'app/core/components/Switch/Switch';
 
 // Types
-import { PanelOptionsProps } from 'app/types';
+import { PanelOptionsProps } from '@grafana/ui';
 import { Options } from './types';
 
 export class GraphOptions extends PureComponent<PanelOptionsProps<Options>> {

+ 5 - 9
public/app/plugins/panel/graph2/GraphPanel.tsx

@@ -1,15 +1,10 @@
 // Libraries
 import _ from 'lodash';
 import React, { PureComponent } from 'react';
+import colors from 'app/core/utils/colors';
 
-// Components
-import Graph from 'app/viz/Graph';
-
-// Services & Utils
-import { getTimeSeriesVMs } from 'app/viz/state/timeSeries';
-
-// Types
-import { PanelProps, NullValueMode } from 'app/types';
+// Components & Types
+import { Graph, PanelProps, NullValueMode, processTimeSeries } from '@grafana/ui';
 import { Options } from './types';
 
 interface Props extends PanelProps<Options> {}
@@ -23,9 +18,10 @@ export class GraphPanel extends PureComponent<Props> {
     const { timeSeries, timeRange, width, height } = this.props;
     const { showLines, showBars, showPoints } = this.props.options;
 
-    const vmSeries = getTimeSeriesVMs({
+    const vmSeries = processTimeSeries({
       timeSeries: timeSeries,
       nullValueMode: NullValueMode.Ignore,
+      colorPalette: colors,
     });
 
     return (

+ 1 - 1
public/app/plugins/panel/text2/module.tsx

@@ -1,5 +1,5 @@
 import React, { PureComponent } from 'react';
-import { PanelProps } from 'app/types';
+import { PanelProps } from '@grafana/ui';
 
 export class Text2 extends PureComponent<PanelProps> {
   constructor(props) {

+ 2 - 1
public/app/types/explore.ts

@@ -1,6 +1,7 @@
 import { Value } from 'slate';
 
-import { DataQuery, RawTimeRange } from './series';
+import { DataQuery } from './series';
+import { RawTimeRange } from '@grafana/ui';
 import TableModel from 'app/core/table_model';
 import { LogsModel } from 'app/core/logs_model';
 import { DataSourceSelectItem } from 'app/types/datasources';

+ 2 - 24
public/app/types/index.ts

@@ -8,20 +8,8 @@ import { DashboardAcl, OrgRole, PermissionLevel } from './acl';
 import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys';
 import { Invitee, OrgUser, User, UsersState, UserState } from './user';
 import { DataSource, DataSourceSelectItem, DataSourcesState } from './datasources';
-import {
-  TimeRange,
-  LoadingState,
-  TimeSeries,
-  TimeSeriesVM,
-  TimeSeriesVMs,
-  TimeSeriesStats,
-  NullValueMode,
-  DataQuery,
-  DataQueryResponse,
-  DataQueryOptions,
-  IntervalValues,
-} from './series';
-import { BasicGaugeColor, MappingType, PanelProps, PanelOptionsProps, RangeMap, Threshold, ValueMap } from './panel';
+import { DataQuery, DataQueryResponse, DataQueryOptions } from './series';
+import { BasicGaugeColor, MappingType, RangeMap, Threshold, ValueMap } from './panel';
 import { PluginDashboard, PluginMeta, Plugin, PanelPlugin, PluginsState } from './plugins';
 import { Organization, OrganizationState } from './organization';
 import {
@@ -68,16 +56,7 @@ export {
   OrgUser,
   User,
   UsersState,
-  TimeRange,
-  LoadingState,
   PanelPlugin,
-  PanelProps,
-  PanelOptionsProps,
-  TimeSeries,
-  TimeSeriesVM,
-  TimeSeriesVMs,
-  NullValueMode,
-  TimeSeriesStats,
   DataQuery,
   DataQueryResponse,
   DataQueryOptions,
@@ -95,7 +74,6 @@ export {
   ValidationRule,
   ValueMap,
   RangeMap,
-  IntervalValues,
   MappingType,
   BasicGaugeColor,
 };

+ 17 - 0
public/app/types/jquery.d.ts

@@ -0,0 +1,17 @@
+interface JQueryPlot {
+  (element: HTMLElement | JQuery, data: any, options: any): void;
+  plugins: any[];
+}
+
+interface JQueryStatic {
+  plot: JQueryPlot;
+}
+
+interface JQuery {
+  place_tt: any;
+  modal: any;
+  tagsinput: any;
+  typeahead: any;
+  accessKey: any;
+  tooltip: any;
+}

+ 0 - 31
public/app/types/panel.ts

@@ -1,34 +1,3 @@
-import { LoadingState, TimeSeries, TimeRange } from './series';
-
-export interface PanelProps<T = any> {
-  timeSeries: TimeSeries[];
-  timeRange: TimeRange;
-  loading: LoadingState;
-  options: T;
-  renderCounter: number;
-  width: number;
-  height: number;
-}
-
-export interface PanelOptionsProps<T = any> {
-  options: T;
-  onChange: (options: T) => void;
-}
-
-export interface PanelSize {
-  width: number;
-  height: number;
-}
-
-export interface PanelMenuItem {
-  type?: 'submenu' | 'divider';
-  text?: string;
-  iconClassName?: string;
-  onClick?: () => void;
-  shortcut?: string;
-  subMenu?: PanelMenuItem[];
-}
-
 export interface Threshold {
   index: number;
   value: number;

+ 1 - 1
public/app/types/plugins.ts

@@ -1,5 +1,5 @@
 import { ComponentClass } from 'react';
-import { PanelProps, PanelOptionsProps } from './panel';
+import { PanelProps, PanelOptionsProps } from '@grafana/ui';
 
 export interface PluginExports {
   Datasource?: any;

+ 1 - 71
public/app/types/series.ts

@@ -1,75 +1,5 @@
-import { Moment } from 'moment';
 import { PluginMeta } from './plugins';
-
-export enum LoadingState {
-  NotStarted = 'NotStarted',
-  Loading = 'Loading',
-  Done = 'Done',
-  Error = 'Error',
-}
-
-export interface RawTimeRange {
-  from: Moment | string;
-  to: Moment | string;
-}
-
-export interface TimeRange {
-  from: Moment;
-  to: Moment;
-  raw: RawTimeRange;
-}
-
-export interface IntervalValues {
-  interval: string; // 10s,5m
-  intervalMs: number;
-}
-
-export type TimeSeriesValue = string | number | null;
-
-export type TimeSeriesPoints = TimeSeriesValue[][];
-
-export interface TimeSeries {
-  target: string;
-  datapoints: TimeSeriesPoints;
-  unit?: string;
-}
-
-/** View model projection of a time series */
-export interface TimeSeriesVM {
-  label: string;
-  color: string;
-  data: TimeSeriesValue[][];
-  stats: TimeSeriesStats;
-}
-
-export interface TimeSeriesStats {
-  total: number;
-  max: number;
-  min: number;
-  logmin: number;
-  avg: number | null;
-  current: number | null;
-  first: number | null;
-  delta: number;
-  diff: number | null;
-  range: number | null;
-  timeStep: number;
-  count: number;
-  allIsNull: boolean;
-  allIsZero: boolean;
-}
-
-export enum NullValueMode {
-  Null = 'null',
-  Ignore = 'connected',
-  AsZero = 'null as zero',
-}
-
-/** View model projection of many time series */
-export interface TimeSeriesVMs {
-  [index: number]: TimeSeriesVM;
-  length: number;
-}
+import { TimeSeries, TimeRange, RawTimeRange } from '@grafana/ui';
 
 export interface DataQueryResponse {
   data: TimeSeries[];

+ 2 - 1
public/app/viz/Gauge.tsx

@@ -1,6 +1,7 @@
 import React, { PureComponent } from 'react';
 import $ from 'jquery';
-import { BasicGaugeColor, MappingType, RangeMap, Threshold, TimeSeriesVMs, ValueMap } from 'app/types';
+import { BasicGaugeColor, MappingType, RangeMap, Threshold, ValueMap } from 'app/types';
+import { TimeSeriesVMs } from '@grafana/ui';
 import config from '../core/config';
 import kbn from '../core/utils/kbn';
 

+ 1 - 1
public/app/viz/state/timeSeries.ts

@@ -5,7 +5,7 @@ import _ from 'lodash';
 import colors from 'app/core/utils/colors';
 
 // Types
-import { TimeSeries, TimeSeriesVMs, NullValueMode } from 'app/types';
+import { TimeSeries, TimeSeriesVMs, NullValueMode } from '@grafana/ui';
 
 interface Options {
   timeSeries: TimeSeries[];

+ 3 - 1
public/sass/_grafana.scss

@@ -38,6 +38,9 @@
 @import 'layout/lists';
 @import 'layout/page';
 
+// LOAD @grafana/ui components
+@import '../../packages/grafana-ui/src/index';
+
 // COMPONENTS
 @import 'components/scrollbar';
 @import 'components/cards';
@@ -98,7 +101,6 @@
 @import 'components/form_select_box';
 @import 'components/panel_editor';
 @import 'components/toolbar';
-@import 'components/delete_button';
 @import 'components/add_data_source.scss';
 @import 'components/page_loader';
 @import 'components/thresholds';

+ 16 - 6
scripts/grunt/default_task.js

@@ -10,16 +10,26 @@ module.exports = function (grunt) {
 
   grunt.registerTask('test', [
     'sasslint',
-    'exec:tsc',
-    'exec:tslint',
-    'exec:jest',
+    'tslint',
+    'typecheck',
+    "exec:jest",
     'no-only-tests'
   ]);
 
+  grunt.registerTask('tslint', [
+    'newer:exec:tslintPackages',
+    'newer:exec:tslintRoot',
+  ]);
+
+  grunt.registerTask('typecheck', [
+    'newer:exec:typecheckPackages',
+    'newer:exec:typecheckRoot',
+  ]);
+
   grunt.registerTask('precommit', [
-    'sasslint',
-    'newer:exec:tslint',
-    'newer:exec:tsc',
+    'newer:sasslint',
+    'typecheck',
+    'tslint',
     'no-only-tests'
   ]);
 

+ 12 - 4
scripts/grunt/options/exec.js

@@ -2,12 +2,20 @@ module.exports = function (config, grunt) {
   'use strict';
 
   return {
-    tslint: {
-      command: 'node ./node_modules/tslint/lib/tslintCli.js -c tslint.json --project ./tsconfig.json',
+    tslintPackages: {
+      command: 'yarn workspaces run tslint',
+      src: ['packages/**/*.ts*'],
+    },
+    tslintRoot: {
+      command: 'yarn run tslint',
       src: ['public/app/**/*.ts*'],
     },
-    tsc: {
-      command: 'yarn tsc --noEmit',
+    typecheckPackages: {
+      command: 'yarn workspaces run typecheck',
+      src: ['packages/**/*.ts*'],
+    },
+    typecheckRoot: {
+      command: 'yarn run typecheck',
       src: ['public/app/**/*.ts*'],
     },
     jest: 'node ./node_modules/jest-cli/bin/jest.js --maxWorkers 2',

+ 1 - 0
scripts/grunt/options/sasslint.js

@@ -4,6 +4,7 @@ module.exports = function(config) {
     options: {
       configFile: 'public/sass/.sass-lint.yml',
     },
+    // src: ['public/sass#<{(||)}>#*'],
     target: [
       'public/sass/*.scss',
       'public/sass/components/*.scss',

+ 0 - 11
scripts/grunt/options/tslint.js

@@ -1,11 +0,0 @@
-module.exports = function(config, grunt) {
-  'use strict'
-  // dummy to avoid template compile error
-  return {
-    source: {
-      files: {
-        src: ""
-      }
-    }
-  };
-};

+ 1 - 0
tsconfig.json

@@ -26,6 +26,7 @@
     "noUnusedLocals": true,
     "baseUrl": "public",
     "pretty": true,
+    "typeRoots": ["node_modules/@types", "types"],
     "paths": {
       "app": ["app"]
     }

+ 22 - 2
yarn.lock

@@ -804,6 +804,11 @@
   resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.9.tgz#b5990152604c2ada749b7f88cab3476f21f39d7b"
   integrity sha512-q6LuBI0t5u04f0Q4/R+cGBqIbZMtJkVvCSF+nTfFBBdQqQvJR/mNHeWjRkszyLl7oyf2rDoKUYMEjTw5AV0hiw==
 
+"@types/classnames@^2.2.6":
+  version "2.2.6"
+  resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.6.tgz#dbe8a666156d556ed018e15a4c65f08937c3f628"
+  integrity sha512-XHcYvVdbtAxVstjKxuULYqYaWIzHR15yr1pZj4fnGChuBVJlIAp9StJna0ZJNSgxPh4Nac2FL4JM3M11Tm6fqQ==
+
 "@types/d3-array@*":
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.2.3.tgz#dd141e3ba311485fffbf0792a1b01a7f2ec12dc1"
@@ -1029,6 +1034,16 @@
   resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.4.tgz#cc43ae176a91dcb1504839b0b9d6659386cf0af5"
   integrity sha512-46jSw0QMerCRkhJZbOwPA0Eb9T1p74HtECsfa0GXdgjkenSGhgvK96w+e2PEPu4GF0/brUK5WQKq/rUQQFyAxA==
 
+"@types/jquery@^1.10.35":
+  version "1.10.35"
+  resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-1.10.35.tgz#4e5c2b1e5b3bf0b863efb8c5e70081f52e6c9518"
+  integrity sha512-SVtqEcudm7yjkTwoRA1gC6CNMhGDdMx4Pg8BPdiqI7bXXdCn1BPmtxgeWYQOgDxrq53/5YTlhq5ULxBEAlWIBg==
+
+"@types/lodash@^4.14.119":
+  version "4.14.119"
+  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.119.tgz#be847e5f4bc3e35e46d041c394ead8b603ad8b39"
+  integrity sha512-Z3TNyBL8Vd/M9D9Ms2S3LmFq2sSMzahodD6rCS9V2N44HUMINb75jNkSuwAx7eo2ufqTdfOdtGQpNbieUjPQmw==
+
 "@types/node@*":
   version "10.11.4"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-10.11.4.tgz#e8bd933c3f78795d580ae41d86590bfc1f4f389d"
@@ -1083,7 +1098,7 @@
   dependencies:
     "@types/react" "*"
 
-"@types/react@*", "@types/react@16.7.6", "@types/react@^16.1.0", "@types/react@^16.7.6":
+"@types/react@*", "@types/react@^16.1.0", "@types/react@^16.7.6":
   version "16.7.6"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.6.tgz#80e4bab0d0731ad3ae51f320c4b08bdca5f03040"
   integrity sha512-QBUfzftr/8eg/q3ZRgf/GaDP6rTYc7ZNem+g4oZM38C9vXyV8AWRWaTQuW5yCoZTsfHrN7b3DeEiUnqH9SrnpA==
@@ -3153,7 +3168,7 @@ caniuse-api@^1.5.2:
     lodash.memoize "^4.1.2"
     lodash.uniq "^4.5.0"
 
-caniuse-db@1.0.30000772, caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
+caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
   version "1.0.30000772"
   resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b"
   integrity sha1-UarokXaChureSj2DGep21qAbUSs=
@@ -14544,6 +14559,11 @@ typescript@^3.0.3:
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.1.tgz#3362ba9dd1e482ebb2355b02dfe8bcd19a2c7c96"
   integrity sha512-Veu0w4dTc/9wlWNf2jeRInNodKlcdLgemvPsrNpfu5Pq39sgfFjvIIgTsvUHCoLBnMhPoUA+tFxsXjU6VexVRQ==
 
+typescript@^3.2.2:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5"
+  integrity sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==
+
 ua-parser-js@^0.7.18:
   version "0.7.19"
   resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b"