From 2379de53c445189190df4d50934042f98303dc5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Fri, 28 Jun 2019 10:47:19 +0200 Subject: [PATCH 01/17] Explore: Uses RFC3339Nano string to retrieve LogRow contexts from Loki API (#17813) * Refactor: Uses nanosecond string to retreive LogRow contexts * Reafactor: Small changes to comments after PR comments --- public/app/features/explore/LogRowContext.tsx | 8 +- .../explore/LogRowContextProvider.tsx | 91 +++++++++++++------ .../app/plugins/datasource/loki/datasource.ts | 4 +- 3 files changed, 64 insertions(+), 39 deletions(-) diff --git a/public/app/features/explore/LogRowContext.tsx b/public/app/features/explore/LogRowContext.tsx index da9c3ec481230..2e0e4b4c18615 100644 --- a/public/app/features/explore/LogRowContext.tsx +++ b/public/app/features/explore/LogRowContext.tsx @@ -103,12 +103,6 @@ const LogRowContextGroupHeader: React.FunctionComponent contextRow !== row.raw); - return (
- Found {logRowsToRender.length} rows. + Found {rows.length} rows. {(rows.length >= 10 || (rows.length > 10 && rows.length % 10 !== 0)) && canLoadMoreRows && ( JSX.Element; } +export const getRowContexts = async ( + getRowContext: (row: LogRowModel, options?: any) => Promise, + row: LogRowModel, + limit: number +) => { + const promises = [ + getRowContext(row, { + limit, + }), + getRowContext(row, { + limit: limit + 1, // Lets add one more to the limit as we're filtering out one row see comment below + direction: 'FORWARD', + }), + ]; + + const results: Array = await Promise.all(promises.map(p => p.catch(e => e))); + + return { + data: results.map((result, index) => { + const dataResult: DataQueryResponse = result as DataQueryResponse; + if (!dataResult.data) { + return []; + } + + // We need to filter out the row we're basing our search from because of how start/end params work in Loki API + // see https://github.com/grafana/loki/issues/597#issuecomment-506408980 + // the alternative to create our own add 1 nanosecond method to the a timestamp string would be quite complex + return dataResult.data.map(series => { + const filteredRows = series.rows.filter((r: any) => r[0] !== row.timestamp); + return filteredRows.map((row: any) => row[1]); + }); + }), + errors: results.map(result => { + const errorResult: DataQueryError = result as DataQueryError; + if (!errorResult.message) { + return null; + } + + return errorResult.message; + }), + }; +}; + export const LogRowContextProvider: React.FunctionComponent = ({ getRowContext, row, children, }) => { + // React Hook that creates a number state value called limit to component state and a setter function called setLimit + // The intial value for limit is 10 + // Used for the number of rows to retrieve from backend from a specific point in time const [limit, setLimit] = useState(10); + + // React Hook that creates an object state value called result to component state and a setter function called setResult + // The intial value for result is null + // Used for sorting the response from backend const [result, setResult] = useState<{ data: string[][]; errors: string[]; }>(null); + + // React Hook that creates an object state value called hasMoreContextRows to component state and a setter function called setHasMoreContextRows + // The intial value for hasMoreContextRows is {before: true, after: true} + // Used for indicating in UI if there are more rows to load in a given direction const [hasMoreContextRows, setHasMoreContextRows] = useState({ before: true, after: true, }); + // React Hook that resolves two promises every time the limit prop changes + // First promise fetches limit number of rows backwards in time from a specific point in time + // Second promise fetches limit number of rows forwards in time from a specific point in time const { value } = useAsync(async () => { - const promises = [ - getRowContext(row, { - limit, - }), - getRowContext(row, { - limit, - direction: 'FORWARD', - }), - ]; - - const results: Array = await Promise.all(promises.map(p => p.catch(e => e))); - - return { - data: results.map(result => { - if ((result as DataQueryResponse).data) { - return (result as DataQueryResponse).data.map(series => { - return series.rows.map(row => row[1]); - }); - } else { - return []; - } - }), - errors: results.map(result => { - if ((result as DataQueryError).message) { - return (result as DataQueryError).message; - } else { - return null; - } - }), - }; + return await getRowContexts(getRowContext, row, limit); // Moved it to a separate function for debugging purposes }, [limit]); + // React Hook that performs a side effect every time the value (from useAsync hook) prop changes + // The side effect changes the result state with the response from the useAsync hook + // The side effect changes the hasMoreContextRows state if there are more context rows before or after the current result useEffect(() => { if (value) { setResult(currentResult => { diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 55bb4f5260952..7a4a3f6ab432c 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -310,13 +310,13 @@ export class LokiDatasource extends DataSourceApi { return { ...commontTargetOptons, start: timeEpochNs - contextTimeBuffer, - end: timeEpochNs, + end: row.timestamp, direction, }; } else { return { ...commontTargetOptons, - start: timeEpochNs, // TODO: We should add 1ns here for the original row not no be included in the result + start: row.timestamp, // start param in Loki API is inclusive so we'll have to filter out the row that this request is based from end: timeEpochNs + contextTimeBuffer, }; } From ead4b1f5c7e0ad5d655c1d111efb6d74a777dd1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Fri, 28 Jun 2019 12:07:55 +0200 Subject: [PATCH 02/17] Explore: Uses new TimePicker from Grafana/UI (#17793) * Wip: Intiail commit * Refactor: Replaces TimePicker in Explore * Refactor: Removes Angular TimePicker folder * Refactor: Adds tests for getShiftedTimeRange * Fix: Fixes invalid import to removed TimePicker * Fix: Fixes dateTime tests * Refactor: Reuses getShiftedTimeRange for both Explore and Dashboards * Refactor: Shares getZoomedTimeRange between Explore and Dashboard --- public/app/core/utils/timePicker.test.ts | 79 +++++ public/app/core/utils/timePicker.ts | 38 +++ .../DashNav/DashNavTimeControls.tsx | 19 +- .../components/TimePicker/TimePickerCtrl.ts | 189 ----------- .../dashboard/components/TimePicker/index.ts | 1 - .../components/TimePicker/settings.html | 24 -- .../components/TimePicker/template.html | 84 ----- .../components/TimePicker/validation.ts | 49 --- public/app/features/dashboard/index.ts | 1 - .../features/dashboard/services/TimeSrv.ts | 8 +- public/app/features/explore/Explore.tsx | 26 +- .../features/explore/ExploreTimeControls.tsx | 112 +++++++ .../app/features/explore/ExploreToolbar.tsx | 41 +-- public/app/features/explore/Logs.tsx | 2 +- public/app/features/explore/LogsContainer.tsx | 14 +- .../app/features/explore/TimePicker.test.tsx | 238 -------------- public/app/features/explore/TimePicker.tsx | 305 ------------------ .../app/features/explore/state/actionTypes.ts | 17 +- public/app/features/explore/state/actions.ts | 15 +- .../epics/processQueryResultsEpic.test.ts | 10 +- .../state/epics/processQueryResultsEpic.ts | 16 +- .../features/explore/state/reducers.test.ts | 17 +- public/app/features/explore/state/reducers.ts | 10 +- public/app/store/configureStore.ts | 4 + public/app/types/explore.ts | 6 - public/sass/pages/_explore.scss | 4 + public/test/core/redux/epicTester.ts | 5 + 27 files changed, 302 insertions(+), 1032 deletions(-) create mode 100644 public/app/core/utils/timePicker.test.ts create mode 100644 public/app/core/utils/timePicker.ts delete mode 100644 public/app/features/dashboard/components/TimePicker/TimePickerCtrl.ts delete mode 100644 public/app/features/dashboard/components/TimePicker/index.ts delete mode 100644 public/app/features/dashboard/components/TimePicker/settings.html delete mode 100644 public/app/features/dashboard/components/TimePicker/template.html delete mode 100644 public/app/features/dashboard/components/TimePicker/validation.ts create mode 100644 public/app/features/explore/ExploreTimeControls.tsx delete mode 100644 public/app/features/explore/TimePicker.test.tsx delete mode 100644 public/app/features/explore/TimePicker.tsx diff --git a/public/app/core/utils/timePicker.test.ts b/public/app/core/utils/timePicker.test.ts new file mode 100644 index 0000000000000..b9c54a92d8a83 --- /dev/null +++ b/public/app/core/utils/timePicker.test.ts @@ -0,0 +1,79 @@ +import { toUtc, AbsoluteTimeRange } from '@grafana/ui'; + +import { getShiftedTimeRange, getZoomedTimeRange } from './timePicker'; + +export const setup = (options?: any) => { + const defaultOptions = { + range: { + from: toUtc('2019-01-01 10:00:00'), + to: toUtc('2019-01-01 16:00:00'), + raw: { + from: 'now-6h', + to: 'now', + }, + }, + direction: 0, + }; + + return { ...defaultOptions, ...options }; +}; + +describe('getShiftedTimeRange', () => { + describe('when called with a direction of -1', () => { + it('then it should return correct result', () => { + const { range, direction } = setup({ direction: -1 }); + const expectedRange: AbsoluteTimeRange = { + from: toUtc('2019-01-01 07:00:00').valueOf(), + to: toUtc('2019-01-01 13:00:00').valueOf(), + }; + + const result = getShiftedTimeRange(direction, range); + + expect(result).toEqual(expectedRange); + }); + }); + + describe('when called with a direction of 1', () => { + it('then it should return correct result', () => { + const { range, direction } = setup({ direction: 1 }); + const expectedRange: AbsoluteTimeRange = { + from: toUtc('2019-01-01 13:00:00').valueOf(), + to: toUtc('2019-01-01 19:00:00').valueOf(), + }; + + const result = getShiftedTimeRange(direction, range); + + expect(result).toEqual(expectedRange); + }); + }); + + describe('when called with any other direction', () => { + it('then it should return correct result', () => { + const { range, direction } = setup({ direction: 0 }); + const expectedRange: AbsoluteTimeRange = { + from: toUtc('2019-01-01 10:00:00').valueOf(), + to: toUtc('2019-01-01 16:00:00').valueOf(), + }; + + const result = getShiftedTimeRange(direction, range); + + expect(result).toEqual(expectedRange); + }); + }); +}); + +describe('getZoomedTimeRange', () => { + describe('when called', () => { + it('then it should return correct result', () => { + const { range } = setup(); + const expectedRange: AbsoluteTimeRange = { + from: toUtc('2019-01-01 07:00:00').valueOf(), + to: toUtc('2019-01-01 19:00:00').valueOf(), + }; + + const result = getZoomedTimeRange(range, 2); + + expect(result).toEqual(expectedRange); + }); + }); +}); diff --git a/public/app/core/utils/timePicker.ts b/public/app/core/utils/timePicker.ts new file mode 100644 index 0000000000000..974588857a5dc --- /dev/null +++ b/public/app/core/utils/timePicker.ts @@ -0,0 +1,38 @@ +import { TimeRange, toUtc, AbsoluteTimeRange } from '@grafana/ui'; + +export const getShiftedTimeRange = (direction: number, origRange: TimeRange): AbsoluteTimeRange => { + const range = { + from: toUtc(origRange.from), + to: toUtc(origRange.to), + }; + + const timespan = (range.to.valueOf() - range.from.valueOf()) / 2; + let to: number, from: number; + + if (direction === -1) { + to = range.to.valueOf() - timespan; + from = range.from.valueOf() - timespan; + } else if (direction === 1) { + to = range.to.valueOf() + timespan; + from = range.from.valueOf() + timespan; + if (to > Date.now() && range.to.valueOf() < Date.now()) { + to = Date.now(); + from = range.from.valueOf(); + } + } else { + to = range.to.valueOf(); + from = range.from.valueOf(); + } + + return { from, to }; +}; + +export const getZoomedTimeRange = (range: TimeRange, factor: number): AbsoluteTimeRange => { + const timespan = range.to.valueOf() - range.from.valueOf(); + const center = range.to.valueOf() - timespan / 2; + + const to = center + (timespan * factor) / 2; + const from = center - (timespan * factor) / 2; + + return { from, to }; +}; diff --git a/public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx b/public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx index 065596d73df0a..401e9347ec548 100644 --- a/public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx @@ -16,6 +16,7 @@ import { TimePicker, RefreshPicker, RawTimeRange } from '@grafana/ui'; // Utils & Services import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { defaultSelectOptions } from '@grafana/ui/src/components/TimePicker/TimePicker'; +import { getShiftedTimeRange } from 'app/core/utils/timePicker'; export interface Props { $injector: any; @@ -44,23 +45,7 @@ export class DashNavTimeControls extends Component { onMoveTimePicker = (direction: number) => { const range = this.timeSrv.timeRange(); - const timespan = (range.to.valueOf() - range.from.valueOf()) / 2; - let to: number, from: number; - - if (direction === -1) { - to = range.to.valueOf() - timespan; - from = range.from.valueOf() - timespan; - } else if (direction === 1) { - to = range.to.valueOf() + timespan; - from = range.from.valueOf() + timespan; - if (to > Date.now() && range.to.valueOf() < Date.now()) { - to = Date.now(); - from = range.from.valueOf(); - } - } else { - to = range.to.valueOf(); - from = range.from.valueOf(); - } + const { from, to } = getShiftedTimeRange(direction, range); this.timeSrv.setTime({ from: toUtc(from), diff --git a/public/app/features/dashboard/components/TimePicker/TimePickerCtrl.ts b/public/app/features/dashboard/components/TimePicker/TimePickerCtrl.ts deleted file mode 100644 index f1d626e9e3479..0000000000000 --- a/public/app/features/dashboard/components/TimePicker/TimePickerCtrl.ts +++ /dev/null @@ -1,189 +0,0 @@ -import _ from 'lodash'; -import angular from 'angular'; - -import * as rangeUtil from '@grafana/ui/src/utils/rangeutil'; - -export class TimePickerCtrl { - static tooltipFormat = 'MMM D, YYYY HH:mm:ss'; - static defaults = { - time_options: ['5m', '15m', '1h', '6h', '12h', '24h', '2d', '7d', '30d'], - refresh_intervals: ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'], - }; - - dashboard: any; - panel: any; - absolute: any; - timeRaw: any; - editTimeRaw: any; - tooltip: string; - rangeString: string; - timeOptions: any; - refresh: any; - isUtc: boolean; - firstDayOfWeek: number; - isOpen: boolean; - isAbsolute: boolean; - - /** @ngInject */ - constructor(private $scope, private $rootScope, private timeSrv) { - this.$scope.ctrl = this; - - $rootScope.onAppEvent('shift-time-forward', () => this.move(1), $scope); - $rootScope.onAppEvent('shift-time-backward', () => this.move(-1), $scope); - $rootScope.onAppEvent('closeTimepicker', this.openDropdown.bind(this), $scope); - - this.dashboard.on('refresh', this.onRefresh.bind(this), $scope); - - // init options - this.panel = this.dashboard.timepicker; - _.defaults(this.panel, TimePickerCtrl.defaults); - this.firstDayOfWeek = getLocaleData().firstDayOfWeek(); - - // init time stuff - this.onRefresh(); - } - - onRefresh() { - const time = angular.copy(this.timeSrv.timeRange()); - const timeRaw = angular.copy(time.raw); - - if (!this.dashboard.isTimezoneUtc()) { - time.from.local(); - time.to.local(); - if (isDateTime(timeRaw.from)) { - timeRaw.from.local(); - } - if (isDateTime(timeRaw.to)) { - timeRaw.to.local(); - } - this.isUtc = false; - } else { - this.isUtc = true; - } - - this.rangeString = rangeUtil.describeTimeRange(timeRaw); - this.absolute = { fromJs: time.from.toDate(), toJs: time.to.toDate() }; - this.tooltip = this.dashboard.formatDate(time.from) + '
to
'; - this.tooltip += this.dashboard.formatDate(time.to); - this.timeRaw = timeRaw; - this.isAbsolute = isDateTime(this.timeRaw.to); - } - - zoom(factor) { - this.$rootScope.appEvent('zoom-out', 2); - } - - move(direction) { - const range = this.timeSrv.timeRange(); - - const timespan = (range.to.valueOf() - range.from.valueOf()) / 2; - let to, from; - if (direction === -1) { - to = range.to.valueOf() - timespan; - from = range.from.valueOf() - timespan; - } else if (direction === 1) { - to = range.to.valueOf() + timespan; - from = range.from.valueOf() + timespan; - if (to > Date.now() && range.to < Date.now()) { - to = Date.now(); - from = range.from.valueOf(); - } - } else { - to = range.to.valueOf(); - from = range.from.valueOf(); - } - - this.timeSrv.setTime({ from: toUtc(from), to: toUtc(to) }); - } - - openDropdown() { - if (this.isOpen) { - this.closeDropdown(); - return; - } - - this.onRefresh(); - this.editTimeRaw = this.timeRaw; - this.timeOptions = rangeUtil.getRelativeTimesList(this.panel, this.rangeString); - this.refresh = { - value: this.dashboard.refresh, - options: this.panel.refresh_intervals.map((interval: any) => { - return { text: interval, value: interval }; - }), - }; - - this.refresh.options.unshift({ text: 'off' }); - this.isOpen = true; - this.$rootScope.appEvent('timepickerOpen'); - } - - closeDropdown() { - this.isOpen = false; - this.$rootScope.appEvent('timepickerClosed'); - } - - applyCustom() { - if (this.refresh.value !== this.dashboard.refresh) { - this.timeSrv.setAutoRefresh(this.refresh.value); - } - - this.timeSrv.setTime(this.editTimeRaw); - this.closeDropdown(); - } - - absoluteFromChanged() { - this.editTimeRaw.from = this.getAbsoluteMomentForTimezone(this.absolute.fromJs); - } - - absoluteToChanged() { - this.editTimeRaw.to = this.getAbsoluteMomentForTimezone(this.absolute.toJs); - } - - getAbsoluteMomentForTimezone(jsDate) { - return this.dashboard.isTimezoneUtc() ? dateTime(jsDate).utc() : dateTime(jsDate); - } - - setRelativeFilter(timespan) { - const range = { from: timespan.from, to: timespan.to }; - - if (this.panel.nowDelay && range.to === 'now') { - range.to = 'now-' + this.panel.nowDelay; - } - - this.timeSrv.setTime(range); - this.closeDropdown(); - } -} - -export function settingsDirective() { - return { - restrict: 'E', - templateUrl: 'public/app/features/dashboard/components/TimePicker/settings.html', - controller: TimePickerCtrl, - bindToController: true, - controllerAs: 'ctrl', - scope: { - dashboard: '=', - }, - }; -} - -export function timePickerDirective() { - return { - restrict: 'E', - templateUrl: 'public/app/features/dashboard/components/TimePicker/template.html', - controller: TimePickerCtrl, - bindToController: true, - controllerAs: 'ctrl', - scope: { - dashboard: '=', - }, - }; -} - -angular.module('grafana.directives').directive('gfTimePickerSettings', settingsDirective); -angular.module('grafana.directives').directive('gfTimePicker', timePickerDirective); - -import { inputDateDirective } from './validation'; -import { toUtc, getLocaleData, isDateTime, dateTime } from '@grafana/ui/src/utils/moment_wrapper'; -angular.module('grafana.directives').directive('inputDatetime', inputDateDirective); diff --git a/public/app/features/dashboard/components/TimePicker/index.ts b/public/app/features/dashboard/components/TimePicker/index.ts deleted file mode 100644 index ca6e2792c43ae..0000000000000 --- a/public/app/features/dashboard/components/TimePicker/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TimePickerCtrl } from './TimePickerCtrl'; diff --git a/public/app/features/dashboard/components/TimePicker/settings.html b/public/app/features/dashboard/components/TimePicker/settings.html deleted file mode 100644 index fd5170013c299..0000000000000 --- a/public/app/features/dashboard/components/TimePicker/settings.html +++ /dev/null @@ -1,24 +0,0 @@ -
-
Time Options
- -
-
- -
- -
-
- -
- Auto-refresh - -
-
- Now delay now- - -
- - -
-
diff --git a/public/app/features/dashboard/components/TimePicker/template.html b/public/app/features/dashboard/components/TimePicker/template.html deleted file mode 100644 index 2821dd0ced537..0000000000000 --- a/public/app/features/dashboard/components/TimePicker/template.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - -
-
-
- Quick ranges -
-
-
    -
  • - -
  • -
-
-
- -
-
- Custom range -
-
- -
-
- -
-
- -
-
- -
- -
- - - -
-
- -
-
- -
-
- -
- -
- -
-
- -
-
-
-
-
- diff --git a/public/app/features/dashboard/components/TimePicker/validation.ts b/public/app/features/dashboard/components/TimePicker/validation.ts deleted file mode 100644 index a99409a0fb0c6..0000000000000 --- a/public/app/features/dashboard/components/TimePicker/validation.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as dateMath from '@grafana/ui/src/utils/datemath'; -import { toUtc, dateTime, isDateTime } from '@grafana/ui/src/utils/moment_wrapper'; - -export function inputDateDirective() { - return { - restrict: 'A', - require: 'ngModel', - link: ($scope, $elem, attrs, ngModel) => { - const format = 'YYYY-MM-DD HH:mm:ss'; - - const fromUser = text => { - if (text.indexOf('now') !== -1) { - if (!dateMath.isValid(text)) { - ngModel.$setValidity('error', false); - return undefined; - } - ngModel.$setValidity('error', true); - return text; - } - - let parsed; - if ($scope.ctrl.isUtc) { - parsed = toUtc(text, format); - } else { - parsed = dateTime(text, format); - } - - if (!parsed.isValid()) { - ngModel.$setValidity('error', false); - return undefined; - } - - ngModel.$setValidity('error', true); - return parsed; - }; - - const toUser = currentValue => { - if (isDateTime(currentValue)) { - return currentValue.format(format); - } else { - return currentValue; - } - }; - - ngModel.$parsers.push(fromUser); - ngModel.$formatters.push(toUser); - }, - }; -} diff --git a/public/app/features/dashboard/index.ts b/public/app/features/dashboard/index.ts index 1a326d73bd9e9..e2c042ef13826 100644 --- a/public/app/features/dashboard/index.ts +++ b/public/app/features/dashboard/index.ts @@ -12,7 +12,6 @@ import './components/FolderPicker'; import './components/VersionHistory'; import './components/DashboardSettings'; import './components/SubMenu'; -import './components/TimePicker'; import './components/UnsavedChangesModal'; import './components/SaveModals'; import './components/ShareModal'; diff --git a/public/app/features/dashboard/services/TimeSrv.ts b/public/app/features/dashboard/services/TimeSrv.ts index 1716563874e25..5c1a305a2ba1c 100644 --- a/public/app/features/dashboard/services/TimeSrv.ts +++ b/public/app/features/dashboard/services/TimeSrv.ts @@ -12,6 +12,7 @@ import { ITimeoutService, ILocationService } from 'angular'; import { ContextSrv } from 'app/core/services/context_srv'; import { DashboardModel } from '../state/DashboardModel'; import { toUtc, dateTime, isDateTime } from '@grafana/ui/src/utils/moment_wrapper'; +import { getZoomedTimeRange } from 'app/core/utils/timePicker'; export class TimeSrv { time: any; @@ -238,12 +239,7 @@ export class TimeSrv { zoomOut(e: any, factor: number) { const range = this.timeRange(); - - const timespan = range.to.valueOf() - range.from.valueOf(); - const center = range.to.valueOf() - timespan / 2; - - const to = center + (timespan * factor) / 2; - const from = center - (timespan * factor) / 2; + const { from, to } = getZoomedTimeRange(range, factor); this.setTime({ from: toUtc(from), to: toUtc(to) }); } diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index b662d961158a7..ffd04e6a23559 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -16,7 +16,6 @@ import GraphContainer from './GraphContainer'; import LogsContainer from './LogsContainer'; import QueryRows from './QueryRows'; import TableContainer from './TableContainer'; -import TimePicker from './TimePicker'; // Actions import { @@ -35,7 +34,6 @@ import { RawTimeRange, DataQuery, ExploreStartPageProps, DataSourceApi, DataQuer import { ExploreItemState, ExploreUrlState, - RangeScanner, ExploreId, ExploreUpdateState, ExploreUIState, @@ -71,7 +69,6 @@ interface ExploreProps { update: ExploreUpdateState; reconnectDatasource: typeof reconnectDatasource; refreshExplore: typeof refreshExplore; - scanner?: RangeScanner; scanning?: boolean; scanRange?: RawTimeRange; scanStart: typeof scanStart; @@ -117,15 +114,10 @@ interface ExploreProps { export class Explore extends React.PureComponent { el: any; exploreEvents: Emitter; - /** - * Timepicker to control scanning - */ - timepickerRef: React.RefObject; constructor(props: ExploreProps) { super(props); this.exploreEvents = new Emitter(); - this.timepickerRef = React.createRef(); } componentDidMount() { @@ -159,11 +151,9 @@ export class Explore extends React.PureComponent { this.el = el; }; - onChangeTime = (rawRange: RawTimeRange, changedByScanner?: boolean) => { - const { updateTimeRange, exploreId, scanning } = this.props; - if (scanning && !changedByScanner) { - this.onStopScanning(); - } + onChangeTime = (rawRange: RawTimeRange) => { + const { updateTimeRange, exploreId } = this.props; + updateTimeRange({ exploreId, rawRange }); }; @@ -190,13 +180,7 @@ export class Explore extends React.PureComponent { onStartScanning = () => { // Scanner will trigger a query - const scanner = this.scanPreviousRange; - this.props.scanStart(this.props.exploreId, scanner); - }; - - scanPreviousRange = (): RawTimeRange => { - // Calling move() on the timepicker will trigger this.onChangeTime() - return this.timepickerRef.current.move(-1, true); + this.props.scanStart(this.props.exploreId); }; onStopScanning = () => { @@ -244,7 +228,7 @@ export class Explore extends React.PureComponent { return (
- + {datasourceLoading ?
Loading datasource...
: null} {datasourceMissing ? this.renderEmptyState() : null} diff --git a/public/app/features/explore/ExploreTimeControls.tsx b/public/app/features/explore/ExploreTimeControls.tsx new file mode 100644 index 0000000000000..7ae5bf10a7b1a --- /dev/null +++ b/public/app/features/explore/ExploreTimeControls.tsx @@ -0,0 +1,112 @@ +// Libaries +import React, { Component } from 'react'; + +// Types +import { ExploreId } from 'app/types'; +import { TimeRange, TimeOption, TimeZone, SetInterval, toUtc, dateTime } from '@grafana/ui'; + +// State + +// Components +import { TimePicker, RefreshPicker, RawTimeRange } from '@grafana/ui'; + +// Utils & Services +import { defaultSelectOptions } from '@grafana/ui/src/components/TimePicker/TimePicker'; +import { getShiftedTimeRange, getZoomedTimeRange } from 'app/core/utils/timePicker'; + +export interface Props { + exploreId: ExploreId; + hasLiveOption: boolean; + isLive: boolean; + loading: boolean; + range: TimeRange; + refreshInterval: string; + timeZone: TimeZone; + onRunQuery: () => void; + onChangeRefreshInterval: (interval: string) => void; + onChangeTime: (range: RawTimeRange) => void; +} + +export class ExploreTimeControls extends Component { + onMoveTimePicker = (direction: number) => { + const { range, onChangeTime, timeZone } = this.props; + const { from, to } = getShiftedTimeRange(direction, range); + const nextTimeRange = { + from: timeZone === 'utc' ? toUtc(from) : dateTime(from), + to: timeZone === 'utc' ? toUtc(to) : dateTime(to), + }; + + onChangeTime(nextTimeRange); + }; + + onMoveForward = () => this.onMoveTimePicker(1); + onMoveBack = () => this.onMoveTimePicker(-1); + + onChangeTimePicker = (timeRange: TimeRange) => { + this.props.onChangeTime(timeRange.raw); + }; + + onZoom = () => { + const { range, onChangeTime, timeZone } = this.props; + const { from, to } = getZoomedTimeRange(range, 2); + const nextTimeRange = { + from: timeZone === 'utc' ? toUtc(from) : dateTime(from), + to: timeZone === 'utc' ? toUtc(to) : dateTime(to), + }; + + onChangeTime(nextTimeRange); + }; + + setActiveTimeOption = (timeOptions: TimeOption[], rawTimeRange: RawTimeRange): TimeOption[] => { + return timeOptions.map(option => { + if (option.to === rawTimeRange.to && option.from === rawTimeRange.from) { + return { + ...option, + active: true, + }; + } + return { + ...option, + active: false, + }; + }); + }; + + render() { + const { + hasLiveOption, + isLive, + loading, + range, + refreshInterval, + timeZone, + onRunQuery, + onChangeRefreshInterval, + } = this.props; + + return ( + <> + {!isLive && ( + + )} + + + {refreshInterval && } + + ); + } +} diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index 75f8cc75b7c04..ed8f27d561f25 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -3,15 +3,7 @@ import { connect } from 'react-redux'; import { hot } from 'react-hot-loader'; import { ExploreId, ExploreMode } from 'app/types/explore'; -import { - DataSourceSelectItem, - RawTimeRange, - ClickOutsideWrapper, - TimeZone, - TimeRange, - SelectOptionItem, - LoadingState, -} from '@grafana/ui'; +import { DataSourceSelectItem, RawTimeRange, TimeZone, TimeRange, SelectOptionItem, LoadingState } from '@grafana/ui'; import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { StoreState } from 'app/types/store'; import { @@ -23,10 +15,9 @@ import { changeRefreshInterval, changeMode, } from './state/actions'; -import TimePicker from './TimePicker'; import { getTimeZone } from '../profile/state/selectors'; -import { RefreshPicker, SetInterval } from '@grafana/ui'; import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup'; +import { ExploreTimeControls } from './ExploreTimeControls'; enum IconSide { left = 'left', @@ -63,7 +54,6 @@ const createResponsiveButton = (options: { interface OwnProps { exploreId: ExploreId; - timepickerRef: React.RefObject; onChangeTime: (range: RawTimeRange, changedByScanner?: boolean) => void; } @@ -111,10 +101,6 @@ export class UnConnectedExploreToolbar extends PureComponent { return this.props.runQueries(this.props.exploreId); }; - onCloseTimePicker = () => { - this.props.timepickerRef.current.setState({ isOpen: false }); - }; - onChangeRefreshInterval = (item: string) => { const { changeRefreshInterval, exploreId } = this.props; changeRefreshInterval(exploreId, item); @@ -136,7 +122,6 @@ export class UnConnectedExploreToolbar extends PureComponent { timeZone, selectedDatasource, splitted, - timepickerRef, refreshInterval, onChangeTime, split, @@ -214,20 +199,18 @@ export class UnConnectedExploreToolbar extends PureComponent {
) : null}
- {!isLive && ( - - - - )} - - - {refreshInterval && }
diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 1d6d6460ff83c..1632bc684054a 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -101,7 +101,7 @@ export default class Logs extends PureComponent { } } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps: Props, prevState: State) { // Staged rendering if (prevState.deferLogs && !this.state.deferLogs && !this.state.renderAll) { this.renderAllTimer = setTimeout(() => this.setState({ renderAll: true }), 2000); diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 93fb547eb8715..a5ef33a6945ac 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -11,6 +11,7 @@ import { LogRowModel, LogsDedupStrategy, LoadingState, + TimeRange, } from '@grafana/ui'; import { ExploreId, ExploreItemState } from 'app/types/explore'; @@ -47,6 +48,7 @@ interface LogsContainerProps { isLive: boolean; stopLive: typeof changeRefreshIntervalAction; updateTimeRange: typeof updateTimeRange; + range: TimeRange; absoluteRange: AbsoluteTimeRange; } @@ -90,7 +92,9 @@ export class LogsContainer extends Component { return ( nextProps.loading !== this.props.loading || nextProps.dedupStrategy !== this.props.dedupStrategy || - nextProps.logsHighlighterExpressions !== this.props.logsHighlighterExpressions + nextProps.logsHighlighterExpressions !== this.props.logsHighlighterExpressions || + nextProps.scanning !== this.props.scanning || + nextProps.isLive !== this.props.isLive ); } @@ -107,7 +111,7 @@ export class LogsContainer extends Component { absoluteRange, timeZone, scanning, - scanRange, + range, width, hiddenLogLevels, isLive, @@ -139,7 +143,7 @@ export class LogsContainer extends Component { absoluteRange={absoluteRange} timeZone={timeZone} scanning={scanning} - scanRange={scanRange} + scanRange={range.raw} width={width} hiddenLogLevels={hiddenLogLevels} getRowContext={this.getLogRowContext} @@ -157,9 +161,9 @@ function mapStateToProps(state: StoreState, { exploreId }) { logsResult, loadingState, scanning, - scanRange, datasourceInstance, isLive, + range, absoluteRange, } = item; const loading = loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming; @@ -173,13 +177,13 @@ function mapStateToProps(state: StoreState, { exploreId }) { logsHighlighterExpressions, logsResult, scanning, - scanRange, timeZone, dedupStrategy, hiddenLogLevels, dedupedResult, datasourceInstance, isLive, + range, absoluteRange, }; } diff --git a/public/app/features/explore/TimePicker.test.tsx b/public/app/features/explore/TimePicker.test.tsx deleted file mode 100644 index ea793096374b8..0000000000000 --- a/public/app/features/explore/TimePicker.test.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import sinon from 'sinon'; - -import * as dateMath from '@grafana/ui/src/utils/datemath'; -import * as rangeUtil from '@grafana/ui/src/utils/rangeutil'; -import TimePicker from './TimePicker'; -import { RawTimeRange, TimeRange, TIME_FORMAT } from '@grafana/ui'; -import { toUtc, isDateTime, dateTime } from '@grafana/ui/src/utils/moment_wrapper'; - -const DEFAULT_RANGE = { - from: 'now-6h', - to: 'now', -}; - -const fromRaw = (rawRange: RawTimeRange): TimeRange => { - const raw = { - from: isDateTime(rawRange.from) ? dateTime(rawRange.from) : rawRange.from, - to: isDateTime(rawRange.to) ? dateTime(rawRange.to) : rawRange.to, - }; - - return { - from: dateMath.parse(raw.from, false), - to: dateMath.parse(raw.to, true), - raw: rawRange, - }; -}; - -describe('', () => { - it('render default values when closed and relative time range', () => { - const range = fromRaw(DEFAULT_RANGE); - const wrapper = shallow(); - expect(wrapper.state('fromRaw')).toBe(DEFAULT_RANGE.from); - expect(wrapper.state('toRaw')).toBe(DEFAULT_RANGE.to); - expect(wrapper.find('.timepicker-rangestring').text()).toBe('Last 6 hours'); - expect(wrapper.find('.gf-timepicker-dropdown').exists()).toBeFalsy(); - expect(wrapper.find('.gf-timepicker-utc').exists()).toBeFalsy(); - }); - - it('render default values when closed, utc and relative time range', () => { - const range = fromRaw(DEFAULT_RANGE); - const wrapper = shallow(); - expect(wrapper.state('fromRaw')).toBe(DEFAULT_RANGE.from); - expect(wrapper.state('toRaw')).toBe(DEFAULT_RANGE.to); - expect(wrapper.find('.timepicker-rangestring').text()).toBe('Last 6 hours'); - expect(wrapper.find('.gf-timepicker-dropdown').exists()).toBeFalsy(); - expect(wrapper.find('.gf-timepicker-utc').exists()).toBeTruthy(); - }); - - it('renders default values when open and relative range', () => { - const range = fromRaw(DEFAULT_RANGE); - const wrapper = shallow(); - expect(wrapper.state('fromRaw')).toBe(DEFAULT_RANGE.from); - expect(wrapper.state('toRaw')).toBe(DEFAULT_RANGE.to); - expect(wrapper.find('.timepicker-rangestring').text()).toBe('Last 6 hours'); - expect(wrapper.find('.gf-timepicker-dropdown').exists()).toBeTruthy(); - expect(wrapper.find('.gf-timepicker-utc').exists()).toBeFalsy(); - expect(wrapper.find('.timepicker-from').props().value).toBe(DEFAULT_RANGE.from); - expect(wrapper.find('.timepicker-to').props().value).toBe(DEFAULT_RANGE.to); - }); - - it('renders default values when open, utc and relative range', () => { - const range = fromRaw(DEFAULT_RANGE); - const wrapper = shallow(); - expect(wrapper.state('fromRaw')).toBe(DEFAULT_RANGE.from); - expect(wrapper.state('toRaw')).toBe(DEFAULT_RANGE.to); - expect(wrapper.find('.timepicker-rangestring').text()).toBe('Last 6 hours'); - expect(wrapper.find('.gf-timepicker-dropdown').exists()).toBeTruthy(); - expect(wrapper.find('.gf-timepicker-utc').exists()).toBeTruthy(); - expect(wrapper.find('.timepicker-from').props().value).toBe(DEFAULT_RANGE.from); - expect(wrapper.find('.timepicker-to').props().value).toBe(DEFAULT_RANGE.to); - }); - - it('apply with absolute range and non-utc', () => { - const range = { - from: toUtc(1), - to: toUtc(1000), - raw: { - from: toUtc(1), - to: toUtc(1000), - }, - }; - const localRange = { - from: dateTime(1), - to: dateTime(1000), - raw: { - from: dateTime(1), - to: dateTime(1000), - }, - }; - const expectedRangeString = rangeUtil.describeTimeRange(localRange); - - const onChangeTime = sinon.spy(); - const wrapper = shallow(); - expect(wrapper.state('fromRaw')).toBe(localRange.from.format(TIME_FORMAT)); - expect(wrapper.state('toRaw')).toBe(localRange.to.format(TIME_FORMAT)); - expect(wrapper.state('initialRange')).toBe(range.raw); - expect(wrapper.find('.timepicker-rangestring').text()).toBe(expectedRangeString); - expect(wrapper.find('.timepicker-from').props().value).toBe(localRange.from.format(TIME_FORMAT)); - expect(wrapper.find('.timepicker-to').props().value).toBe(localRange.to.format(TIME_FORMAT)); - - wrapper.find('button.gf-form-btn').simulate('click'); - expect(onChangeTime.calledOnce).toBeTruthy(); - expect(onChangeTime.getCall(0).args[0].from.valueOf()).toBe(0); - expect(onChangeTime.getCall(0).args[0].to.valueOf()).toBe(1000); - - expect(wrapper.state('isOpen')).toBeFalsy(); - expect(wrapper.state('rangeString')).toBe(expectedRangeString); - }); - - it('apply with absolute range and utc', () => { - const range = { - from: toUtc(1), - to: toUtc(1000), - raw: { - from: toUtc(1), - to: toUtc(1000), - }, - }; - const onChangeTime = sinon.spy(); - const wrapper = shallow(); - expect(wrapper.state('fromRaw')).toBe('1970-01-01 00:00:00'); - expect(wrapper.state('toRaw')).toBe('1970-01-01 00:00:01'); - expect(wrapper.state('initialRange')).toBe(range.raw); - expect(wrapper.find('.timepicker-rangestring').text()).toBe('1970-01-01 00:00:00 to 1970-01-01 00:00:01'); - expect(wrapper.find('.timepicker-from').props().value).toBe('1970-01-01 00:00:00'); - expect(wrapper.find('.timepicker-to').props().value).toBe('1970-01-01 00:00:01'); - - wrapper.find('button.gf-form-btn').simulate('click'); - expect(onChangeTime.calledOnce).toBeTruthy(); - expect(onChangeTime.getCall(0).args[0].from.valueOf()).toBe(0); - expect(onChangeTime.getCall(0).args[0].to.valueOf()).toBe(1000); - - expect(wrapper.state('isOpen')).toBeFalsy(); - expect(wrapper.state('rangeString')).toBe('1970-01-01 00:00:00 to 1970-01-01 00:00:01'); - }); - - it('moves ranges backward by half the range on left arrow click when utc', () => { - const rawRange = { - from: toUtc(2000), - to: toUtc(4000), - raw: { - from: toUtc(2000), - to: toUtc(4000), - }, - }; - const range = fromRaw(rawRange); - - const onChangeTime = sinon.spy(); - const wrapper = shallow(); - expect(wrapper.state('fromRaw')).toBe('1970-01-01 00:00:02'); - expect(wrapper.state('toRaw')).toBe('1970-01-01 00:00:04'); - - wrapper.find('.timepicker-left').simulate('click'); - expect(onChangeTime.calledOnce).toBeTruthy(); - expect(onChangeTime.getCall(0).args[0].from.valueOf()).toBe(1000); - expect(onChangeTime.getCall(0).args[0].to.valueOf()).toBe(3000); - }); - - it('moves ranges backward by half the range on left arrow click when not utc', () => { - const range = { - from: toUtc(2000), - to: toUtc(4000), - raw: { - from: toUtc(2000), - to: toUtc(4000), - }, - }; - const localRange = { - from: dateTime(2000), - to: dateTime(4000), - raw: { - from: dateTime(2000), - to: dateTime(4000), - }, - }; - - const onChangeTime = sinon.spy(); - const wrapper = shallow(); - expect(wrapper.state('fromRaw')).toBe(localRange.from.format(TIME_FORMAT)); - expect(wrapper.state('toRaw')).toBe(localRange.to.format(TIME_FORMAT)); - - wrapper.find('.timepicker-left').simulate('click'); - expect(onChangeTime.calledOnce).toBeTruthy(); - expect(onChangeTime.getCall(0).args[0].from.valueOf()).toBe(1000); - expect(onChangeTime.getCall(0).args[0].to.valueOf()).toBe(3000); - }); - - it('moves ranges forward by half the range on right arrow click when utc', () => { - const range = { - from: toUtc(1000), - to: toUtc(3000), - raw: { - from: toUtc(1000), - to: toUtc(3000), - }, - }; - - const onChangeTime = sinon.spy(); - const wrapper = shallow(); - expect(wrapper.state('fromRaw')).toBe('1970-01-01 00:00:01'); - expect(wrapper.state('toRaw')).toBe('1970-01-01 00:00:03'); - - wrapper.find('.timepicker-right').simulate('click'); - expect(onChangeTime.calledOnce).toBeTruthy(); - expect(onChangeTime.getCall(0).args[0].from.valueOf()).toBe(2000); - expect(onChangeTime.getCall(0).args[0].to.valueOf()).toBe(4000); - }); - - it('moves ranges forward by half the range on right arrow click when not utc', () => { - const range = { - from: toUtc(1000), - to: toUtc(3000), - raw: { - from: toUtc(1000), - to: toUtc(3000), - }, - }; - const localRange = { - from: dateTime(1000), - to: dateTime(3000), - raw: { - from: dateTime(1000), - to: dateTime(3000), - }, - }; - - const onChangeTime = sinon.spy(); - const wrapper = shallow(); - expect(wrapper.state('fromRaw')).toBe(localRange.from.format(TIME_FORMAT)); - expect(wrapper.state('toRaw')).toBe(localRange.to.format(TIME_FORMAT)); - - wrapper.find('.timepicker-right').simulate('click'); - expect(onChangeTime.calledOnce).toBeTruthy(); - expect(onChangeTime.getCall(0).args[0].from.valueOf()).toBe(2000); - expect(onChangeTime.getCall(0).args[0].to.valueOf()).toBe(4000); - }); -}); diff --git a/public/app/features/explore/TimePicker.tsx b/public/app/features/explore/TimePicker.tsx deleted file mode 100644 index 510646dcfe4fb..0000000000000 --- a/public/app/features/explore/TimePicker.tsx +++ /dev/null @@ -1,305 +0,0 @@ -import React, { PureComponent, ChangeEvent } from 'react'; -import * as rangeUtil from '@grafana/ui/src/utils/rangeutil'; -import { Input, RawTimeRange, TimeRange, TIME_FORMAT, TimeZone } from '@grafana/ui'; -import { toUtc, isDateTime, dateTime } from '@grafana/ui/src/utils/moment_wrapper'; - -interface TimePickerProps { - isOpen?: boolean; - range: TimeRange; - timeZone: TimeZone; - onChangeTime?: (range: RawTimeRange, scanning?: boolean) => void; -} - -interface TimePickerState { - isOpen: boolean; - isUtc: boolean; - rangeString: string; - refreshInterval?: string; - initialRange: RawTimeRange; - - // Input-controlled text, keep these in a shape that is human-editable - fromRaw: string; - toRaw: string; -} - -const getRaw = (range: any, timeZone: TimeZone) => { - const rawRange = { - from: range.raw.from, - to: range.raw.to, - }; - - if (isDateTime(rawRange.from)) { - if (timeZone === 'browser') { - rawRange.from = rawRange.from.local(); - } - rawRange.from = rawRange.from.format(TIME_FORMAT); - } - - if (isDateTime(rawRange.to)) { - if (timeZone === 'browser') { - rawRange.to = rawRange.to.local(); - } - rawRange.to = rawRange.to.format(TIME_FORMAT); - } - - return rawRange; -}; - -/** - * TimePicker with dropdown menu for relative dates. - * - * Initialize with a range that is either based on relative rawRange.strings, - * or on Moment objects. - * Internally the component needs to keep a string representation in `fromRaw` - * and `toRaw` for the controlled inputs. - * When a time is picked, `onChangeTime` is called with the new range that - * is again based on relative time strings or Moment objects. - */ -export default class TimePicker extends PureComponent { - dropdownEl: any; - - constructor(props) { - super(props); - - const { range, timeZone, isOpen } = props; - const rawRange = getRaw(range, timeZone); - - this.state = { - isOpen: isOpen, - isUtc: timeZone === 'utc', - rangeString: rangeUtil.describeTimeRange(range.raw), - fromRaw: rawRange.from, - toRaw: rawRange.to, - initialRange: range.raw, - refreshInterval: '', - }; - } - - static getDerivedStateFromProps(props: TimePickerProps, state: TimePickerState) { - if ( - state.initialRange && - state.initialRange.from === props.range.raw.from && - state.initialRange.to === props.range.raw.to - ) { - return state; - } - - const { range } = props; - const rawRange = getRaw(range, props.timeZone); - - return { - ...state, - fromRaw: rawRange.from, - toRaw: rawRange.to, - initialRange: range.raw, - rangeString: rangeUtil.describeTimeRange(range.raw), - }; - } - - move(direction: number, scanning?: boolean): RawTimeRange { - const { onChangeTime, range: origRange } = this.props; - const range = { - from: toUtc(origRange.from), - to: toUtc(origRange.to), - }; - - const timespan = (range.to.valueOf() - range.from.valueOf()) / 2; - let to, from; - - if (direction === -1) { - to = range.to.valueOf() - timespan; - from = range.from.valueOf() - timespan; - } else if (direction === 1) { - to = range.to.valueOf() + timespan; - from = range.from.valueOf() + timespan; - } else { - to = range.to.valueOf(); - from = range.from.valueOf(); - } - - const nextTimeRange = { - from: this.props.timeZone === 'utc' ? toUtc(from) : dateTime(from), - to: this.props.timeZone === 'utc' ? toUtc(to) : dateTime(to), - }; - - if (onChangeTime) { - onChangeTime(nextTimeRange); - } - return nextTimeRange; - } - - handleChangeFrom = (event: ChangeEvent) => { - this.setState({ - fromRaw: event.target.value, - }); - }; - - handleChangeTo = (event: ChangeEvent) => { - this.setState({ - toRaw: event.target.value, - }); - }; - - handleClickApply = () => { - const { onChangeTime, timeZone } = this.props; - let rawRange; - - this.setState( - state => { - const { toRaw, fromRaw } = this.state; - rawRange = { - from: fromRaw, - to: toRaw, - }; - - if (rawRange.from.indexOf('now') === -1) { - rawRange.from = timeZone === 'utc' ? toUtc(rawRange.from, TIME_FORMAT) : dateTime(rawRange.from, TIME_FORMAT); - } - - if (rawRange.to.indexOf('now') === -1) { - rawRange.to = timeZone === 'utc' ? toUtc(rawRange.to, TIME_FORMAT) : dateTime(rawRange.to, TIME_FORMAT); - } - - const rangeString = rangeUtil.describeTimeRange(rawRange); - return { - isOpen: false, - rangeString, - }; - }, - () => { - if (onChangeTime) { - onChangeTime(rawRange); - } - } - ); - }; - - handleClickLeft = () => this.move(-1); - handleClickPicker = () => { - this.setState(state => ({ - isOpen: !state.isOpen, - })); - }; - handleClickRight = () => this.move(1); - handleClickRefresh = () => {}; - handleClickRelativeOption = range => { - const { onChangeTime } = this.props; - const rangeString = rangeUtil.describeTimeRange(range); - const rawRange = { - from: range.from, - to: range.to, - }; - this.setState( - { - toRaw: rawRange.to, - fromRaw: rawRange.from, - isOpen: false, - rangeString, - }, - () => { - if (onChangeTime) { - onChangeTime(rawRange); - } - } - ); - }; - - getTimeOptions() { - return rangeUtil.getRelativeTimesList({}, this.state.rangeString); - } - - dropdownRef = el => { - this.dropdownEl = el; - }; - - renderDropdown() { - const { fromRaw, isOpen, toRaw } = this.state; - if (!isOpen) { - return null; - } - const timeOptions = this.getTimeOptions(); - return ( -
-
-
- Quick ranges -
-
- {Object.keys(timeOptions).map(section => { - const group = timeOptions[section]; - return ( - - ); - })} -
-
- -
-
- Custom range -
-
- -
-
- -
-
- - -
-
- -
-
-
- -
-
-
-
- ); - } - - render() { - const { isUtc, rangeString, refreshInterval } = this.state; - - return ( -
-
- - - -
- {this.renderDropdown()} -
- ); - } -} diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index 89fdc79dc52ef..cc230bc2d4f0e 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -16,15 +16,7 @@ import { LoadingState, AbsoluteTimeRange, } from '@grafana/ui/src/types'; -import { - ExploreId, - ExploreItemState, - HistoryItem, - RangeScanner, - ExploreUIState, - ExploreMode, - QueryOptions, -} from 'app/types/explore'; +import { ExploreId, ExploreItemState, HistoryItem, ExploreUIState, ExploreMode, QueryOptions } from 'app/types/explore'; import { actionCreatorFactory, noPayloadActionCreatorFactory, ActionOf } from 'app/core/redux/actionCreatorFactory'; import TableModel from 'app/core/table_model'; @@ -171,12 +163,6 @@ export interface RemoveQueryRowPayload { export interface ScanStartPayload { exploreId: ExploreId; - scanner: RangeScanner; -} - -export interface ScanRangePayload { - exploreId: ExploreId; - range: RawTimeRange; } export interface ScanStopPayload { @@ -397,7 +383,6 @@ export const runQueriesAction = actionCreatorFactory('explore * @param scanner Function that a) returns a new time range and b) triggers a query run for the new range */ export const scanStartAction = actionCreatorFactory('explore/SCAN_START').create(); -export const scanRangeAction = actionCreatorFactory('explore/SCAN_RANGE').create(); /** * Stop any scanning for more results. diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 718f0475bf904..8d961a37c3ed0 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -26,7 +26,7 @@ import { LogsDedupStrategy, AbsoluteTimeRange, } from '@grafana/ui'; -import { ExploreId, RangeScanner, ExploreUIState, QueryTransaction, ExploreMode } from 'app/types/explore'; +import { ExploreId, ExploreUIState, QueryTransaction, ExploreMode } from 'app/types/explore'; import { updateDatasourceInstanceAction, changeQueryAction, @@ -58,7 +58,6 @@ import { loadExploreDatasources, changeModeAction, scanStopAction, - scanRangeAction, runQueriesAction, stateSaveAction, updateTimeRangeAction, @@ -66,6 +65,7 @@ import { import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory'; import { getTimeZone } from 'app/features/profile/state/selectors'; import { offOption } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker'; +import { getShiftedTimeRange } from 'app/core/utils/timePicker'; /** * Updates UI state and save it to the URL @@ -413,14 +413,15 @@ export function runQueries(exploreId: ExploreId): ThunkResult { * @param exploreId Explore area * @param scanner Function that a) returns a new time range and b) triggers a query run for the new range */ -export function scanStart(exploreId: ExploreId, scanner: RangeScanner): ThunkResult { - return dispatch => { +export function scanStart(exploreId: ExploreId): ThunkResult { + return (dispatch, getState) => { // Register the scanner - dispatch(scanStartAction({ exploreId, scanner })); + dispatch(scanStartAction({ exploreId })); // Scanning must trigger query run, and return the new range - const range = scanner(); + const range = getShiftedTimeRange(-1, getState().explore[exploreId].range); // Set the new range to be displayed - dispatch(scanRangeAction({ exploreId, range })); + dispatch(updateTimeRangeAction({ exploreId, absoluteRange: range })); + dispatch(runQueriesAction({ exploreId })); }; } diff --git a/public/app/features/explore/state/epics/processQueryResultsEpic.test.ts b/public/app/features/explore/state/epics/processQueryResultsEpic.test.ts index c5da93081aa5d..9a427e97a22bc 100644 --- a/public/app/features/explore/state/epics/processQueryResultsEpic.test.ts +++ b/public/app/features/explore/state/epics/processQueryResultsEpic.test.ts @@ -1,11 +1,12 @@ import { mockExploreState } from 'test/mocks/mockExploreState'; -import { epicTester } from 'test/core/redux/epicTester'; +import { epicTester, MOCKED_ABSOLUTE_RANGE } from 'test/core/redux/epicTester'; import { processQueryResultsAction, resetQueryErrorAction, querySuccessAction, scanStopAction, - scanRangeAction, + updateTimeRangeAction, + runQueriesAction, } from '../actionTypes'; import { SeriesData, LoadingState } from '@grafana/ui'; import { processQueryResultsEpic } from './processQueryResultsEpic'; @@ -81,7 +82,7 @@ describe('processQueryResultsEpic', () => { describe('and we do not have a result', () => { it('then correct actions are dispatched', () => { - const { datasourceId, exploreId, state, scanner } = mockExploreState({ scanning: true }); + const { datasourceId, exploreId, state } = mockExploreState({ scanning: true }); const { latency, loadingState } = testContext(); const graphResult = []; const tableResult = new TableModel(); @@ -94,7 +95,8 @@ describe('processQueryResultsEpic', () => { .thenResultingActionsEqual( resetQueryErrorAction({ exploreId, refIds: [] }), querySuccessAction({ exploreId, loadingState, graphResult, tableResult, logsResult, latency }), - scanRangeAction({ exploreId, range: scanner() }) + updateTimeRangeAction({ exploreId, absoluteRange: MOCKED_ABSOLUTE_RANGE }), + runQueriesAction({ exploreId }) ); }); }); diff --git a/public/app/features/explore/state/epics/processQueryResultsEpic.ts b/public/app/features/explore/state/epics/processQueryResultsEpic.ts index 76e767c36a095..db46659eb6e37 100644 --- a/public/app/features/explore/state/epics/processQueryResultsEpic.ts +++ b/public/app/features/explore/state/epics/processQueryResultsEpic.ts @@ -11,17 +11,22 @@ import { processQueryResultsAction, ProcessQueryResultsPayload, querySuccessAction, - scanRangeAction, resetQueryErrorAction, scanStopAction, + updateTimeRangeAction, + runQueriesAction, } from '../actionTypes'; import { ResultProcessor } from '../../utils/ResultProcessor'; -export const processQueryResultsEpic: Epic, ActionOf, StoreState> = (action$, state$) => { +export const processQueryResultsEpic: Epic, ActionOf, StoreState> = ( + action$, + state$, + { getTimeZone, getShiftedTimeRange } +) => { return action$.ofType(processQueryResultsAction.type).pipe( mergeMap((action: ActionOf) => { const { exploreId, datasourceId, latency, loadingState, series, delta } = action.payload; - const { datasourceInstance, scanning, scanner, eventBridge } = state$.value.explore[exploreId]; + const { datasourceInstance, scanning, eventBridge } = state$.value.explore[exploreId]; // If datasource already changed, results do not matter if (datasourceInstance.meta.id !== datasourceId) { @@ -62,8 +67,9 @@ export const processQueryResultsEpic: Epic, ActionOf, StoreSt // Keep scanning for results if this was the last scanning transaction if (scanning) { if (_.size(result) === 0) { - const range = scanner(); - actions.push(scanRangeAction({ exploreId, range })); + const range = getShiftedTimeRange(-1, state$.value.explore[exploreId].range, getTimeZone(state$.value.user)); + actions.push(updateTimeRangeAction({ exploreId, absoluteRange: range })); + actions.push(runQueriesAction({ exploreId })); } else { // We can stop scanning if we have a result actions.push(scanStopAction({ exploreId })); diff --git a/public/app/features/explore/state/reducers.test.ts b/public/app/features/explore/state/reducers.test.ts index 40196df763949..9404e1591f469 100644 --- a/public/app/features/explore/state/reducers.test.ts +++ b/public/app/features/explore/state/reducers.test.ts @@ -5,14 +5,7 @@ import { makeInitialUpdateState, initialExploreState, } from './reducers'; -import { - ExploreId, - ExploreItemState, - ExploreUrlState, - ExploreState, - RangeScanner, - ExploreMode, -} from 'app/types/explore'; +import { ExploreId, ExploreItemState, ExploreUrlState, ExploreState, ExploreMode } from 'app/types/explore'; import { reducerTester } from 'test/core/redux/reducerTester'; import { scanStartAction, @@ -36,28 +29,23 @@ import { DataSourceApi, DataQuery, LogsModel, LogsDedupStrategy, LoadingState } describe('Explore item reducer', () => { describe('scanning', () => { it('should start scanning', () => { - const scanner = jest.fn(); const initalState = { ...makeExploreItemState(), scanning: false, - scanner: undefined as RangeScanner, }; reducerTester() .givenReducer(itemReducer as Reducer>, initalState) - .whenActionIsDispatched(scanStartAction({ exploreId: ExploreId.left, scanner })) + .whenActionIsDispatched(scanStartAction({ exploreId: ExploreId.left })) .thenStateShouldEqual({ ...makeExploreItemState(), scanning: true, - scanner, }); }); it('should stop scanning', () => { - const scanner = jest.fn(); const initalState = { ...makeExploreItemState(), scanning: true, - scanner, scanRange: {}, }; @@ -67,7 +55,6 @@ describe('Explore item reducer', () => { .thenStateShouldEqual({ ...makeExploreItemState(), scanning: false, - scanner: undefined, scanRange: undefined, }); }); diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index b19a394ba8a6b..dcbd19bc1cfee 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -24,7 +24,6 @@ import { queryFailureAction, setUrlReplacedAction, querySuccessAction, - scanRangeAction, scanStopAction, resetQueryErrorAction, queryStartAction, @@ -404,16 +403,10 @@ export const itemReducer = reducerFactory({} as ExploreItemSta }; }, }) - .addMapper({ - filter: scanRangeAction, - mapper: (state, action): ExploreItemState => { - return { ...state, scanRange: action.payload.range }; - }, - }) .addMapper({ filter: scanStartAction, mapper: (state, action): ExploreItemState => { - return { ...state, scanning: true, scanner: action.payload.scanner }; + return { ...state, scanning: true }; }, }) .addMapper({ @@ -423,7 +416,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta ...state, scanning: false, scanRange: undefined, - scanner: undefined, update: makeInitialUpdateState(), }; }, diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index 2c4ad840271d3..6b3c3dafcbc14 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -36,6 +36,7 @@ import { DateTime, toUtc, dateTime, + AbsoluteTimeRange, } from '@grafana/ui'; import { Observable } from 'rxjs'; import { getQueryResponse } from 'app/core/utils/explore'; @@ -46,6 +47,7 @@ import { TimeSrv, getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { UserState } from 'app/types/user'; import { getTimeRange } from 'app/core/utils/explore'; import { getTimeZone } from 'app/features/profile/state/selectors'; +import { getShiftedTimeRange } from 'app/core/utils/timePicker'; const rootReducers = { ...sharedReducers, @@ -87,6 +89,7 @@ export interface EpicDependencies { getTimeZone: (state: UserState) => TimeZone; toUtc: (input?: DateTimeInput, formatInput?: FormatInput) => DateTime; dateTime: (input?: DateTimeInput, formatInput?: FormatInput) => DateTime; + getShiftedTimeRange: (direction: number, origRange: TimeRange, timeZone: TimeZone) => AbsoluteTimeRange; } const dependencies: EpicDependencies = { @@ -96,6 +99,7 @@ const dependencies: EpicDependencies = { getTimeZone, toUtc, dateTime, + getShiftedTimeRange, }; const epicMiddleware = createEpicMiddleware({ dependencies }); diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index a9cc5d7185354..2ca3fa99b8d67 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -192,10 +192,6 @@ export interface ExploreItemState { range: TimeRange; absoluteRange: AbsoluteTimeRange; - /** - * Scanner function that calculates a new range, triggers a query run, and returns the new range. - */ - scanner?: RangeScanner; /** * True if scanning for more results is active. */ @@ -334,8 +330,6 @@ export interface QueryTransaction { scanning?: boolean; } -export type RangeScanner = () => RawTimeRange; - export interface TextMatch { text: string; start: number; diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index c06af5864c76c..be33165ba37e1 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -98,6 +98,10 @@ padding: 10px 2px; } +.explore-toolbar-content-item.timepicker { + z-index: $zindex-timepicker-popover; +} + .explore-toolbar-content-item:first-child { padding-left: $dashboard-padding; margin-right: auto; diff --git a/public/test/core/redux/epicTester.ts b/public/test/core/redux/epicTester.ts index f92e786c3a045..7b5f0a47b76ea 100644 --- a/public/test/core/redux/epicTester.ts +++ b/public/test/core/redux/epicTester.ts @@ -17,6 +17,8 @@ import { EpicDependencies } from 'app/store/configureStore'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { DEFAULT_RANGE } from 'app/core/utils/explore'; +export const MOCKED_ABSOLUTE_RANGE = { from: 1, to: 2 }; + export const epicTester = ( epic: Epic, ActionOf, StoreState, EpicDependencies>, state?: Partial, @@ -48,6 +50,8 @@ export const epicTester = ( const getTimeRange = jest.fn().mockReturnValue(DEFAULT_RANGE); + const getShiftedTimeRange = jest.fn().mockReturnValue(MOCKED_ABSOLUTE_RANGE); + const getTimeZone = jest.fn().mockReturnValue(DefaultTimeZone); const toUtc = jest.fn().mockReturnValue(null); @@ -61,6 +65,7 @@ export const epicTester = ( getTimeZone, toUtc, dateTime, + getShiftedTimeRange, }; const theDependencies: EpicDependencies = { ...defaultDependencies, ...dependencies }; From 742e0d56eb4d600e7d27235d40ab8f6a35a880ff Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Fri, 28 Jun 2019 14:11:12 +0200 Subject: [PATCH 03/17] Toolkit: moved front end cli scripts to separate package and introduced very early version of plugin tools * Move cli to grafana-toolkit * Moving packages, fixing ts * Add basics of plugin build task * Add toolkit build task * Circle - use node 10 for test-frontend * Prettier fix * First attempt for having shared tsconfig for plugins * Add enzyme as peer depencency * Do not expose internal commands when using toolkit from npm package * Introduce plugin linting * Fix missing file * Fix shim extenstion * Remove rollup typings * Add tslint as dependency * Toolkit - use the same versions of enzyme and tslint as core does * Remove include property from plugin tsconfig * Take failed suites into consideration when tests failed * Set ts-jest preset for jest * Cleanup tsconfig.plugins * Add plugin:test task * Rename file causing build failute * Fixing those missed renames * Add ts as peer dependency * Remove enzyme dependency and tweak test plugin task * Allow jest options overrides via package.json config * Improvements * Remove rollup node packages * TMP : Fix ts errors when linked * use local tslint if it exists * support coverage commands * Fix merge * fix build * Some minors * Make jest pass when no tests discovered --- .circleci/config.yml | 4 +- package.json | 27 +- packages/grafana-toolkit/CHANGELOG.md | 0 packages/grafana-toolkit/README.md | 52 ++ .../bin/grafana-toolkit.dist.js | 5 + .../grafana-toolkit/bin/grafana-toolkit.js | 13 + packages/grafana-toolkit/package.json | 71 ++ .../grafana-toolkit/src}/cli/index.d.ts | 0 packages/grafana-toolkit/src/cli/index.ts | 166 ++++ .../src}/cli/tasks/changelog.ts | 16 +- .../src}/cli/tasks/cherrypick.ts | 6 +- .../src}/cli/tasks/closeMilestone.ts | 7 +- .../src}/cli/tasks/core.start.ts | 5 +- .../src}/cli/tasks/grafanaui.build.ts | 46 +- .../src}/cli/tasks/grafanaui.release.ts | 51 +- .../src/cli/tasks/plugin.build.ts | 84 ++ .../src/cli/tasks/plugin.dev.ts | 9 + .../src/cli/tasks/plugin.tests.ts | 8 + .../src/cli/tasks/plugin/bundle.ts | 29 + .../src/cli/tasks/plugin/tests.ts | 22 + .../src}/cli/tasks/precommit.ts | 25 +- .../src}/cli/tasks/searchTestDataSetup.ts | 10 +- .../grafana-toolkit/src}/cli/tasks/task.ts | 9 +- .../grafana-toolkit/src/cli/tasks/template.ts | 9 + .../src/cli/tasks/toolkit.build.ts | 91 ++ .../grafana-toolkit/src/cli/tsconfig.json | 6 + .../grafana-toolkit/src}/cli/utils/cwd.ts | 5 + .../src}/cli/utils/execTask.ts | 0 .../src}/cli/utils/githubClient.test.ts | 3 + .../src}/cli/utils/githubClient.ts | 0 .../src}/cli/utils/useSpinner.ts | 2 +- .../src/config/jest.plugin.config.ts | 45 + .../src/config/rollup.plugin.config.ts | 160 ++++ .../src/config/tsconfig.plugin.json | 30 + .../src/config/tslint.plugin.json | 75 ++ packages/grafana-toolkit/tsconfig.json | 18 + packages/grafana-toolkit/tslint.json | 6 + scripts/cli/generateSassVariableFiles.ts | 2 +- scripts/cli/index.ts | 108 --- yarn.lock | 821 ++++++++++++++---- 40 files changed, 1663 insertions(+), 383 deletions(-) create mode 100644 packages/grafana-toolkit/CHANGELOG.md create mode 100644 packages/grafana-toolkit/README.md create mode 100755 packages/grafana-toolkit/bin/grafana-toolkit.dist.js create mode 100755 packages/grafana-toolkit/bin/grafana-toolkit.js create mode 100644 packages/grafana-toolkit/package.json rename {scripts => packages/grafana-toolkit/src}/cli/index.d.ts (100%) create mode 100644 packages/grafana-toolkit/src/cli/index.ts rename {scripts => packages/grafana-toolkit/src}/cli/tasks/changelog.ts (77%) rename {scripts => packages/grafana-toolkit/src}/cli/tasks/cherrypick.ts (88%) rename {scripts => packages/grafana-toolkit/src}/cli/tasks/closeMilestone.ts (94%) rename {scripts => packages/grafana-toolkit/src}/cli/tasks/core.start.ts (87%) rename {scripts => packages/grafana-toolkit/src}/cli/tasks/grafanaui.build.ts (68%) rename {scripts => packages/grafana-toolkit/src}/cli/tasks/grafanaui.release.ts (84%) create mode 100644 packages/grafana-toolkit/src/cli/tasks/plugin.build.ts create mode 100644 packages/grafana-toolkit/src/cli/tasks/plugin.dev.ts create mode 100644 packages/grafana-toolkit/src/cli/tasks/plugin.tests.ts create mode 100644 packages/grafana-toolkit/src/cli/tasks/plugin/bundle.ts create mode 100644 packages/grafana-toolkit/src/cli/tasks/plugin/tests.ts rename {scripts => packages/grafana-toolkit/src}/cli/tasks/precommit.ts (70%) rename {scripts => packages/grafana-toolkit/src}/cli/tasks/searchTestDataSetup.ts (93%) rename {scripts => packages/grafana-toolkit/src}/cli/tasks/task.ts (62%) create mode 100644 packages/grafana-toolkit/src/cli/tasks/template.ts create mode 100644 packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts create mode 100644 packages/grafana-toolkit/src/cli/tsconfig.json rename {scripts => packages/grafana-toolkit/src}/cli/utils/cwd.ts (70%) rename {scripts => packages/grafana-toolkit/src}/cli/utils/execTask.ts (100%) rename {scripts => packages/grafana-toolkit/src}/cli/utils/githubClient.test.ts (96%) rename {scripts => packages/grafana-toolkit/src}/cli/utils/githubClient.ts (100%) rename {scripts => packages/grafana-toolkit/src}/cli/utils/useSpinner.ts (91%) create mode 100644 packages/grafana-toolkit/src/config/jest.plugin.config.ts create mode 100644 packages/grafana-toolkit/src/config/rollup.plugin.config.ts create mode 100644 packages/grafana-toolkit/src/config/tsconfig.plugin.json create mode 100644 packages/grafana-toolkit/src/config/tslint.plugin.json create mode 100644 packages/grafana-toolkit/tsconfig.json create mode 100644 packages/grafana-toolkit/tslint.json delete mode 100644 scripts/cli/index.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 90f736fc211a5..a783c8641a9f5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -96,7 +96,7 @@ jobs: destination: expected-screenshots - store_artifacts: path: public/e2e-test/screenShots/theOutput - destination: output-screenshots + destination: output-screenshots codespell: docker: @@ -653,7 +653,7 @@ workflows: - mysql-integration-test - postgres-integration-test - build-oss-msi - filters: *filter-only-master + filters: *filter-only-master - grafana-docker-master: requires: - build-all diff --git a/package.json b/package.json index 7e21e57d440e2..ffe32b1400806 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,11 @@ "@emotion/core": "10.0.10", "@rtsao/plugin-proposal-class-properties": "7.0.1-patch.1", "@types/angular": "1.6.54", - "@types/chalk": "2.2.0", "@types/classnames": "2.2.7", "@types/clipboard": "2.0.1", - "@types/commander": "2.12.2", "@types/d3": "4.13.1", "@types/enzyme": "3.9.0", "@types/expect-puppeteer": "3.3.1", - "@types/inquirer": "0.0.43", "@types/jest": "24.0.13", "@types/jquery": "1.10.35", "@types/lodash": "4.14.123", @@ -49,16 +46,13 @@ "babel-jest": "24.8.0", "babel-loader": "8.0.5", "babel-plugin-angularjs-annotate": "0.10.0", - "chalk": "2.4.2", "clean-webpack-plugin": "2.0.0", - "concurrently": "4.1.0", "css-loader": "2.1.1", "enzyme": "3.9.0", "enzyme-adapter-react-16": "1.11.2", "enzyme-to-json": "3.3.5", "es6-promise": "3.3.1", "es6-shim": "0.35.5", - "execa": "1.0.0", "expect-puppeteer": "4.1.1", "expect.js": "0.2.0", "expose-loader": "0.7.5", @@ -83,7 +77,6 @@ "html-webpack-harddisk-plugin": "1.0.1", "html-webpack-plugin": "3.2.0", "husky": "1.3.1", - "inquirer": "6.2.2", "jest": "24.8.0", "jest-date-mock": "1.0.7", "lint-staged": "8.1.5", @@ -98,7 +91,6 @@ "node-sass": "4.11.0", "npm": "6.9.0", "optimize-css-assets-webpack-plugin": "5.0.1", - "ora": "3.2.0", "phantomjs-prebuilt": "2.1.16", "pixelmatch": "4.0.2", "pngjs": "3.4.0", @@ -115,8 +107,6 @@ "rimraf": "2.6.3", "sass-lint": "1.12.1", "sass-loader": "7.1.0", - "semver": "5.7.0", - "simple-git": "^1.112.0", "sinon": "1.17.6", "style-loader": "0.23.1", "systemjs": "0.20.19", @@ -140,9 +130,9 @@ }, "scripts": { "dev": "webpack --progress --colors --mode development --config scripts/webpack/webpack.dev.js", - "start": "npm run cli -- core:start --watchTheme", - "start:hot": "npm run cli -- core:start --hot --watchTheme", - "start:ignoreTheme": "npm run cli -- core:start --hot", + "start": "grafana-toolkit core:start --watchTheme", + "start:hot": "grafana-toolkit core:start --hot --watchTheme", + "start:ignoreTheme": "grafana-toolkit core:start --hot", "watch": "yarn start -d watch,start core:start --watchTheme ", "build": "grunt build", "test": "grunt test", @@ -153,16 +143,15 @@ "api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js", "storybook": "cd packages/grafana-ui && yarn storybook", "storybook:build": "cd packages/grafana-ui && yarn storybook:build", - "themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts", "prettier:check": "prettier --list-different \"**/*.{ts,tsx,scss}\"", "prettier:write": "prettier --list-different \"**/*.{ts,tsx,scss}\" --write", - "cli": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts", "gui:tslint": "tslint -c ./packages/grafana-ui/tslint.json --project ./packages/grafana-ui/tsconfig.json", - "gui:build": "npm run cli -- gui:build", - "gui:releasePrepare": "npm run cli -- gui:release", + "gui:build": "grafana-toolkit gui:build", + "gui:releasePrepare": "grafana-toolkit gui:release", "gui:publish": "cd packages/grafana-ui/dist && npm publish --access public", - "gui:release": "npm run cli -- gui:release -p --createVersionCommit", - "precommit": "npm run cli -- precommit" + "gui:release": "grafana-toolkit gui:release -p --createVersionCommit", + "precommit": "grafana-toolkit precommit", + "themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts" }, "husky": { "hooks": { diff --git a/packages/grafana-toolkit/CHANGELOG.md b/packages/grafana-toolkit/CHANGELOG.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/grafana-toolkit/README.md b/packages/grafana-toolkit/README.md new file mode 100644 index 0000000000000..5655afcb2ad47 --- /dev/null +++ b/packages/grafana-toolkit/README.md @@ -0,0 +1,52 @@ +# Grafana Toolkit + +Make sure to run `yarn install` before trying anything! Otherwise you may see unknown command grafana-toolkit and spend a while tracking that down. + +## Internal development +For development use `yarn link`. First, navigate to `packages/grafana-toolkit` and run `yarn link`. Then, in your project use `yarn link @grafana/toolkit` to use linked version. + +## Grafana extensions development with grafana-toolkit overview + +### Typescript +To configure Typescript create `tsconfig.json` file in the root dir of your app. grafana-toolkit comes with default tsconfig located in `packages/grafana-toolkit/src/config/tsconfig.plugin.ts`. In order for Typescript to be able to pickup your source files you need to extend that config as follows: + +```json +{ + "extends": "./node_modules/@grafana/toolkit/src/config/tsconfig.plugin.json", + "include": ["src"], + "compilerOptions": { + "rootDir": "./src", + "typeRoots": ["./node_modules/@types"] + } +} +``` + +### TSLint +grafana-toolkit comes with default config for TSLint, that's located in `packages/grafana-toolkit/src/config/tslint.plugin.ts`. As for now there is now way to customise TSLint config. + +### Tests +grafana-toolkit comes with Jest as a test runner. It runs tests according to common config locted in `packages/grafana-toolkit/src/config/jest.plugin.config.ts`. + +For now the config is not extendable, but our goal is to enable custom jest config via jest.config or package.json file. This might be required in the future if you want to use i.e. `enzyme-to-json` snapshots serializer. For that particular serializer we can also utilise it's API and add initialisation in the setup files (https://github.com/adriantoine/enzyme-to-json#serializer-in-unit-tests). We need to test that approach first. + +#### Jest setup +We are not opinionated about tool used for implmenting tests. Internally at Grafana we use Enzyme. If you want to configure Enzyme as a testing utility, you need to configure enzyme-adapter-react. To do so, you need to create `[YOUR_APP]/config/jest-setup.ts` file that will provide React/Enzyme setup. Simply copy following code into that file to get Enzyme working with React: + +```ts +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +configure({ adapter: new Adapter() }); +``` + +grafana-toolkit will use that file as Jest's setup file. You can also setup Jest with shims of your needs by creating `jest-shim.ts` file in the same directory: `[YOUR_APP]/config/jest-shim.ts` + +Adidtionaly, you can also provide additional Jest config via package.json file. For more details please refer to [Jest docs](https://jest-bot.github.io/jest/docs/configuration.html#verbose-boolean). Currently we support following properties: +- [`snapshotSerializers`](https://jest-bot.github.io/jest/docs/configuration.html#snapshotserializers-array-string) + +## Prettier [todo] + +## Development mode [todo] +TODO +- Enable rollup watch on extension sources + diff --git a/packages/grafana-toolkit/bin/grafana-toolkit.dist.js b/packages/grafana-toolkit/bin/grafana-toolkit.dist.js new file mode 100755 index 0000000000000..d6498f3d3a49d --- /dev/null +++ b/packages/grafana-toolkit/bin/grafana-toolkit.dist.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +// This bin is used for cli installed from npm + +require('../src/cli/index.js').run(); diff --git a/packages/grafana-toolkit/bin/grafana-toolkit.js b/packages/grafana-toolkit/bin/grafana-toolkit.js new file mode 100755 index 0000000000000..6cab0391954df --- /dev/null +++ b/packages/grafana-toolkit/bin/grafana-toolkit.js @@ -0,0 +1,13 @@ +#!/usr/bin/env node + +var path = require('path') ; + +// This bin is used for cli executed internally + +var tsProjectPath = path.resolve(__dirname, '../tsconfig.json'); + +require('ts-node').register({ + project: tsProjectPath +}); + +require('../src/cli/index.ts').run(true); diff --git a/packages/grafana-toolkit/package.json b/packages/grafana-toolkit/package.json new file mode 100644 index 0000000000000..e602f60dcf1ae --- /dev/null +++ b/packages/grafana-toolkit/package.json @@ -0,0 +1,71 @@ +{ + "name": "@grafana/toolkit", + "version": "6.3.0-alpha.2", + "description": "Grafana Toolkit", + "keywords": [ + "typescript", + "react", + "react-component" + ], + "bin": { + "grafana-toolkit": "./bin/grafana-toolkit.js" + }, + "scripts": { + "tslint": "tslint -c tslint.json --project tsconfig.json", + "typecheck": "tsc --noEmit", + "precommit": "npm run tslint & npm run typecheck", + "clean": "rimraf ./dist ./compiled" + }, + "author": "Grafana Labs", + "license": "Apache-2.0", + "dependencies": { + "@types/execa": "^0.9.0", + "@types/inquirer": "^6.0.3", + "@types/jest": "24.0.13", + "@types/jest-cli": "^23.6.0", + "@types/node": "^12.0.4", + "@types/prettier": "^1.16.4", + "@types/semver": "^6.0.0", + "axios": "0.19.0", + "chalk": "^2.4.2", + "commander": "^2.20.0", + "concurrently": "4.1.0", + "execa": "^1.0.0", + "glob": "^7.1.4", + "inquirer": "^6.3.1", + "jest-cli": "^24.8.0", + "lodash": "4.17.11", + "ora": "^3.4.0", + "prettier": "^1.17.1", + "replace-in-file": "^4.1.0", + "rollup": "^1.14.2", + "rollup-plugin-commonjs": "^10.0.0", + "rollup-plugin-copy-glob": "^0.3.0", + "rollup-plugin-json": "^4.0.0", + "rollup-plugin-node-builtins": "^2.1.2", + "rollup-plugin-node-globals": "^1.4.0", + "rollup-plugin-node-resolve": "^5.1.0", + "rollup-plugin-sourcemaps": "^0.4.2", + "rollup-plugin-terser": "^5.0.0", + "rollup-plugin-typescript2": "^0.21.1", + "rollup-plugin-visualizer": "^1.1.1", + "semver": "^6.1.1", + "simple-git": "^1.112.0", + "ts-node": "^8.2.0", + "tslint": "5.14.0" + }, + "peerDependencies": { + "jest": "24.8.0", + "ts-jest": "24.0.2", + "tslib": "1.10.0", + "typescript": "3.5.1" + }, + "resolutions": { + "@types/lodash": "4.14.119", + "rollup-plugin-typescript2": "0.21.1" + }, + "devDependencies": { + "@types/glob": "^7.1.1", + "rollup-watch": "^4.3.1" + } +} diff --git a/scripts/cli/index.d.ts b/packages/grafana-toolkit/src/cli/index.d.ts similarity index 100% rename from scripts/cli/index.d.ts rename to packages/grafana-toolkit/src/cli/index.d.ts diff --git a/packages/grafana-toolkit/src/cli/index.ts b/packages/grafana-toolkit/src/cli/index.ts new file mode 100644 index 0000000000000..c457983ef7aa4 --- /dev/null +++ b/packages/grafana-toolkit/src/cli/index.ts @@ -0,0 +1,166 @@ +// @ts-ignore +import program from 'commander'; +import { execTask } from './utils/execTask'; +import chalk from 'chalk'; +import { startTask } from './tasks/core.start'; +import { buildTask } from './tasks/grafanaui.build'; +import { releaseTask } from './tasks/grafanaui.release'; +import { changelogTask } from './tasks/changelog'; +import { cherryPickTask } from './tasks/cherrypick'; +import { precommitTask } from './tasks/precommit'; +import { templateTask } from './tasks/template'; +import { pluginBuildTask } from './tasks/plugin.build'; +import { toolkitBuildTask } from './tasks/toolkit.build'; +import { pluginTestTask } from './tasks/plugin.tests'; +import { searchTestDataSetupTask } from './tasks/searchTestDataSetup'; +import { closeMilestoneTask } from './tasks/closeMilestone'; +import { pluginDevTask } from './tasks/plugin.dev'; + +export const run = (includeInternalScripts = false) => { + if (includeInternalScripts) { + program.option('-d, --depreciate ', 'Inform about npm script deprecation', v => v.split(',')); + program + .command('core:start') + .option('-h, --hot', 'Run front-end with HRM enabled') + .option('-t, --watchTheme', 'Watch for theme changes and regenerate variables.scss files') + .description('Starts Grafana front-end in development mode with watch enabled') + .action(async cmd => { + await execTask(startTask)({ + watchThemes: cmd.watchTheme, + hot: cmd.hot, + }); + }); + + program + .command('gui:build') + .description('Builds @grafana/ui package to packages/grafana-ui/dist') + .action(async cmd => { + // @ts-ignore + await execTask(buildTask)(); + }); + + program + .command('gui:release') + .description('Prepares @grafana/ui release (and publishes to npm on demand)') + .option('-p, --publish', 'Publish @grafana/ui to npm registry') + .option('-u, --usePackageJsonVersion', 'Use version specified in package.json') + .option('--createVersionCommit', 'Create and push version commit') + .action(async cmd => { + await execTask(releaseTask)({ + publishToNpm: !!cmd.publish, + usePackageJsonVersion: !!cmd.usePackageJsonVersion, + createVersionCommit: !!cmd.createVersionCommit, + }); + }); + + program + .command('changelog') + .option('-m, --milestone ', 'Specify milestone') + .description('Builds changelog markdown') + .action(async cmd => { + if (!cmd.milestone) { + console.log('Please specify milestone, example: -m '); + return; + } + + await execTask(changelogTask)({ + milestone: cmd.milestone, + }); + }); + + program + .command('cherrypick') + .description('Helps find commits to cherry pick') + .action(async cmd => { + await execTask(cherryPickTask)({}); + }); + + program + .command('precommit') + .description('Executes checks') + .action(async cmd => { + await execTask(precommitTask)({}); + }); + + program + .command('debug:template') + .description('Just testing') + .action(async cmd => { + await execTask(templateTask)({}); + }); + + program + .command('toolkit:build') + .description('Prepares grafana/toolkit dist package') + .action(async cmd => { + // @ts-ignore + await execTask(toolkitBuildTask)(); + }); + + program + .command('searchTestData') + .option('-c, --count ', 'Specify number of dashboards') + .description('Setup test data for search') + .action(async cmd => { + await execTask(searchTestDataSetupTask)({ count: cmd.count }); + }); + + program + .command('close-milestone') + .option('-m, --milestone ', 'Specify milestone') + .description('Helps ends a milestone by removing the cherry-pick label and closing it') + .action(async cmd => { + if (!cmd.milestone) { + console.log('Please specify milestone, example: -m '); + return; + } + + await execTask(closeMilestoneTask)({ + milestone: cmd.milestone, + }); + }); + } + + program + .command('plugin:build') + .description('Prepares plugin dist package') + .action(async cmd => { + await execTask(pluginBuildTask)({}); + }); + + program + .command('plugin:dev') + .description('Starts plugin dev mode') + .action(async cmd => { + await execTask(pluginDevTask)({ + watch: true, + }); + }); + + program + .command('plugin:test') + .option('-u, --updateSnapshot', 'Run snapshots update') + .option('--coverage', 'Run code coverage') + .description('Executes plugin tests') + .action(async cmd => { + await execTask(pluginTestTask)({ + updateSnapshot: !!cmd.updateSnapshot, + coverage: !!cmd.coverage, + }); + }); + + program.on('command:*', () => { + console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' ')); + process.exit(1); + }); + + program.parse(process.argv); + + if (program.depreciate && program.depreciate.length === 2) { + console.log( + chalk.yellow.bold( + `[NPM script depreciation] ${program.depreciate[0]} is deprecated! Use ${program.depreciate[1]} instead!` + ) + ); + } +}; diff --git a/scripts/cli/tasks/changelog.ts b/packages/grafana-toolkit/src/cli/tasks/changelog.ts similarity index 77% rename from scripts/cli/tasks/changelog.ts rename to packages/grafana-toolkit/src/cli/tasks/changelog.ts index e2cb9da7e55d1..004ea46603a10 100644 --- a/scripts/cli/tasks/changelog.ts +++ b/packages/grafana-toolkit/src/cli/tasks/changelog.ts @@ -1,4 +1,6 @@ -import _ from 'lodash'; +import axios from 'axios'; +// @ts-ignore +import * as _ from 'lodash'; import { Task, TaskRunner } from './task'; import GithubClient from '../utils/githubClient'; @@ -27,11 +29,11 @@ const changelogTaskRunner: TaskRunner = async ({ milestone }) const issues = res.data; const bugs = _.sortBy( - issues.filter(item => { + issues.filter((item: any) => { if (item.title.match(/fix|fixes/i)) { return true; } - if (item.labels.find(label => label.name === 'type/bug')) { + if (item.labels.find((label: any) => label.name === 'type/bug')) { return true; } return false; @@ -39,7 +41,7 @@ const changelogTaskRunner: TaskRunner = async ({ milestone }) 'title' ); - const notBugs = _.sortBy(issues.filter(item => !bugs.find(bug => bug === item)), 'title'); + const notBugs = _.sortBy(issues.filter((item: any) => !bugs.find((bug: any) => bug === item)), 'title'); let markdown = ''; @@ -65,7 +67,7 @@ const changelogTaskRunner: TaskRunner = async ({ milestone }) function getMarkdownLineForIssue(item: any) { const githubGrafanaUrl = 'https://github.com/grafana/grafana'; let markdown = ''; - const title = item.title.replace(/^([^:]*)/, (match, g1) => { + const title = item.title.replace(/^([^:]*)/, (_match: any, g1: any) => { return `**${g1}**`; }); @@ -78,6 +80,4 @@ function getMarkdownLineForIssue(item: any) { return markdown; } -export const changelogTask = new Task(); -changelogTask.setName('Changelog generator task'); -changelogTask.setRunner(changelogTaskRunner); +export const changelogTask = new Task('Changelog generator task', changelogTaskRunner); diff --git a/scripts/cli/tasks/cherrypick.ts b/packages/grafana-toolkit/src/cli/tasks/cherrypick.ts similarity index 88% rename from scripts/cli/tasks/cherrypick.ts rename to packages/grafana-toolkit/src/cli/tasks/cherrypick.ts index 3e5a7addf4259..38ce9b4a5891e 100644 --- a/scripts/cli/tasks/cherrypick.ts +++ b/packages/grafana-toolkit/src/cli/tasks/cherrypick.ts @@ -15,7 +15,7 @@ const cherryPickRunner: TaskRunner = async () => { }); // sort by closed date ASC - res.data.sort(function(a, b) { + res.data.sort((a: any, b: any) => { return new Date(a.closed_at).getTime() - new Date(b.closed_at).getTime(); }); @@ -42,6 +42,4 @@ const cherryPickRunner: TaskRunner = async () => { console.log(commands); }; -export const cherryPickTask = new Task(); -cherryPickTask.setName('Cherry pick task'); -cherryPickTask.setRunner(cherryPickRunner); +export const cherryPickTask = new Task('Cherry pick task', cherryPickRunner); diff --git a/scripts/cli/tasks/closeMilestone.ts b/packages/grafana-toolkit/src/cli/tasks/closeMilestone.ts similarity index 94% rename from scripts/cli/tasks/closeMilestone.ts rename to packages/grafana-toolkit/src/cli/tasks/closeMilestone.ts index 9873863dc253c..3bdbbd46a21dd 100644 --- a/scripts/cli/tasks/closeMilestone.ts +++ b/packages/grafana-toolkit/src/cli/tasks/closeMilestone.ts @@ -70,6 +70,7 @@ const closeMilestoneTaskRunner: TaskRunner = async ({ mil } }; -export const closeMilestoneTask = new Task(); -closeMilestoneTask.setName('Close Milestone generator task'); -closeMilestoneTask.setRunner(closeMilestoneTaskRunner); +export const closeMilestoneTask = new Task( + 'Close Milestone generator task', + closeMilestoneTaskRunner +); diff --git a/scripts/cli/tasks/core.start.ts b/packages/grafana-toolkit/src/cli/tasks/core.start.ts similarity index 87% rename from scripts/cli/tasks/core.start.ts rename to packages/grafana-toolkit/src/cli/tasks/core.start.ts index dbd2dfe711917..2132ea33a8e46 100644 --- a/scripts/cli/tasks/core.start.ts +++ b/packages/grafana-toolkit/src/cli/tasks/core.start.ts @@ -1,3 +1,4 @@ +//@ts-ignore import concurrently from 'concurrently'; import { Task, TaskRunner } from './task'; @@ -33,6 +34,4 @@ const startTaskRunner: TaskRunner = async ({ watchThemes, hot } }; -export const startTask = new Task(); -startTask.setName('Core startTask'); -startTask.setRunner(startTaskRunner); +export const startTask = new Task('Core startTask', startTaskRunner); diff --git a/scripts/cli/tasks/grafanaui.build.ts b/packages/grafana-toolkit/src/cli/tasks/grafanaui.build.ts similarity index 68% rename from scripts/cli/tasks/grafanaui.build.ts rename to packages/grafana-toolkit/src/cli/tasks/grafanaui.build.ts index 1a48bb1a7243b..6dbbc7b1d3609 100644 --- a/scripts/cli/tasks/grafanaui.build.ts +++ b/packages/grafana-toolkit/src/cli/tasks/grafanaui.build.ts @@ -1,34 +1,45 @@ -import execa from 'execa'; -import fs from 'fs'; +import execa = require('execa'); +// @ts-ignore +import * as fs from 'fs'; import { changeCwdToGrafanaUi, restoreCwd } from '../utils/cwd'; import chalk from 'chalk'; import { useSpinner } from '../utils/useSpinner'; import { Task, TaskRunner } from './task'; -let distDir, cwd; +let distDir: string, cwd: string; +// @ts-ignore export const clean = useSpinner('Cleaning', async () => await execa('npm', ['run', 'clean'])); +// @ts-ignore const compile = useSpinner('Compiling sources', () => execa('tsc', ['-p', './tsconfig.build.json'])); +// @ts-ignore const rollup = useSpinner('Bundling', () => execa('npm', ['run', 'build'])); -export const savePackage = useSpinner<{ +interface SavePackageOptions { path: string; pkg: {}; -}>('Updating package.json', async ({ path, pkg }) => { - return new Promise((resolve, reject) => { - fs.writeFile(path, JSON.stringify(pkg, null, 2), err => { - if (err) { - reject(err); - return; - } - resolve(); +} + +// @ts-ignore +export const savePackage = useSpinner( + 'Updating package.json', + // @ts-ignore + async ({ path, pkg }: SavePackageOptions) => { + return new Promise((resolve, reject) => { + fs.writeFile(path, JSON.stringify(pkg, null, 2), err => { + if (err) { + reject(err); + return; + } + resolve(); + }); }); - }); -}); + } +); -const preparePackage = async pkg => { +const preparePackage = async (pkg: any) => { pkg.main = 'index.js'; pkg.types = 'index.d.ts'; await savePackage({ @@ -39,6 +50,7 @@ const preparePackage = async pkg => { const moveFiles = () => { const files = ['README.md', 'CHANGELOG.md', 'index.js']; + // @ts-ignore return useSpinner(`Moving ${files.join(', ')} files`, async () => { const promises = files.map(file => { return new Promise((resolve, reject) => { @@ -71,6 +83,4 @@ const buildTaskRunner: TaskRunner = async () => { restoreCwd(); }; -export const buildTask = new Task(); -buildTask.setName('@grafana/ui build'); -buildTask.setRunner(buildTaskRunner); +export const buildTask = new Task('@grafana/ui build', buildTaskRunner); diff --git a/scripts/cli/tasks/grafanaui.release.ts b/packages/grafana-toolkit/src/cli/tasks/grafanaui.release.ts similarity index 84% rename from scripts/cli/tasks/grafanaui.release.ts rename to packages/grafana-toolkit/src/cli/tasks/grafanaui.release.ts index dd7c441a53bfc..eb8da0da1c28c 100644 --- a/scripts/cli/tasks/grafanaui.release.ts +++ b/packages/grafana-toolkit/src/cli/tasks/grafanaui.release.ts @@ -1,14 +1,15 @@ -import execa from 'execa'; +import execa = require('execa'); import { execTask } from '../utils/execTask'; import { changeCwdToGrafanaUiDist, changeCwdToGrafanaUi, restoreCwd } from '../utils/cwd'; -import semver from 'semver'; -import inquirer from 'inquirer'; +import { ReleaseType, inc } from 'semver'; +import { prompt } from 'inquirer'; import chalk from 'chalk'; import { useSpinner } from '../utils/useSpinner'; import { savePackage, buildTask, clean } from './grafanaui.build'; import { TaskRunner, Task } from './task'; type VersionBumpType = 'prerelease' | 'patch' | 'minor' | 'major'; + interface ReleaseTaskOptions { publishToNpm: boolean; usePackageJsonVersion: boolean; @@ -16,43 +17,29 @@ interface ReleaseTaskOptions { } const promptBumpType = async () => { - return inquirer.prompt<{ type: VersionBumpType }>([ + return prompt<{ type: VersionBumpType }>([ { type: 'list', message: 'Select version bump', name: 'type', choices: ['prerelease', 'patch', 'minor', 'major'], - validate: answer => { - if (answer.length < 1) { - return 'You must choose something'; - } - - return true; - }, }, ]); }; const promptPrereleaseId = async (message = 'Is this a prerelease?', allowNo = true) => { - return inquirer.prompt<{ id: string }>([ + return prompt<{ id: string }>([ { type: 'list', message: message, name: 'id', choices: allowNo ? ['no', 'alpha', 'beta'] : ['alpha', 'beta'], - validate: answer => { - if (answer.length < 1) { - return 'You must choose something'; - } - - return true; - }, }, ]); }; const promptConfirm = async (message?: string) => { - return inquirer.prompt<{ confirmed: boolean }>([ + return prompt<{ confirmed: boolean }>([ { type: 'confirm', message: message || 'Is that correct?', @@ -64,11 +51,18 @@ const promptConfirm = async (message?: string) => { // Since Grafana core depends on @grafana/ui highly, we run full check before release const runChecksAndTests = async () => + // @ts-ignore useSpinner(`Running checks and tests`, async () => { - await execa('npm', ['run', 'test']); + try { + await execa('npm', ['run', 'test']); + } catch (e) { + console.log(e); + throw e; + } })(); const bumpVersion = (version: string) => + // @ts-ignore useSpinner(`Saving version ${version} to package.json`, async () => { changeCwdToGrafanaUi(); await execa('npm', ['version', version]); @@ -79,6 +73,7 @@ const bumpVersion = (version: string) => })(); const publishPackage = (name: string, version: string) => + // @ts-ignore useSpinner(`Publishing ${name} @ ${version} to npm registry...`, async () => { changeCwdToGrafanaUiDist(); await execa('npm', ['publish', '--access', 'public']); @@ -95,6 +90,7 @@ const ensureMasterBranch = async () => { }; const prepareVersionCommitAndPush = async (version: string) => + // @ts-ignore useSpinner('Commiting and pushing @grafana/ui version update', async () => { await execa.stdout('git', ['commit', '-a', '-m', `Upgrade @grafana/ui version to v${version}`]); await execa.stdout('git', ['push']); @@ -106,6 +102,7 @@ const releaseTaskRunner: TaskRunner = async ({ createVersionCommit, }) => { changeCwdToGrafanaUi(); + // @ts-ignore await clean(); // Clean previous build if exists restoreCwd(); @@ -117,7 +114,7 @@ const releaseTaskRunner: TaskRunner = async ({ await runChecksAndTests(); - await execTask(buildTask)(); + await execTask(buildTask)({} as any); let releaseConfirmed = false; let nextVersion; @@ -133,13 +130,13 @@ const releaseTaskRunner: TaskRunner = async ({ console.log(type); if (type === 'prerelease') { const { id } = await promptPrereleaseId('What kind of prerelease?', false); - nextVersion = semver.inc(pkg.version, type, id); + nextVersion = inc(pkg.version, type, id as any); } else { const { id } = await promptPrereleaseId(); if (id !== 'no') { - nextVersion = semver.inc(pkg.version, `pre${type}`, id); + nextVersion = inc(pkg.version, `pre${type}` as ReleaseType, id as any); } else { - nextVersion = semver.inc(pkg.version, type); + nextVersion = inc(pkg.version, type as ReleaseType); } } } else { @@ -190,6 +187,4 @@ const releaseTaskRunner: TaskRunner = async ({ } }; -export const releaseTask = new Task(); -releaseTask.setName('@grafana/ui release'); -releaseTask.setRunner(releaseTaskRunner); +export const releaseTask = new Task('@grafana/ui release', releaseTaskRunner); diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.build.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.build.ts new file mode 100644 index 0000000000000..eeccb7d10f997 --- /dev/null +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.build.ts @@ -0,0 +1,84 @@ +import { Task, TaskRunner } from './task'; +// @ts-ignore +import execa = require('execa'); +import path = require('path'); +import fs = require('fs'); +import glob = require('glob'); +import * as rollup from 'rollup'; +import { inputOptions, outputOptions } from '../../config/rollup.plugin.config'; + +import { useSpinner } from '../utils/useSpinner'; +import { Linter, Configuration, RuleFailure } from 'tslint'; +import { testPlugin } from './plugin/tests'; +interface PrecommitOptions {} + +// @ts-ignore +export const clean = useSpinner('Cleaning', async () => await execa('rimraf', ['./dist'])); + +// @ts-ignore +const typecheckPlugin = useSpinner('Typechecking', async () => { + await execa('tsc', ['--noEmit']); +}); + +// @ts-ignore +const lintPlugin = useSpinner('Linting', async () => { + let tsLintConfigPath = path.resolve(process.cwd(), 'tslint.json'); + if (!fs.existsSync(tsLintConfigPath)) { + tsLintConfigPath = path.resolve(__dirname, '../../config/tslint.plugin.json'); + } + const globPattern = path.resolve(process.cwd(), 'src/**/*.+(ts|tsx)'); + const sourcesToLint = glob.sync(globPattern); + const options = { + fix: true, // or fail + formatter: 'json', + }; + + const configuration = Configuration.findConfiguration(tsLintConfigPath).results; + + const lintResults = sourcesToLint + .map(fileName => { + const linter = new Linter(options); + const fileContents = fs.readFileSync(fileName, 'utf8'); + linter.lint(fileName, fileContents, configuration); + return linter.getResult(); + }) + .filter(result => { + return result.errorCount > 0 || result.warningCount > 0; + }); + + if (lintResults.length > 0) { + console.log('\n'); + const failures = lintResults.reduce((failures, result) => { + return [...failures, ...result.failures]; + }, []); + failures.forEach(f => { + // tslint:disable-next-line + console.log( + `${f.getRuleSeverity() === 'warning' ? 'WARNING' : 'ERROR'}: ${f.getFileName().split('src')[1]}[${ + f.getStartPosition().getLineAndCharacter().line + }:${f.getStartPosition().getLineAndCharacter().character}]: ${f.getFailure()}` + ); + }); + console.log('\n'); + throw new Error(`${failures.length} linting errors found in ${lintResults.length} files`); + } +}); + +const bundlePlugin = useSpinner('Bundling plugin', async () => { + // @ts-ignore + const bundle = await rollup.rollup(inputOptions()); + // TODO: we can work on more verbose output + await bundle.generate(outputOptions); + await bundle.write(outputOptions); +}); + +const pluginBuildRunner: TaskRunner = async () => { + await clean(); + // @ts-ignore + await lintPlugin(); + await testPlugin({ updateSnapshot: false, coverage: false }); + // @ts-ignore + await bundlePlugin(); +}; + +export const pluginBuildTask = new Task('Build plugin', pluginBuildRunner); diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.dev.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.dev.ts new file mode 100644 index 0000000000000..1793d7ddbc073 --- /dev/null +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.dev.ts @@ -0,0 +1,9 @@ +import { Task, TaskRunner } from './task'; +import { bundlePlugin, PluginBundleOptions } from './plugin/bundle'; + +const pluginDevRunner: TaskRunner = async options => { + const result = await bundlePlugin(options); + return result; +}; + +export const pluginDevTask = new Task('Dev plugin', pluginDevRunner); diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.tests.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.tests.ts new file mode 100644 index 0000000000000..7840512892268 --- /dev/null +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.tests.ts @@ -0,0 +1,8 @@ +import { Task, TaskRunner } from './task'; +import { testPlugin, PluginTestOptions } from './plugin/tests'; + +const pluginTestRunner: TaskRunner = async options => { + await testPlugin(options); +}; + +export const pluginTestTask = new Task('Test plugin', pluginTestRunner); diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.ts b/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.ts new file mode 100644 index 0000000000000..4c905764b4710 --- /dev/null +++ b/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.ts @@ -0,0 +1,29 @@ +import path = require('path'); +import * as jestCLI from 'jest-cli'; +import * as rollup from 'rollup'; +import { inputOptions, outputOptions } from '../../../config/rollup.plugin.config'; + +export interface PluginBundleOptions { + watch: boolean; +} + +export const bundlePlugin = async ({ watch }: PluginBundleOptions) => { + if (watch) { + const watcher = rollup.watch([ + { + ...inputOptions(), + output: outputOptions, + watch: { + chokidar: true, + clearScreen: true, + }, + }, + ]); + } else { + // @ts-ignore + const bundle = await rollup.rollup(inputOptions()); + // TODO: we can work on more verbose output + await bundle.generate(outputOptions); + await bundle.write(outputOptions); + } +}; diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin/tests.ts b/packages/grafana-toolkit/src/cli/tasks/plugin/tests.ts new file mode 100644 index 0000000000000..eba45d6c24afd --- /dev/null +++ b/packages/grafana-toolkit/src/cli/tasks/plugin/tests.ts @@ -0,0 +1,22 @@ +import path = require('path'); +import * as jestCLI from 'jest-cli'; +import { useSpinner } from '../../utils/useSpinner'; +import { jestConfig } from '../../../config/jest.plugin.config'; + +export interface PluginTestOptions { + updateSnapshot: boolean; + coverage: boolean; +} + +export const testPlugin = useSpinner('Running tests', async ({ updateSnapshot, coverage }) => { + const testConfig = jestConfig(); + + testConfig.updateSnapshot = updateSnapshot; + testConfig.coverage = coverage; + + const results = await jestCLI.runCLI(testConfig as any, [process.cwd()]); + + if (results.results.numFailedTests > 0 || results.results.numFailedTestSuites > 0) { + throw new Error('Tests failed'); + } +}); diff --git a/scripts/cli/tasks/precommit.ts b/packages/grafana-toolkit/src/cli/tasks/precommit.ts similarity index 70% rename from scripts/cli/tasks/precommit.ts rename to packages/grafana-toolkit/src/cli/tasks/precommit.ts index b759aa414f20c..6431b486451f8 100644 --- a/scripts/cli/tasks/precommit.ts +++ b/packages/grafana-toolkit/src/cli/tasks/precommit.ts @@ -1,6 +1,8 @@ import { Task, TaskRunner } from './task'; import chalk from 'chalk'; +// @ts-ignore import get from 'lodash/get'; +// @ts-ignore import flatten from 'lodash/flatten'; import execa = require('execa'); const simpleGit = require('simple-git/promise')(process.cwd()); @@ -28,13 +30,18 @@ const tasks = { const precommitRunner: TaskRunner = async () => { const status = await simpleGit.status(); const sassFiles = status.files.filter( - file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.scss)$/g) || file.path.indexOf('.sass-lint.yml') > -1 + (file: any) => + (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.scss)$/g) || file.path.indexOf('.sass-lint.yml') > -1 ); - const tsFiles = status.files.filter(file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.(ts|tsx))$/g)); - const testFiles = status.files.filter(file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.test.(ts|tsx))$/g)); - const goTestFiles = status.files.filter(file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\_test.go)$/g)); - const grafanaUiFiles = tsFiles.filter(file => (file.path as string).indexOf('grafana-ui') > -1); + const tsFiles = status.files.filter((file: any) => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.(ts|tsx))$/g)); + const testFiles = status.files.filter((file: any) => + (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.test.(ts|tsx))$/g) + ); + const goTestFiles = status.files.filter((file: any) => + (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\_test.go)$/g) + ); + const grafanaUiFiles = tsFiles.filter((file: any) => (file.path as string).indexOf('grafana-ui') > -1); const grafanaUIFilesChangedOnly = tsFiles.length > 0 && tsFiles.length - grafanaUiFiles.length === 0; const coreFilesChangedOnly = tsFiles.length > 0 && grafanaUiFiles.length === 0; @@ -69,13 +76,13 @@ const precommitRunner: TaskRunner = async () => { const task = execa('grunt', gruntTasks); // @ts-ignore const stream = task.stdout; - stream.pipe(process.stdout); + if (stream) { + stream.pipe(process.stdout); + } return task; } console.log(chalk.yellow('Skipping precommit checks, not front-end changes detected')); return; }; -export const precommitTask = new Task(); -precommitTask.setName('Precommit task'); -precommitTask.setRunner(precommitRunner); +export const precommitTask = new Task('Precommit task', precommitRunner); diff --git a/scripts/cli/tasks/searchTestDataSetup.ts b/packages/grafana-toolkit/src/cli/tasks/searchTestDataSetup.ts similarity index 93% rename from scripts/cli/tasks/searchTestDataSetup.ts rename to packages/grafana-toolkit/src/cli/tasks/searchTestDataSetup.ts index 2bac9b6c903c1..915f431a9578c 100644 --- a/scripts/cli/tasks/searchTestDataSetup.ts +++ b/packages/grafana-toolkit/src/cli/tasks/searchTestDataSetup.ts @@ -1,5 +1,4 @@ import axios from 'axios'; -import _ from 'lodash'; import { Task, TaskRunner } from './task'; interface SearchTestDataSetupOptions { @@ -14,7 +13,7 @@ const client = axios.create({ }, }); -export async function getUser(user): Promise { +export async function getUser(user: any): Promise { console.log('Creating user ' + user.name); const search = await client.get('/users/search', { params: { query: user.login }, @@ -112,6 +111,7 @@ const searchTestDataSetupRunnner: TaskRunner = async } }; -export const searchTestDataSetupTask = new Task(); -searchTestDataSetupTask.setName('Search test data setup'); -searchTestDataSetupTask.setRunner(searchTestDataSetupRunnner); +export const searchTestDataSetupTask = new Task( + 'Search test data setup', + searchTestDataSetupRunnner +); diff --git a/scripts/cli/tasks/task.ts b/packages/grafana-toolkit/src/cli/tasks/task.ts similarity index 62% rename from scripts/cli/tasks/task.ts rename to packages/grafana-toolkit/src/cli/tasks/task.ts index cc7d77a0664e9..4524508bd83c3 100644 --- a/scripts/cli/tasks/task.ts +++ b/packages/grafana-toolkit/src/cli/tasks/task.ts @@ -1,11 +1,10 @@ export type TaskRunner = (options: T) => Promise; export class Task { - name: string; - runner: (options: TOptions) => Promise; - options: TOptions; + options: TOptions = {} as any; - setName = name => { + constructor(public name: string, public runner: TaskRunner) {} + setName = (name: string) => { this.name = name; }; @@ -13,7 +12,7 @@ export class Task { this.runner = runner; }; - setOptions = options => { + setOptions = (options: TOptions) => { this.options = options; }; diff --git a/packages/grafana-toolkit/src/cli/tasks/template.ts b/packages/grafana-toolkit/src/cli/tasks/template.ts new file mode 100644 index 0000000000000..4a965e05f9721 --- /dev/null +++ b/packages/grafana-toolkit/src/cli/tasks/template.ts @@ -0,0 +1,9 @@ +import { Task, TaskRunner } from './task'; + +interface TemplateOptions {} + +const templateRunner: TaskRunner = async () => { + console.log('Template task'); +}; + +export const templateTask = new Task('Template task', templateRunner); diff --git a/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts b/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts new file mode 100644 index 0000000000000..975b1d6fa7920 --- /dev/null +++ b/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts @@ -0,0 +1,91 @@ +import execa = require('execa'); +import * as fs from 'fs'; +import { changeCwdToGrafanaUi, restoreCwd, changeCwdToGrafanaToolkit } from '../utils/cwd'; +import chalk from 'chalk'; +import { useSpinner } from '../utils/useSpinner'; +import { Task, TaskRunner } from './task'; + +let distDir: string, cwd: string; + +// @ts-ignore +export const clean = useSpinner('Cleaning', async () => await execa('npm', ['run', 'clean'])); + +// @ts-ignore +const compile = useSpinner('Compiling sources', async () => { + try { + await execa('tsc', ['-p', './tsconfig.json']); + } catch (e) { + console.log(e); + throw e; + } +}); + +// @ts-ignore +export const savePackage = useSpinner<{ + path: string; + pkg: {}; + // @ts-ignore +}>('Updating package.json', async ({ path, pkg }) => { + return new Promise((resolve, reject) => { + fs.writeFile(path, JSON.stringify(pkg, null, 2), err => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); +}); + +const preparePackage = async (pkg: any) => { + pkg.bin = { + 'grafana-toolkit': './bin/grafana-toolkit.dist.js', + }; + + await savePackage({ + path: `${cwd}/dist/package.json`, + pkg, + }); +}; + +const moveFiles = () => { + const files = [ + 'README.md', + 'CHANGELOG.md', + 'bin/grafana-toolkit.dist.js', + 'src/config/tsconfig.plugin.json', + 'src/config/tslint.plugin.json', + ]; + // @ts-ignore + return useSpinner(`Moving ${files.join(', ')} files`, async () => { + const promises = files.map(file => { + return new Promise((resolve, reject) => { + fs.copyFile(`${cwd}/${file}`, `${distDir}/${file}`, err => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + }); + + await Promise.all(promises); + })(); +}; + +const toolkitBuildTaskRunner: TaskRunner = async () => { + cwd = changeCwdToGrafanaToolkit(); + distDir = `${cwd}/dist`; + const pkg = require(`${cwd}/package.json`); + console.log(chalk.yellow(`Building ${pkg.name} (package.json version: ${pkg.version})`)); + + await clean(); + await compile(); + await preparePackage(pkg); + fs.mkdirSync('./dist/bin'); + await moveFiles(); + restoreCwd(); +}; + +export const toolkitBuildTask = new Task('@grafana/toolkit build', toolkitBuildTaskRunner); diff --git a/packages/grafana-toolkit/src/cli/tsconfig.json b/packages/grafana-toolkit/src/cli/tsconfig.json new file mode 100644 index 0000000000000..475f3aa406f83 --- /dev/null +++ b/packages/grafana-toolkit/src/cli/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "module": "commonjs" + } +} diff --git a/scripts/cli/utils/cwd.ts b/packages/grafana-toolkit/src/cli/utils/cwd.ts similarity index 70% rename from scripts/cli/utils/cwd.ts rename to packages/grafana-toolkit/src/cli/utils/cwd.ts index 9b4241b136903..48ca39e5da2ba 100644 --- a/scripts/cli/utils/cwd.ts +++ b/packages/grafana-toolkit/src/cli/utils/cwd.ts @@ -5,6 +5,11 @@ export const changeCwdToGrafanaUi = () => { return process.cwd(); }; +export const changeCwdToGrafanaToolkit = () => { + process.chdir(`${cwd}/packages/grafana-toolkit`); + return process.cwd(); +}; + export const changeCwdToGrafanaUiDist = () => { process.chdir(`${cwd}/packages/grafana-ui/dist`); }; diff --git a/scripts/cli/utils/execTask.ts b/packages/grafana-toolkit/src/cli/utils/execTask.ts similarity index 100% rename from scripts/cli/utils/execTask.ts rename to packages/grafana-toolkit/src/cli/utils/execTask.ts diff --git a/scripts/cli/utils/githubClient.test.ts b/packages/grafana-toolkit/src/cli/utils/githubClient.test.ts similarity index 96% rename from scripts/cli/utils/githubClient.test.ts rename to packages/grafana-toolkit/src/cli/utils/githubClient.test.ts index 95c67cb3111c4..dbdadda0d92c6 100644 --- a/scripts/cli/utils/githubClient.test.ts +++ b/packages/grafana-toolkit/src/cli/utils/githubClient.test.ts @@ -20,6 +20,7 @@ describe('GithubClient', () => { describe('#client', () => { it('it should contain a client', () => { + // @ts-ignore const spy = jest.spyOn(GithubClient.prototype, 'createClient').mockImplementation(() => fakeClient); const github = new GithubClient(); @@ -40,6 +41,7 @@ describe('GithubClient', () => { process.env.GITHUB_USERNAME = username; process.env.GITHUB_ACCESS_TOKEN = token; + // @ts-ignore const spy = jest.spyOn(GithubClient.prototype, 'createClient').mockImplementation(() => fakeClient); const github = new GithubClient(true); @@ -57,6 +59,7 @@ describe('GithubClient', () => { describe('when the credentials are not defined', () => { it('should throw an error', () => { expect(() => { + // tslint:disable-next-line new GithubClient(true); }).toThrow(/operation needs a GITHUB_USERNAME and GITHUB_ACCESS_TOKEN environment variables/); }); diff --git a/scripts/cli/utils/githubClient.ts b/packages/grafana-toolkit/src/cli/utils/githubClient.ts similarity index 100% rename from scripts/cli/utils/githubClient.ts rename to packages/grafana-toolkit/src/cli/utils/githubClient.ts diff --git a/scripts/cli/utils/useSpinner.ts b/packages/grafana-toolkit/src/cli/utils/useSpinner.ts similarity index 91% rename from scripts/cli/utils/useSpinner.ts rename to packages/grafana-toolkit/src/cli/utils/useSpinner.ts index 298a6516689d6..b901ed96d9c12 100644 --- a/scripts/cli/utils/useSpinner.ts +++ b/packages/grafana-toolkit/src/cli/utils/useSpinner.ts @@ -10,7 +10,7 @@ export const useSpinner = (spinnerLabel: string, fn: FnToSpin, killProcess await fn(options); spinner.succeed(); } catch (e) { - spinner.fail(e); + spinner.fail(e.message || e); if (killProcess) { process.exit(1); } diff --git a/packages/grafana-toolkit/src/config/jest.plugin.config.ts b/packages/grafana-toolkit/src/config/jest.plugin.config.ts new file mode 100644 index 0000000000000..685791bd984ec --- /dev/null +++ b/packages/grafana-toolkit/src/config/jest.plugin.config.ts @@ -0,0 +1,45 @@ +import path = require('path'); +import fs = require('fs'); + +const whitelistedJestConfigOverrides = ['snapshotSerializers']; + +export const jestConfig = () => { + const jestConfigOverrides = require(path.resolve(process.cwd(), 'package.json')).jest; + const blacklistedOverrides = jestConfigOverrides + ? Object.keys(jestConfigOverrides).filter(override => whitelistedJestConfigOverrides.indexOf(override) === -1) + : []; + if (blacklistedOverrides.length > 0) { + console.error("\ngrafana-toolkit doesn't support following Jest options: ", blacklistedOverrides); + console.log('Supported Jest options are: ', JSON.stringify(whitelistedJestConfigOverrides)); + throw new Error('Provided Jest config is not supported'); + } + + const shimsFilePath = path.resolve(process.cwd(), 'config/jest-shim.ts'); + const setupFilePath = path.resolve(process.cwd(), 'config/jest-setup.ts'); + + const setupFile = fs.existsSync(setupFilePath) ? setupFilePath : undefined; + const shimsFile = fs.existsSync(shimsFilePath) ? shimsFilePath : undefined; + const setupFiles = [setupFile, shimsFile].filter(f => f); + const defaultJestConfig = { + preset: 'ts-jest', + verbose: false, + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + }, + moduleDirectories: ['node_modules', 'src'], + rootDir: process.cwd(), + roots: ['/src'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], + setupFiles, + globals: { 'ts-jest': { isolatedModules: true } }, + coverageReporters: ['json-summary', 'text', 'lcov'], + collectCoverageFrom: ['src/**/*.{ts,tsx}', '!**/node_modules/**', '!**/vendor/**'], + updateSnapshot: false, + passWithNoTests: true, + }; + + return { + ...defaultJestConfig, + ...jestConfigOverrides, + }; +}; diff --git a/packages/grafana-toolkit/src/config/rollup.plugin.config.ts b/packages/grafana-toolkit/src/config/rollup.plugin.config.ts new file mode 100644 index 0000000000000..2636ed6b685e1 --- /dev/null +++ b/packages/grafana-toolkit/src/config/rollup.plugin.config.ts @@ -0,0 +1,160 @@ +// @ts-ignore +import resolve from 'rollup-plugin-node-resolve'; +// @ts-ignore +import commonjs from 'rollup-plugin-commonjs'; +// @ts-ignore +import sourceMaps from 'rollup-plugin-sourcemaps'; +// @ts-ignore +import typescript from 'rollup-plugin-typescript2'; +// @ts-ignore +import json from 'rollup-plugin-json'; +// @ts-ignore +import copy from 'rollup-plugin-copy-glob'; +// @ts-ignore +import { terser } from 'rollup-plugin-terser'; +// @ts-ignore +import visualizer from 'rollup-plugin-visualizer'; + +// @ts-ignore +const replace = require('replace-in-file'); +const pkg = require(`${process.cwd()}/package.json`); +const path = require('path'); +const fs = require('fs'); +const tsConfig = require(`${__dirname}/tsconfig.plugin.json`); +import { OutputOptions, InputOptions, GetManualChunk } from 'rollup'; +const { PRODUCTION } = process.env; + +export const outputOptions: OutputOptions = { + dir: 'dist', + format: 'amd', + sourcemap: true, + chunkFileNames: '[name].js', +}; + +const findModuleTs = (base: string, files?: string[], result?: string[]) => { + files = files || fs.readdirSync(base); + result = result || []; + + if (files) { + files.forEach(file => { + const newbase = path.join(base, file); + if (fs.statSync(newbase).isDirectory()) { + result = findModuleTs(newbase, fs.readdirSync(newbase), result); + } else { + if (file.indexOf('module.ts') > -1) { + // @ts-ignore + result.push(newbase); + } + } + }); + } + return result; +}; + +const getModuleFiles = () => { + return findModuleTs(path.resolve(process.cwd(), 'src')); +}; + +const getManualChunk: GetManualChunk = (id: string) => { + // id == absolute path + if (id.endsWith('module.ts')) { + const idx = id.indexOf('/src/'); + if (idx > 0) { + const p = id.substring(idx + 5, id.lastIndexOf('.')); + console.log('MODULE:', id, p); + return p; + } + } + console.log('shared:', id); + return 'shared'; +}; + +const getExternals = () => { + // Those are by default exported by Grafana + const defaultExternals = [ + 'jquery', + 'lodash', + 'moment', + 'rxjs', + 'd3', + 'react', + 'react-dom', + '@grafana/ui', + '@grafana/runtime', + '@grafana/data', + ]; + const toolkitConfig = require(path.resolve(process.cwd(), 'package.json')).grafanaToolkit; + const userDefinedExternals = (toolkitConfig && toolkitConfig.externals) || []; + return [...defaultExternals, ...userDefinedExternals]; +}; + +export const inputOptions = (): InputOptions => { + const inputFiles = getModuleFiles(); + return { + input: inputFiles, + manualChunks: inputFiles.length > 1 ? getManualChunk : undefined, + external: getExternals(), + plugins: [ + // Allow json resolution + json(), + // globals(), + // builtins(), + + // Compile TypeScript files + typescript({ + typescript: require('typescript'), + objectHashIgnoreUnknownHack: true, + tsconfigDefaults: tsConfig, + }), + + // Allow node_modules resolution, so you can use 'external' to control + // which external modules to include in the bundle + // https://github.com/rollup/rollup-plugin-node-resolve#usage + resolve(), + + // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs) + commonjs(), + + // Resolve source maps to the original source + sourceMaps(), + + // Minify + PRODUCTION && terser(), + + // Copy files + copy([{ files: 'src/**/*.{json,svg,png,html}', dest: 'dist' }], { verbose: true }), + + // Help avoid including things accidentally + visualizer({ + filename: 'dist/stats.html', + title: 'Plugin Stats', + }), + + // Custom callback when we are done + finish(), + ], + }; +}; + +function finish() { + return { + name: 'finish', + buildEnd() { + const files = 'dist/plugin.json'; + replace.sync({ + files: files, + from: /%VERSION%/g, + to: pkg.version, + }); + replace.sync({ + files: files, + from: /%TODAY%/g, + to: new Date().toISOString().substring(0, 10), + }); + + if (PRODUCTION) { + console.log('*minified*'); + } + }, + }; +} diff --git a/packages/grafana-toolkit/src/config/tsconfig.plugin.json b/packages/grafana-toolkit/src/config/tsconfig.plugin.json new file mode 100644 index 0000000000000..0e565014b3f8f --- /dev/null +++ b/packages/grafana-toolkit/src/config/tsconfig.plugin.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "target": "es5", + "lib": ["es6", "dom"], + "module": "esnext", + "strict": true, + "alwaysStrict": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitUseStrict": false, + "noUnusedLocals": true, + "strictNullChecks": true, + "skipLibCheck": true, + "removeComments": false, + "jsx": "react", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "importHelpers": true, + "noEmitHelpers": true, + "inlineSourceMap": false, + "sourceMap": true, + "emitDecoratorMetadata": false, + "experimentalDecorators": true, + "downlevelIteration": true, + "pretty": true + } +} diff --git a/packages/grafana-toolkit/src/config/tslint.plugin.json b/packages/grafana-toolkit/src/config/tslint.plugin.json new file mode 100644 index 0000000000000..3f0a5b6dc52c0 --- /dev/null +++ b/packages/grafana-toolkit/src/config/tslint.plugin.json @@ -0,0 +1,75 @@ +{ + "rules": { + "array-type": [true, "array-simple"], + "arrow-return-shorthand": true, + "ban": [true, { "name": "Array", "message": "tsstyle#array-constructor" }], + "ban-types": [ + true, + ["Object", "Use {} instead."], + ["String", "Use 'string' instead."], + ["Number", "Use 'number' instead."], + ["Boolean", "Use 'boolean' instead."] + ], + "interface-name": [true, "never-prefix"], + "no-string-throw": true, + "no-unused-expression": true, + "no-unused-variable": false, + "no-use-before-declare": false, + "no-duplicate-variable": true, + "curly": true, + "class-name": true, + "semicolon": [true, "always", "ignore-bound-class-methods"], + "triple-equals": [true, "allow-null-check"], + "comment-format": [false, "check-space"], + "eofline": true, + "forin": false, + "indent": [true, "spaces", 2], + "jsdoc-format": true, + "label-position": true, + "max-line-length": [true, 150], + "member-access": [true, "no-public"], + "new-parens": true, + "no-angle-bracket-type-assertion": true, + "no-arg": true, + "no-bitwise": false, + "no-conditional-assignment": true, + "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], + "no-construct": true, + "no-debugger": true, + "no-empty": false, + "no-eval": true, + "no-inferrable-types": true, + "no-namespace": [true, "allow-declarations"], + "no-reference": true, + "no-shadowed-variable": false, + "no-string-literal": false, + "no-switch-case-fall-through": false, + "no-trailing-whitespace": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [true, "check-open-brace", "check-catch", "check-else"], + "only-arrow-functions": [true, "allow-declarations", "allow-named-functions"], + "prefer-const": true, + "radix": true, + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "variable-name": [ + true, + "check-format", + "ban-keywords", + "allow-leading-underscore", + "allow-trailing-underscore", + "allow-pascal-case" + ], + "use-isnan": true, + "whitespace": [true, "check-branch", "check-decl", "check-type", "check-preblock"] + } +} diff --git a/packages/grafana-toolkit/tsconfig.json b/packages/grafana-toolkit/tsconfig.json new file mode 100644 index 0000000000000..28b734aa9cd48 --- /dev/null +++ b/packages/grafana-toolkit/tsconfig.json @@ -0,0 +1,18 @@ +{ + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules"], + "compilerOptions": { + "module": "commonjs", + "rootDirs": ["."], + "outDir": "dist/src", + "strict": true, + "alwaysStrict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "typeRoots": ["./node_modules/@types"], + "skipLibCheck": true, // Temp workaround for Duplicate identifier tsc errors, + "removeComments": false, + "esModuleInterop": true, + "lib": ["es2015", "es2017.string"] + } +} diff --git a/packages/grafana-toolkit/tslint.json b/packages/grafana-toolkit/tslint.json new file mode 100644 index 0000000000000..f512937362444 --- /dev/null +++ b/packages/grafana-toolkit/tslint.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tslint.json", + "rules": { + "import-blacklist": [true, ["^@grafana/runtime.*"]] + } +} diff --git a/scripts/cli/generateSassVariableFiles.ts b/scripts/cli/generateSassVariableFiles.ts index 43a5ff193c1d8..89133005d6700 100644 --- a/scripts/cli/generateSassVariableFiles.ts +++ b/scripts/cli/generateSassVariableFiles.ts @@ -1,4 +1,4 @@ -import fs from 'fs'; +import * as fs from 'fs'; import darkTheme from '@grafana/ui/src/themes/dark'; import lightTheme from '@grafana/ui/src/themes/light'; import defaultTheme from '@grafana/ui/src/themes/default'; diff --git a/scripts/cli/index.ts b/scripts/cli/index.ts deleted file mode 100644 index dec2a05c424f2..0000000000000 --- a/scripts/cli/index.ts +++ /dev/null @@ -1,108 +0,0 @@ -import program from 'commander'; -import { execTask } from './utils/execTask'; -import chalk from 'chalk'; -import { startTask } from './tasks/core.start'; -import { buildTask } from './tasks/grafanaui.build'; -import { releaseTask } from './tasks/grafanaui.release'; -import { changelogTask } from './tasks/changelog'; -import { cherryPickTask } from './tasks/cherrypick'; -import { closeMilestoneTask } from './tasks/closeMilestone'; -import { precommitTask } from './tasks/precommit'; -import { searchTestDataSetupTask } from './tasks/searchTestDataSetup'; - -program.option('-d, --depreciate ', 'Inform about npm script deprecation', v => v.split(',')); - -program - .command('core:start') - .option('-h, --hot', 'Run front-end with HRM enabled') - .option('-t, --watchTheme', 'Watch for theme changes and regenerate variables.scss files') - .description('Starts Grafana front-end in development mode with watch enabled') - .action(async cmd => { - await execTask(startTask)({ - watchThemes: cmd.watchTheme, - hot: cmd.hot, - }); - }); - -program - .command('gui:build') - .description('Builds @grafana/ui package to packages/grafana-ui/dist') - .action(async cmd => { - await execTask(buildTask)(); - }); - -program - .command('gui:release') - .description('Prepares @grafana/ui release (and publishes to npm on demand)') - .option('-p, --publish', 'Publish @grafana/ui to npm registry') - .option('-u, --usePackageJsonVersion', 'Use version specified in package.json') - .option('--createVersionCommit', 'Create and push version commit') - .action(async cmd => { - await execTask(releaseTask)({ - publishToNpm: !!cmd.publish, - usePackageJsonVersion: !!cmd.usePackageJsonVersion, - createVersionCommit: !!cmd.createVersionCommit, - }); - }); - -program - .command('changelog') - .option('-m, --milestone ', 'Specify milestone') - .description('Builds changelog markdown') - .action(async cmd => { - if (!cmd.milestone) { - console.log('Please specify milestone, example: -m '); - return; - } - - await execTask(changelogTask)({ - milestone: cmd.milestone, - }); - }); - -program - .command('cherrypick') - .description('Helps find commits to cherry pick') - .action(async cmd => { - await execTask(cherryPickTask)({}); - }); - -program - .command('close-milestone') - .option('-m, --milestone ', 'Specify milestone') - .description('Helps ends a milestone by removing the cherry-pick label and closing it') - .action(async cmd => { - if (!cmd.milestone) { - console.log('Please specify milestone, example: -m '); - return; - } - - await execTask(closeMilestoneTask)({ - milestone: cmd.milestone, - }); - }); - -program - .command('precommit') - .description('Executes checks') - .action(async cmd => { - await execTask(precommitTask)({}); - }); - -program - .command('searchTestData') - .option('-c, --count ', 'Specify number of dashboards') - .description('Setup test data for search') - .action(async cmd => { - await execTask(searchTestDataSetupTask)({ count: cmd.count }); - }); - -program.parse(process.argv); - -if (program.depreciate && program.depreciate.length === 2) { - console.log( - chalk.yellow.bold( - `[NPM script depreciation] ${program.depreciate[0]} is deprecated! Use ${program.depreciate[1]} instead!` - ) - ); -} diff --git a/yarn.lock b/yarn.lock index 767d9d8126777..230a2bf457b98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1704,12 +1704,6 @@ dependencies: "@babel/types" "^7.3.0" -"@types/chalk@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-2.2.0.tgz#b7f6e446f4511029ee8e3f43075fb5b73fbaa0ba" - dependencies: - chalk "*" - "@types/cheerio@*": version "0.22.11" resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.11.tgz#61c0facf9636d14ba5f77fc65ed8913aa845d717" @@ -1724,12 +1718,6 @@ version "2.0.1" resolved "https://registry.yarnpkg.com/@types/clipboard/-/clipboard-2.0.1.tgz#75a74086c293d75b12bc93ff13bc7797fef05a40" -"@types/commander@2.12.2": - version "2.12.2" - resolved "https://registry.yarnpkg.com/@types/commander/-/commander-2.12.2.tgz#183041a23842d4281478fa5d23c5ca78e6fd08ae" - dependencies: - commander "*" - "@types/d3-array@*": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-2.0.0.tgz#a0d63a296a2d8435a9ec59393dcac746c6174a96" @@ -1996,6 +1984,13 @@ version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" +"@types/execa@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@types/execa/-/execa-0.9.0.tgz#9b025d2755f17e80beaf9368c3f4f319d8b0fb93" + integrity sha512-mgfd93RhzjYBUHHV532turHC2j4l/qxsF/PbfDmprHDEUHmNZGlDn1CEsulGK3AfsPdhkWzZQT/S/k0UGhLGsA== + dependencies: + "@types/node" "*" + "@types/expect-puppeteer@3.3.1": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/expect-puppeteer/-/expect-puppeteer-3.3.1.tgz#46e5944bf425b86ea13a563c7c8b86901414988d" @@ -2022,12 +2017,13 @@ "@types/react" "*" hoist-non-react-statics "^3.3.0" -"@types/inquirer@0.0.43": - version "0.0.43" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-0.0.43.tgz#1eb0bbb4648e6cc568bd396c1e989f620ad01273" +"@types/inquirer@^6.0.3": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-6.0.3.tgz#597b3c1aa4a575899841ab99bb4f1774d0b8c090" + integrity sha512-lBsdZScFMaFYYIE3Y6CWX22B9VeY2NerT1kyU2heTc3u/W6a+Om6Au2q0rMzBrzynN0l4QoABhI0cbNdyz6fDg== dependencies: - "@types/rx" "*" "@types/through" "*" + rxjs "^6.4.0" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.1" @@ -2046,6 +2042,11 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/jest-cli@^23.6.0": + version "23.6.0" + resolved "https://registry.yarnpkg.com/@types/jest-cli/-/jest-cli-23.6.0.tgz#c9cf542d28328cf5a6f1fb017dabaa3f71ab6499" + integrity sha512-o7vy+63lsHKFDibL3qZud87WpB0nTfk4j4QYB2aD6vtWMVVRRBACNcK9tMTm7Ebo68b7WiPsb/nyDYPZgtD0tg== + "@types/jest-diff@*": version "20.0.1" resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89" @@ -2073,6 +2074,7 @@ "@types/lodash@4.14.123": version "4.14.123" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.123.tgz#39be5d211478c8dd3bdae98ee75bb7efe4abfe4d" + integrity sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q== "@types/marked@0.6.5": version "0.6.5" @@ -2099,6 +2101,11 @@ version "11.13.10" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.10.tgz#4df59e5966b56f512bac98898bcbee5067411f0f" +"@types/node@^12.0.4", "@types/node@^12.0.8": + version "12.0.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.10.tgz#51babf9c7deadd5343620055fc8aff7995c8b031" + integrity sha512-LcsGbPomWsad6wmMNv7nBLw7YYYyfdYcz6xryKYQhx89c3XXan+8Q6AJ43G5XDIaklaVkK3mE4fCb0SBvMiPSQ== + "@types/papaparse@4.5.9": version "4.5.9" resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-4.5.9.tgz#ff887bd362f57cd0c87320d2de38ac232bb55e81" @@ -2117,6 +2124,11 @@ dependencies: "@types/node" "*" +"@types/prettier@^1.16.4": + version "1.16.4" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.16.4.tgz#5e5e97702cb68498aaba7349b941648daaf2385c" + integrity sha512-MG7ExKBo7AQ5UrL1awyYLNinNM/kyXgE4iP4Ul9fB+T7n768Z5Xem8IZeP6Bna0xze8gkDly49Rgge2HOEw4xA== + "@types/pretty-format@20.0.1": version "20.0.1" resolved "https://registry.yarnpkg.com/@types/pretty-format/-/pretty-format-20.0.1.tgz#7ce03b403887b087701a2b4534464f48ce7b2f48" @@ -2187,7 +2199,7 @@ dependencies: "@types/react" "*" -"@types/react-test-renderer@^16.8.2": +"@types/react-test-renderer@16.8.2": version "16.8.2" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.8.2.tgz#ad544b5571ebfc5f182c320376f1431a2b725c5e" integrity sha512-cm42QR9S9V3aOxLh7Fh7PUqQ8oSfSdnSni30T7TiTmlKvE6aUlo+LhQAzjnZBPriD9vYmgG8MXI8UDK4Nfb7gg== @@ -2239,93 +2251,17 @@ dependencies: reselect "*" -"@types/rx-core-binding@*": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz#d969d32f15a62b89e2862c17b3ee78fe329818d3" - dependencies: - "@types/rx-core" "*" - -"@types/rx-core@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-core/-/rx-core-4.0.3.tgz#0b3354b1238cedbe2b74f6326f139dbc7a591d60" - -"@types/rx-lite-aggregates@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-aggregates/-/rx-lite-aggregates-4.0.3.tgz#6efb2b7f3d5f07183a1cb2bd4b1371d7073384c2" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-async@*": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/rx-lite-async/-/rx-lite-async-4.0.2.tgz#27fbf0caeff029f41e2d2aae638b05e91ceb600c" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-backpressure@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-backpressure/-/rx-lite-backpressure-4.0.3.tgz#05abb19bdf87cc740196c355e5d0b37bb50b5d56" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-coincidence@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-coincidence/-/rx-lite-coincidence-4.0.3.tgz#80bd69acc4054a15cdc1638e2dc8843498cd85c0" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-experimental@*": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/rx-lite-experimental/-/rx-lite-experimental-4.0.1.tgz#c532f5cbdf3f2c15da16ded8930d1b2984023cbd" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-joinpatterns@*": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/rx-lite-joinpatterns/-/rx-lite-joinpatterns-4.0.1.tgz#f70fe370518a8432f29158cc92ffb56b4e4afc3e" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-testing@*": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/rx-lite-testing/-/rx-lite-testing-4.0.1.tgz#21b19d11f4dfd6ffef5a9d1648e9c8879bfe21e9" - dependencies: - "@types/rx-lite-virtualtime" "*" - -"@types/rx-lite-time@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-time/-/rx-lite-time-4.0.3.tgz#0eda65474570237598f3448b845d2696f2dbb1c4" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-virtualtime@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-virtualtime/-/rx-lite-virtualtime-4.0.3.tgz#4b30cacd0fe2e53af29f04f7438584c7d3959537" - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite@*": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@types/rx-lite/-/rx-lite-4.0.6.tgz#3c02921c4244074234f26b772241bcc20c18c253" +"@types/resolve@0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" + integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== dependencies: - "@types/rx-core" "*" - "@types/rx-core-binding" "*" + "@types/node" "*" -"@types/rx@*": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@types/rx/-/rx-4.1.1.tgz#598fc94a56baed975f194574e0f572fd8e627a48" - dependencies: - "@types/rx-core" "*" - "@types/rx-core-binding" "*" - "@types/rx-lite" "*" - "@types/rx-lite-aggregates" "*" - "@types/rx-lite-async" "*" - "@types/rx-lite-backpressure" "*" - "@types/rx-lite-coincidence" "*" - "@types/rx-lite-experimental" "*" - "@types/rx-lite-joinpatterns" "*" - "@types/rx-lite-testing" "*" - "@types/rx-lite-time" "*" - "@types/rx-lite-virtualtime" "*" +"@types/semver@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.0.1.tgz#a984b405c702fa5a7ec6abc56b37f2ba35ef5af6" + integrity sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg== "@types/sinon@^7.0.11": version "7.0.11" @@ -2577,6 +2513,13 @@ abbrev@1, abbrev@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" +abstract-leveldown@~0.12.0, abstract-leveldown@~0.12.1: + version "0.12.4" + resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-0.12.4.tgz#29e18e632e60e4e221d5810247852a63d7b2e410" + integrity sha1-KeGOYy5g5OIh1YECR4UqY9ey5BA= + dependencies: + xtend "~3.0.0" + accepts@~1.3.4, accepts@~1.3.5: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -2613,7 +2556,7 @@ acorn@^3.0.4: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" -acorn@^5.0.0, acorn@^5.5.0, acorn@^5.5.3: +acorn@^5.0.0, acorn@^5.5.0, acorn@^5.5.3, acorn@^5.7.3: version "5.7.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" @@ -2828,6 +2771,14 @@ any-observable@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" +anymatch@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" + integrity sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA== + dependencies: + micromatch "^2.1.5" + normalize-path "^2.0.0" + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -3051,7 +3002,7 @@ astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" -async-each@^1.0.1: +async-each@^1.0.0, async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -3556,6 +3507,13 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" +bl@~0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-0.8.2.tgz#c9b6bca08d1bc2ea00fc8afb4f1a5fd1e1c66e4e" + integrity sha1-yba8oI0bwuoA/Ir7Txpf0eHGbk4= + dependencies: + readable-stream "~1.0.26" + block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" @@ -3649,7 +3607,7 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" -braces@^2.3.1, braces@^2.3.2: +braces@^2.3.0, braces@^2.3.1, braces@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" dependencies: @@ -3710,6 +3668,15 @@ browserify-des@^1.0.0: inherits "^2.0.1" safe-buffer "^5.1.2" +browserify-fs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browserify-fs/-/browserify-fs-1.0.0.tgz#f075aa8a729d4d1716d066620e386fcc1311a96f" + integrity sha1-8HWqinKdTRcW0GZiDjhvzBMRqW8= + dependencies: + level-filesystem "^1.0.1" + level-js "^2.1.3" + levelup "^0.18.2" + browserify-rsa@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" @@ -3778,6 +3745,11 @@ buffer-crc32@^0.2.1: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-es6@^4.9.2, buffer-es6@^4.9.3: + version "4.9.3" + resolved "https://registry.yarnpkg.com/buffer-es6/-/buffer-es6-4.9.3.tgz#f26347b82df76fd37e18bcb5288c4970cfd5c404" + integrity sha1-8mNHuC33b9N+GLy1KIxJcM/VxAQ= + buffer-fill@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" @@ -3813,7 +3785,7 @@ builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" -builtin-modules@^3.0.0: +builtin-modules@^3.0.0, builtin-modules@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" @@ -3962,6 +3934,7 @@ caniuse-api@^3.0.0: caniuse-db@1.0.30000772: version "1.0.30000772" resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b" + integrity sha1-UarokXaChureSj2DGep21qAbUSs= caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000947, caniuse-lite@^1.0.30000957, caniuse-lite@^1.0.30000963: version "1.0.30000966" @@ -3996,7 +3969,7 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chalk@*, chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" dependencies: @@ -4080,6 +4053,42 @@ child-process-promise@^2.2.1: node-version "^1.0.0" promise-polyfill "^6.0.1" +chokidar@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" + integrity sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.0" + braces "^2.3.0" + glob-parent "^3.1.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + lodash.debounce "^4.0.8" + normalize-path "^2.1.1" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + upath "^1.0.5" + optionalDependencies: + fsevents "^1.2.2" + +chokidar@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" + integrity sha1-eY5ol3gVHIB2tLNg5e3SjNortGg= + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.4, chokidar@^2.1.0: version "2.1.5" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d" @@ -4244,6 +4253,15 @@ cliui@^4.0.0: strip-ansi "^4.0.0" wrap-ansi "^2.0.0" +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + clone-deep@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.2.4.tgz#4e73dd09e9fb971cc38670c5dced9c1896481cc6" @@ -4271,6 +4289,11 @@ clone@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" +clone@~0.1.9: + version "0.1.19" + resolved "https://registry.yarnpkg.com/clone/-/clone-0.1.19.tgz#613fb68639b26a494ac53253e15b1a6bd88ada85" + integrity sha1-YT+2hjmyaklKxTJT4Vsaa9iK2oU= + cmd-shim@^2.0.2, cmd-shim@~2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.0.2.tgz#6fcbda99483a8fd15d7d30a196ca69d688a2efdb" @@ -4333,7 +4356,7 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" -colors@^1.1.2: +colors@1.3.3, colors@^1.1.2: version "1.3.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" @@ -4358,7 +4381,7 @@ comma-separated-tokens@^1.0.0: version "1.0.6" resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.6.tgz#3cd3d8adc725ab473843db338bcdfd4a7bb087bf" -commander@*, commander@2, commander@^2.12.1, commander@^2.14.1, commander@^2.18.0, commander@^2.19.0, commander@^2.8.1, commander@^2.9.0, commander@~2.20.0: +commander@2, commander@^2.12.1, commander@^2.14.1, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.8.1, commander@^2.9.0, commander@~2.20.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" @@ -4439,7 +4462,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@1.6.2, concat-stream@^1.4.6, concat-stream@^1.5.0: +concat-stream@1.6.2, concat-stream@^1.4.4, concat-stream@^1.4.6, concat-stream@^1.5.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" dependencies: @@ -5469,6 +5492,13 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +deferred-leveldown@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-0.2.0.tgz#2cef1f111e1c57870d8bbb8af2650e587cd2f5b4" + integrity sha1-LO8fER4cV4cNi7uK8mUOWHzS9bQ= + dependencies: + abstract-leveldown "~0.12.1" + define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -5609,6 +5639,11 @@ diff@^3.1.0, diff@^3.2.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" +diff@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" + integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -5975,7 +6010,7 @@ err-code@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" -errno@^0.1.3, errno@~0.1.7: +errno@^0.1.1, errno@^0.1.3, errno@~0.1.1, errno@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" dependencies: @@ -6214,6 +6249,11 @@ estree-walker@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.0.tgz#5d865327c44a618dde5699f763891ae31f257dae" +estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + esutils@^2.0.0, esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" @@ -6262,24 +6302,24 @@ exec-sh@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b" -execa@1.0.0, execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" + cross-spawn "^5.0.1" + get-stream "^3.0.0" is-stream "^1.1.0" npm-run-path "^2.0.0" p-finally "^1.0.0" signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" + cross-spawn "^6.0.0" + get-stream "^4.0.0" is-stream "^1.1.0" npm-run-path "^2.0.0" p-finally "^1.0.0" @@ -6731,6 +6771,11 @@ for-own@^1.0.0: dependencies: for-in "^1.0.1" +foreach@~2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -6864,7 +6909,7 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" -fsevents@^1.2.7: +fsevents@^1.0.0, fsevents@^1.2.2, fsevents@^1.2.7: version "1.2.9" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" dependencies: @@ -6896,6 +6941,13 @@ fuzzy-search@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/fuzzy-search/-/fuzzy-search-3.0.1.tgz#14a4964508a9607d6e9a88818e7ff634108260b6" +fwd-stream@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/fwd-stream/-/fwd-stream-1.0.4.tgz#ed281cabed46feecf921ee32dc4c50b372ac7cfa" + integrity sha1-7Sgcq+1G/uz5Ie4y3ExQs3KsfPo= + dependencies: + readable-stream "~1.0.26-4" + g-status@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/g-status/-/g-status-2.0.2.tgz#270fd32119e8fc9496f066fe5fe88e0a6bc78b97" @@ -6956,6 +7008,11 @@ get-caller-file@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + get-document@1: version "1.0.0" resolved "https://registry.yarnpkg.com/get-document/-/get-document-1.0.0.tgz#4821bce66f1c24cb0331602be6cb6b12c4f01c4b" @@ -7023,19 +7080,19 @@ glob-base@^0.3.0: glob-parent "^2.0.0" is-glob "^2.0.0" -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - dependencies: - is-glob "^2.0.0" - -glob-parent@^3.1.0: +glob-parent@3.1.0, glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" dependencies: is-glob "^3.1.0" path-dirname "^1.0.0" +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" @@ -7062,6 +7119,18 @@ glob@7.1.3, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glo once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@~5.0.0: version "5.0.15" resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" @@ -7803,6 +7872,11 @@ icss-utils@^4.1.0: dependencies: postcss "^7.0.14" +idb-wrapper@^1.5.0: + version "1.7.2" + resolved "https://registry.yarnpkg.com/idb-wrapper/-/idb-wrapper-1.7.2.tgz#8251afd5e77fe95568b1c16152eb44b396767ea2" + integrity sha512-zfNREywMuf0NzDo9mVsL0yegjsirJxHpKHvWcyRozIqQy89g0a3U+oBPOCN4cc0oCiOuYgZHimzaW/R46G1Mpg== + ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" @@ -7902,7 +7976,7 @@ indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" -indexof@0.0.1: +indexof@0.0.1, indexof@~0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" @@ -7963,24 +8037,6 @@ inquirer@6.2.1: strip-ansi "^5.0.0" through "^2.3.6" -inquirer@6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.2.tgz#46941176f65c9eb20804627149b743a218f25406" - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.11" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.0.0" - through "^2.3.6" - inquirer@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" @@ -8017,6 +8073,25 @@ inquirer@^6.2.0: strip-ansi "^5.1.0" through "^2.3.6" +inquirer@^6.3.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.4.1.tgz#7bd9e5ab0567cd23b41b0180b68e0cfa82fc3c0b" + integrity sha512-/Jw+qPZx4EDYsaT6uz7F4GJRNFMRdKNeUZw3ZnKV8lyuUgz/YWRCSUAJMZSVhSq4Ec0R2oYnyi6b3d4JXcL5Nw== + dependencies: + ansi-escapes "^3.2.0" + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^2.0.0" + lodash "^4.17.11" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.4.0" + string-width "^2.1.0" + strip-ansi "^5.1.0" + through "^2.3.6" + internal-ip@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" @@ -8337,6 +8412,11 @@ is-obj@^1.0.0, is-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" +is-object@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-0.1.2.tgz#00efbc08816c33cfc4ac8251d132e10dc65098d7" + integrity sha1-AO+8CIFsM8/ErIJR0TLhDcZQmNc= + is-observable@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-1.1.0.tgz#b3e986c8f44de950867cab5403f5a3465005975e" @@ -8405,6 +8485,13 @@ is-redirect@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" +is-reference@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.2.tgz#01cf91517d21db66a34642287ed6e70d53dcbe5c" + integrity sha512-Kn5g8c7XHKejFOpTf2QN9YjiHHKl5xRj+2uAZf9iM2//nkBNi/NNeB5JMoun28nEaUVHyPUzqzhfRlfAirEjXg== + dependencies: + "@types/estree" "0.0.39" + is-regex@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" @@ -8477,6 +8564,11 @@ is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" +is@~0.2.6: + version "0.2.7" + resolved "https://registry.yarnpkg.com/is/-/is-0.2.7.tgz#3b34a2c48f359972f35042849193ae7264b63562" + integrity sha1-OzSixI81mXLzUEKEkZOucmS2NWI= + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -8485,6 +8577,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" +isbuffer@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/isbuffer/-/isbuffer-0.0.0.tgz#38c146d9df528b8bf9b0701c3d43cf12df3fc39b" + integrity sha1-OMFG2d9Si4v5sHAcPUPPEt8/w5s= + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -9159,6 +9256,91 @@ left-pad@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" +level-blobs@^0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/level-blobs/-/level-blobs-0.1.7.tgz#9ab9b97bb99f1edbf9f78a3433e21ed56386bdaf" + integrity sha1-mrm5e7mfHtv594o0M+Ie1WOGva8= + dependencies: + level-peek "1.0.6" + once "^1.3.0" + readable-stream "^1.0.26-4" + +level-filesystem@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/level-filesystem/-/level-filesystem-1.2.0.tgz#a00aca9919c4a4dfafdca6a8108d225aadff63b3" + integrity sha1-oArKmRnEpN+v3KaoEI0iWq3/Y7M= + dependencies: + concat-stream "^1.4.4" + errno "^0.1.1" + fwd-stream "^1.0.4" + level-blobs "^0.1.7" + level-peek "^1.0.6" + level-sublevel "^5.2.0" + octal "^1.0.0" + once "^1.3.0" + xtend "^2.2.0" + +level-fix-range@2.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/level-fix-range/-/level-fix-range-2.0.0.tgz#c417d62159442151a19d9a2367868f1724c2d548" + integrity sha1-xBfWIVlEIVGhnZojZ4aPFyTC1Ug= + dependencies: + clone "~0.1.9" + +level-fix-range@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/level-fix-range/-/level-fix-range-1.0.2.tgz#bf15b915ae36d8470c821e883ddf79cd16420828" + integrity sha1-vxW5Fa422EcMgh6IPd95zRZCCCg= + +"level-hooks@>=4.4.0 <5": + version "4.5.0" + resolved "https://registry.yarnpkg.com/level-hooks/-/level-hooks-4.5.0.tgz#1b9ae61922930f3305d1a61fc4d83c8102c0dd93" + integrity sha1-G5rmGSKTDzMF0aYfxNg8gQLA3ZM= + dependencies: + string-range "~1.2" + +level-js@^2.1.3: + version "2.2.4" + resolved "https://registry.yarnpkg.com/level-js/-/level-js-2.2.4.tgz#bc055f4180635d4489b561c9486fa370e8c11697" + integrity sha1-vAVfQYBjXUSJtWHJSG+jcOjBFpc= + dependencies: + abstract-leveldown "~0.12.0" + idb-wrapper "^1.5.0" + isbuffer "~0.0.0" + ltgt "^2.1.2" + typedarray-to-buffer "~1.0.0" + xtend "~2.1.2" + +level-peek@1.0.6, level-peek@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/level-peek/-/level-peek-1.0.6.tgz#bec51c72a82ee464d336434c7c876c3fcbcce77f" + integrity sha1-vsUccqgu5GTTNkNMfIdsP8vM538= + dependencies: + level-fix-range "~1.0.2" + +level-sublevel@^5.2.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/level-sublevel/-/level-sublevel-5.2.3.tgz#744c12c72d2e72be78dde3b9b5cd84d62191413a" + integrity sha1-dEwSxy0ucr543eO5tc2E1iGRQTo= + dependencies: + level-fix-range "2.0" + level-hooks ">=4.4.0 <5" + string-range "~1.2.1" + xtend "~2.0.4" + +levelup@^0.18.2: + version "0.18.6" + resolved "https://registry.yarnpkg.com/levelup/-/levelup-0.18.6.tgz#e6a01cb089616c8ecc0291c2a9bd3f0c44e3e5eb" + integrity sha1-5qAcsIlhbI7MApHCqb0/DETj5es= + dependencies: + bl "~0.8.1" + deferred-leveldown "~0.2.0" + errno "~0.1.1" + prr "~0.0.0" + readable-stream "~1.0.26" + semver "~2.3.1" + xtend "~3.0.0" + leven@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" @@ -9669,7 +9851,19 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -magic-string@^0.25.1: +ltgt@^2.1.2: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5" + integrity sha1-81ypHEk/e3PaDgdJUwTxezH4fuU= + +magic-string@^0.22.5: + version "0.22.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e" + integrity sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w== + dependencies: + vlq "^0.2.2" + +magic-string@^0.25.1, magic-string@^0.25.2: version "0.25.2" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.2.tgz#139c3a729515ec55e96e69e82a11fe890a293ad9" dependencies: @@ -9884,7 +10078,7 @@ methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" -micromatch@^2.3.11: +micromatch@^2.1.5, micromatch@^2.3.11: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" dependencies: @@ -10783,6 +10977,20 @@ object-keys@^1.0.0, object-keys@^1.0.11, object-keys@^1.0.12: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" +object-keys@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.2.0.tgz#cddec02998b091be42bf1035ae32e49f1cb6ea67" + integrity sha1-zd7AKZiwkb5CvxA1rjLknxy26mc= + dependencies: + foreach "~2.0.1" + indexof "~0.0.1" + is "~0.2.6" + +object-keys@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" + integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY= + object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -10855,6 +11063,11 @@ obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" +octal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/octal/-/octal-1.0.0.tgz#63e7162a68efbeb9e213588d58e989d1e5c4530b" + integrity sha1-Y+cWKmjvvrniE1iNWOmJ0eXEUws= + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -10922,15 +11135,16 @@ optionator@^0.8.1: type-check "~0.3.2" wordwrap "~1.0.0" -ora@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-3.2.0.tgz#67e98a7e11f7f0ac95deaaaf11bb04de3d09e481" +ora@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" + integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== dependencies: chalk "^2.4.2" cli-cursor "^2.1.0" cli-spinners "^2.0.0" log-symbols "^2.2.0" - strip-ansi "^5.0.0" + strip-ansi "^5.2.0" wcwidth "^1.0.1" ordered-ast-traverse@~1.1.1: @@ -10971,7 +11185,7 @@ os-locale@^2.0.0: lcid "^1.0.0" mem "^1.1.0" -os-locale@^3.0.0: +os-locale@^3.0.0, os-locale@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" dependencies: @@ -11837,6 +12051,11 @@ prettier@1.16.4: version "1.16.4" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717" +prettier@^1.17.1: + version "1.18.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea" + integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw== + pretty-bytes@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" @@ -11887,6 +12106,11 @@ private@^0.1.6, private@~0.1.5: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" +process-es6@^0.11.2, process-es6@^0.11.6: + version "0.11.6" + resolved "https://registry.yarnpkg.com/process-es6/-/process-es6-0.11.6.tgz#c6bb389f9a951f82bd4eb169600105bd2ff9c778" + integrity sha1-xrs4n5qVH4K9TrFpYAEFvS/5x3g= + process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" @@ -12004,6 +12228,11 @@ proxy-from-env@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" +prr@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" + integrity sha1-GoS4WQgyVQFBGFPQCB7j+obikmo= + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -12785,6 +13014,15 @@ read@1, read@~1.0.1, read@~1.0.7: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^1.0.26-4, readable-stream@~1.1.10: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + readable-stream@^3.0.6, readable-stream@^3.1.1: version "3.3.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.3.0.tgz#cb8011aad002eb717bf040291feba8569c986fb9" @@ -12793,9 +13031,10 @@ readable-stream@^3.0.6, readable-stream@^3.1.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@~1.1.10: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" +readable-stream@~1.0.26, readable-stream@~1.0.26-4: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= dependencies: core-util-is "~1.0.0" inherits "~2.0.1" @@ -12811,7 +13050,7 @@ readdir-scoped-modules@^1.0.0: graceful-fs "^4.1.2" once "^1.3.0" -readdirp@^2.2.1: +readdirp@^2.0.0, readdirp@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" dependencies: @@ -13082,6 +13321,15 @@ replace-ext@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" +replace-in-file@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/replace-in-file/-/replace-in-file-4.1.0.tgz#07846245a4b98a8bc5f5f9c3e3e368fa12b18bf7" + integrity sha512-5IANGPAQZVO9PHEjaRz3EHs7eQulv1cvOu2hRi7+Ern+zXIzdslSGX9MIfcI1anaGCpICK2l4R4YOOdThsFryQ== + dependencies: + chalk "^2.4.2" + glob "^7.1.3" + yargs "^13.2.2" + request-progress@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-2.0.1.tgz#5d36bb57961c673aa5b788dbc8141fdf23b44e08" @@ -13143,6 +13391,11 @@ require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" +require-relative@0.8.7: + version "0.8.7" + resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" + integrity sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4= + require-uncached@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" @@ -13209,15 +13462,23 @@ resolve@1.1.7, resolve@~1.1.0: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" +resolve@1.10.1, resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.8.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18" + dependencies: + path-parse "^1.0.6" + resolve@1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" + integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== dependencies: path-parse "^1.0.5" -resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.8.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18" +resolve@^1.11.0, resolve@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" + integrity sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw== dependencies: path-parse "^1.0.6" @@ -13296,6 +13557,56 @@ rollup-plugin-commonjs@9.2.1: resolve "^1.10.0" rollup-pluginutils "^2.3.3" +rollup-plugin-commonjs@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.0.1.tgz#fbfcadf4ce2e826068e056a9f5c19287d9744ddf" + integrity sha512-x0PcCVdEc4J8igv1qe2vttz8JKAKcTs3wfIA3L8xEty3VzxgORLrzZrNWaVMc+pBC4U3aDOb9BnWLAQ8J11vkA== + dependencies: + estree-walker "^0.6.1" + is-reference "^1.1.2" + magic-string "^0.25.2" + resolve "^1.11.0" + rollup-pluginutils "^2.8.1" + +rollup-plugin-copy-glob@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-copy-glob/-/rollup-plugin-copy-glob-0.3.0.tgz#cec0e96c26e8a5aee0930c62e6147f941a39452d" + integrity sha512-x2ialxhpchEiOZ5cE8h3/9MDm2nMNFGXTssJxy+OFCldl0qISRQmtGmqkdal1KOiT4UebLct4K2kkhd6fvugLg== + dependencies: + chokidar "2.0.4" + colors "1.3.3" + glob "7.1.3" + glob-parent "3.1.0" + +rollup-plugin-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-json/-/rollup-plugin-json-4.0.0.tgz#a18da0a4b30bf5ca1ee76ddb1422afbb84ae2b9e" + integrity sha512-hgb8N7Cgfw5SZAkb3jf0QXii6QX/FOkiIq2M7BAQIEydjHvTyxXHQiIzZaTFgx1GK0cRCHOCBHIyEkkLdWKxow== + dependencies: + rollup-pluginutils "^2.5.0" + +rollup-plugin-node-builtins@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-node-builtins/-/rollup-plugin-node-builtins-2.1.2.tgz#24a1fed4a43257b6b64371d8abc6ce1ab14597e9" + integrity sha1-JKH+1KQyV7a2Q3HYq8bOGrFFl+k= + dependencies: + browserify-fs "^1.0.0" + buffer-es6 "^4.9.2" + crypto-browserify "^3.11.0" + process-es6 "^0.11.2" + +rollup-plugin-node-globals@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-node-globals/-/rollup-plugin-node-globals-1.4.0.tgz#5e1f24a9bb97c0ef51249f625e16c7e61b7c020b" + integrity sha512-xRkB+W/m1KLIzPUmG0ofvR+CPNcvuCuNdjVBVS7ALKSxr3EDhnzNceGkGi1m8MToSli13AzKFYH4ie9w3I5L3g== + dependencies: + acorn "^5.7.3" + buffer-es6 "^4.9.3" + estree-walker "^0.5.2" + magic-string "^0.22.5" + process-es6 "^0.11.6" + rollup-pluginutils "^2.3.1" + rollup-plugin-node-resolve@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.0.1.tgz#f95765d174e5daeef9ea6268566141f53aa9d422" @@ -13304,7 +13615,18 @@ rollup-plugin-node-resolve@4.0.1: is-module "^1.0.0" resolve "^1.10.0" -rollup-plugin-sourcemaps@0.4.2: +rollup-plugin-node-resolve@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.1.0.tgz#49608b6ecaf2b776ab83e317d39b282d65d21b76" + integrity sha512-2hwwHNj0s8UEtUNT+lJq8rFWEznP7yJm3GCHBicadF6hiNX1aRARRZIjz2doeTlTGg/hOvJr4C/8+3k9Y/J5Hg== + dependencies: + "@types/resolve" "0.0.8" + builtin-modules "^3.1.0" + is-module "^1.0.0" + resolve "^1.11.1" + rollup-pluginutils "^2.8.1" + +rollup-plugin-sourcemaps@0.4.2, rollup-plugin-sourcemaps@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.4.2.tgz#62125aa94087aadf7b83ef4dfaf629b473135e87" dependencies: @@ -13320,15 +13642,36 @@ rollup-plugin-terser@4.0.4: serialize-javascript "^1.6.1" terser "^3.14.1" +rollup-plugin-terser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.0.0.tgz#ac50fdb703b580447a7e6b1692aeed515a6be8cf" + integrity sha512-W+jJ4opYnlmNyVW0vtRufs+EGf68BIJ7bnOazgz8mgz8pA9lUyrEifAhPs5y9M16wFeAyBGaRjKip4dnFBtXaw== + dependencies: + "@babel/code-frame" "^7.0.0" + jest-worker "^24.6.0" + serialize-javascript "^1.7.0" + terser "^4.0.0" + rollup-plugin-typescript2@0.19.3: version "0.19.3" resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.19.3.tgz#713063233461765f030a2baa2640905c2656164f" + integrity sha512-lsRqfBCZhMl/tq9AT5YnQvzQWzXtnx3EQYFcHD72gul7nyyoOrzx5yCEH20smpw58v6UkHHZz03FbdLEPoHWjA== dependencies: fs-extra "7.0.1" resolve "1.8.1" rollup-pluginutils "2.3.3" tslib "1.9.3" +rollup-plugin-typescript2@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.21.1.tgz#91cea787c5282762f4849e334cbef6a9fdaa7564" + integrity sha512-jM2tn8/fUKDRmDtH++/6CHYHv2R9dxfXnuW0rxbOq1Zrxdsg4g6w+WwbK0X2ma21WQcT9l/U9bA3RO+2SBIJ/A== + dependencies: + fs-extra "7.0.1" + resolve "1.10.1" + rollup-pluginutils "2.6.0" + tslib "1.9.3" + rollup-plugin-visualizer@0.9.2: version "0.9.2" resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-0.9.2.tgz#bbc8e8e67d5aa3e6c188c5ca0fcfa57234fb9f92" @@ -13338,20 +13681,47 @@ rollup-plugin-visualizer@0.9.2: source-map "^0.7.3" typeface-oswald "0.0.54" +rollup-plugin-visualizer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-1.1.1.tgz#454ae0aed23845407ebfb81cc52114af308d6d90" + integrity sha512-7xkSKp+dyJmSC7jg2LXqViaHuOnF1VvIFCnsZEKjrgT5ZVyiLLSbeszxFcQSfNJILphqgAEmWAUz0Z4xYScrRw== + dependencies: + mkdirp "^0.5.1" + opn "^5.4.0" + source-map "^0.7.3" + typeface-oswald "0.0.54" + rollup-pluginutils@2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz#3aad9b1eb3e7fe8262820818840bf091e5ae6794" + integrity sha512-2XZwja7b6P5q4RZ5FhyX1+f46xi1Z3qBKigLRZ6VTZjwbN0K1IFGMlwm06Uu0Emcre2Z63l77nq/pzn+KxIEoA== dependencies: estree-walker "^0.5.2" micromatch "^2.3.11" -rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.3.3: +rollup-pluginutils@2.6.0, rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.3.3: version "2.6.0" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.6.0.tgz#203706edd43dfafeaebc355d7351119402fc83ad" dependencies: estree-walker "^0.6.0" micromatch "^3.1.10" +rollup-pluginutils@^2.3.1, rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz#8fa6dd0697344938ef26c2c09d2488ce9e33ce97" + integrity sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg== + dependencies: + estree-walker "^0.6.1" + +rollup-watch@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/rollup-watch/-/rollup-watch-4.3.1.tgz#5aa1eaeab787addf368905d102b39d6fc5ce4a8b" + integrity sha512-6yjnIwfjpSrqA8IafyIu7fsEyeImNR4aDjA1bQ7KWeVuiA+Clfsx8+PGQkyABWIQzmauQ//tIJ5wAxLXsXs8qQ== + dependencies: + chokidar "^1.7.0" + require-relative "0.8.7" + rollup-pluginutils "^2.0.1" + rollup@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.6.0.tgz#4329f4634718197c678d18491724d50d8b7ee76c" @@ -13360,6 +13730,15 @@ rollup@1.6.0: "@types/node" "^11.9.5" acorn "^6.1.1" +rollup@^1.14.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.16.2.tgz#959aeae4b06c8e540749bac442d6d37aefb9217d" + integrity sha512-UAZxaQvH0klYZdF+90xv9nGb+m4p8jdoaow1VL5/RzDK/gN/4CjvaMmJNcOIv1/+gtzswKhAg/467mzF0sLpAg== + dependencies: + "@types/estree" "0.0.39" + "@types/node" "^12.0.8" + acorn "^6.1.1" + rst-selector-parser@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" @@ -13596,7 +13975,7 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@5.7.0, "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" @@ -13604,6 +13983,16 @@ semver@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.0.0.tgz#05e359ee571e5ad7ed641a6eec1e547ba52dea65" +semver@^6.1.1: + version "6.1.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.2.tgz#079960381376a3db62eb2edc8a3bfb10c7cfe318" + integrity sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ== + +semver@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52" + integrity sha1-uYSPJdbPNjMwc+ye+IVtQvEjPlI= + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -13633,7 +14022,7 @@ sentence-case@^2.1.0: no-case "^2.2.0" upper-case-first "^1.1.2" -serialize-javascript@^1.4.0, serialize-javascript@^1.6.1: +serialize-javascript@^1.4.0, serialize-javascript@^1.6.1, serialize-javascript@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65" @@ -14270,6 +14659,11 @@ string-length@^2.0.0: astral-regex "^1.0.0" strip-ansi "^4.0.0" +string-range@~1.2, string-range@~1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/string-range/-/string-range-1.2.2.tgz#a893ed347e72299bc83befbbf2a692a8d239d5dd" + integrity sha1-qJPtNH5yKZvIO++78qaSqNI51d0= + string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -14285,7 +14679,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^3.0.0: +string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" dependencies: @@ -14397,7 +14791,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0: +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" dependencies: @@ -14647,6 +15041,15 @@ terser@^3.14.1, terser@^3.16.1: source-map "~0.6.1" source-map-support "~0.5.10" +terser@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.0.0.tgz#ef356f6f359a963e2cc675517f21c1c382877374" + integrity sha512-dOapGTU0hETFl1tCo4t56FN+2jffoKyER9qBGoUFyZ6y7WLoKT0bF+lAYi6B6YsILcGF3q1C2FBh8QcKSCgkgA== + dependencies: + commander "^2.19.0" + source-map "~0.6.1" + source-map-support "~0.5.10" + test-exclude@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" @@ -14918,6 +15321,17 @@ ts-node@8.1.0: source-map-support "^0.5.6" yn "^3.0.0" +ts-node@^8.2.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.3.0.tgz#e4059618411371924a1fb5f3b125915f324efb57" + integrity sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.6" + yn "^3.0.0" + tslib@1.9.3, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" @@ -15001,6 +15415,11 @@ typed-styles@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9" +typedarray-to-buffer@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-1.0.4.tgz#9bb8ba0e841fb3f4cf1fe7c245e9f3fa8a5fe99c" + integrity sha1-m7i6DoQfs/TPH+fCRenz+opf6Zw= + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -15176,7 +15595,7 @@ unzip-response@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" -upath@^1.1.1: +upath@^1.0.5, upath@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" @@ -15377,6 +15796,11 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" +vlq@^0.2.2: + version "0.2.3" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" + integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== + vm-browserify@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" @@ -15749,6 +16173,15 @@ wrap-ansi@^3.0.1: string-width "^2.1.1" strip-ansi "^4.0.0" +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -15818,10 +16251,35 @@ xss@1.0.3: commander "^2.9.0" cssfilter "0.0.10" +xtend@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.2.0.tgz#eef6b1f198c1c8deafad8b1765a04dad4a01c5a9" + integrity sha1-7vax8ZjByN6vrYsXZaBNrUoBxak= + xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" +xtend@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.0.6.tgz#5ea657a6dba447069c2e59c58a1138cb0c5e6cee" + integrity sha1-XqZXptukRwacLlnFihE4ywxebO4= + dependencies: + is-object "~0.1.2" + object-keys "~0.2.0" + +xtend@~2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b" + integrity sha1-bv7MKk2tjmlixJAbM3znuoe10os= + dependencies: + object-keys "~0.4.0" + +xtend@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" + integrity sha1-XM50B7r2Qsunvs2laBEcST9ZZlo= + y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" @@ -15851,6 +16309,14 @@ yargs-parser@^11.1.1: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^13.1.0: + version "13.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" + integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" @@ -15914,6 +16380,23 @@ yargs@^12.0.1, yargs@^12.0.2, yargs@^12.0.4: y18n "^3.2.1 || ^4.0.0" yargs-parser "^11.1.1" +yargs@^13.2.2: + version "13.2.4" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" + integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + os-locale "^3.1.0" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.0" + yargs@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" From 70d4dfe9f70a962591109b3c3b830a78bfd2faa1 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 28 Jun 2019 05:25:38 -0700 Subject: [PATCH 04/17] Dashboard: Force update after dashboard resize (#17808) --- public/app/features/dashboard/dashgrid/DashboardGrid.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index 6f57f6f42e391..639e34a16ae27 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -163,6 +163,7 @@ export class DashboardGrid extends PureComponent { for (const panel of this.props.dashboard.panels) { panel.resizeDone(); } + this.forceUpdate(); }; onViewModeChanged = () => { From d9fea07e6edc05056f825525cbe6756588c2ca30 Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki Date: Fri, 28 Jun 2019 16:21:32 +0300 Subject: [PATCH 05/17] Graphite: use POST for /metrics/find requests (#17814) * Add test that expects a POST request * Change graphite /metric/find request to POST Query parameter can become large enough to exceed GET URI limits. * Fix requests with time range Initialise httpOptions.params * Fix for supporting queries referencing template variable --- public/app/plugins/datasource/graphite/datasource.ts | 8 +++++--- .../datasource/graphite/specs/datasource.test.ts | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index ea41dc126e1d9..c1220c9504f23 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -245,10 +245,12 @@ export function GraphiteDatasource(this: any, instanceSettings, $q, backendSrv, } const httpOptions: any = { - method: 'GET', + method: 'POST', url: '/metrics/find', - params: { - query: interpolatedQuery, + params: {}, + data: `query=${interpolatedQuery}`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', }, // for cancellations requestId: options.requestId, diff --git a/public/app/plugins/datasource/graphite/specs/datasource.test.ts b/public/app/plugins/datasource/graphite/specs/datasource.test.ts index de98f55172bd3..d0f83ab271778 100644 --- a/public/app/plugins/datasource/graphite/specs/datasource.test.ts +++ b/public/app/plugins/datasource/graphite/specs/datasource.test.ts @@ -303,6 +303,18 @@ describe('graphiteDatasource', () => { expect(requestOptions.params.expr).toEqual(['server=~backend*']); expect(results).not.toBe(null); }); + + it('/metrics/find should be POST', () => { + ctx.templateSrv.setGrafanaVariable('foo', 'bar'); + ctx.ds.metricFindQuery('[[foo]]').then(data => { + results = data; + }); + expect(requestOptions.url).toBe('/api/datasources/proxy/1/metrics/find'); + expect(requestOptions.method).toEqual('POST'); + expect(requestOptions.headers).toHaveProperty('Content-Type', 'application/x-www-form-urlencoded'); + expect(requestOptions.data).toMatch(`query=bar`); + expect(requestOptions).toHaveProperty('params'); + }); }); }); From c392a492aef1c98979e3e93a99b989c83c29ef68 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 28 Jun 2019 16:32:42 +0200 Subject: [PATCH 06/17] Docs: Add v6.3 version notes and encryption format information (#17825) Adds version note for alert rule tags. Adds version note for graph data links. Adds information about secret_key and encryption format in use Fixes #17815 Fixes #17803 --- docs/sources/alerting/notifications.md | 2 ++ docs/sources/features/panels/graph.md | 2 ++ docs/sources/installation/configuration.md | 2 +- docs/sources/installation/upgrading.md | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/sources/alerting/notifications.md b/docs/sources/alerting/notifications.md index 40b830c8d54b2..061587688708b 100644 --- a/docs/sources/alerting/notifications.md +++ b/docs/sources/alerting/notifications.md @@ -199,6 +199,8 @@ Notification services which need public image access are marked as 'external onl # Use alert rule tags in notifications {#alert-rule-tags} +> Only available in Grafana v6.3+. + Grafana can include a list of tags (key/value) in the notification. It's called alert rule tags to contrast with tags parsed from timeseries. It currently supports only the Prometheus Alertmanager notifier. diff --git a/docs/sources/features/panels/graph.md b/docs/sources/features/panels/graph.md index 95a07da6cc2b1..97a9c7c9e0e1e 100644 --- a/docs/sources/features/panels/graph.md +++ b/docs/sources/features/panels/graph.md @@ -37,6 +37,8 @@ Repeat a panel for each value of a variable. Repeating panels are described in ### Data link +> Only available in Grafana v6.3+. + Data link in graph settings allows adding dynamic links to the visualization. Those links can link to either other dashboard or to an external URL. {{< docs-imagebox img="/img/docs/data_link.png" max-width= "800px" >}} diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index 755b4615a5064..d3a87a979d53c 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -304,7 +304,7 @@ The number of days the keep me logged in / remember me cookie lasts. ### secret_key -Used for signing some datasource settings like secrets and passwords. Cannot be changed without requiring an update +Used for signing some datasource settings like secrets and passwords, the encryption format used is AES-256 in CFB mode. Cannot be changed without requiring an update to datasource settings to re-encode them. ### disable_gravatar diff --git a/docs/sources/installation/upgrading.md b/docs/sources/installation/upgrading.md index 22195cb9df503..e605b9bcadfc4 100644 --- a/docs/sources/installation/upgrading.md +++ b/docs/sources/installation/upgrading.md @@ -154,7 +154,7 @@ The default cookie name for storing the auth token is `grafana_session`. you can ### Ensure encryption of datasource secrets -Datasources store passwords and basic auth passwords in secureJsonData encrypted by default. Existing datasource +Datasources store passwords and basic auth passwords in secureJsonData encrypted (AES-256 in CFB mode) by default. Existing datasource will keep working with unencrypted passwords. If you want to migrate to encrypted storage for your existing datasources you can do that by: From 53db8235115c85ed11497b68177b69467c2ff4f0 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 28 Jun 2019 17:05:59 +0200 Subject: [PATCH 07/17] Explore: Fix filter by series level in logs graph (#17798) Adds back support for filtering log messages by series level in logs graph which seem to have been broken since merging of #17027. Fixes #17769 --- public/app/features/explore/Graph.tsx | 10 +++++++--- public/app/features/explore/Logs.tsx | 6 +++--- public/app/features/explore/LogsContainer.tsx | 5 +++-- public/app/features/explore/state/actionTypes.ts | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/public/app/features/explore/Graph.tsx b/public/app/features/explore/Graph.tsx index 1a65bd2397cf6..7d665fd2520d6 100644 --- a/public/app/features/explore/Graph.tsx +++ b/public/app/features/explore/Graph.tsx @@ -79,7 +79,7 @@ interface GraphProps { split?: boolean; userOptions?: any; onChangeTime?: (range: AbsoluteTimeRange) => void; - onToggleSeries?: (alias: string, hiddenSeries: Set) => void; + onToggleSeries?: (alias: string, hiddenSeries: string[]) => void; } interface GraphState { @@ -218,8 +218,8 @@ export class Graph extends PureComponent { const exclusive = event.ctrlKey || event.metaKey || event.shiftKey; this.setState((state, props) => { - const { data } = props; - let nextHiddenSeries = []; + const { data, onToggleSeries } = props; + let nextHiddenSeries: string[] = []; if (exclusive) { // Toggling series with key makes the series itself to toggle if (state.hiddenSeries.indexOf(label) > -1) { @@ -238,6 +238,10 @@ export class Graph extends PureComponent { } } + if (onToggleSeries) { + onToggleSeries(label, nextHiddenSeries); + } + return { hiddenSeries: nextHiddenSeries, }; diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 1632bc684054a..e2a9539be97f1 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -68,7 +68,7 @@ interface Props { onStartScanning?: () => void; onStopScanning?: () => void; onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void; - onToggleLogLevel: (hiddenLogLevels: Set) => void; + onToggleLogLevel: (hiddenLogLevels: LogLevel[]) => void; getRowContext?: (row: LogRowModel, options?: any) => Promise; } @@ -135,8 +135,8 @@ export default class Logs extends PureComponent { }); }; - onToggleLogLevel = (rawLevel: string, hiddenRawLevels: Set) => { - const hiddenLogLevels: Set = new Set(Array.from(hiddenRawLevels).map(level => LogLevel[level])); + onToggleLogLevel = (rawLevel: string, hiddenRawLevels: string[]) => { + const hiddenLogLevels: LogLevel[] = hiddenRawLevels.map(level => LogLevel[level]); this.props.onToggleLogLevel(hiddenLogLevels); }; diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index a5ef33a6945ac..4b5c00798d498 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -68,7 +68,7 @@ export class LogsContainer extends Component { this.props.changeDedupStrategy(this.props.exploreId, dedupStrategy); }; - hangleToggleLogLevel = (hiddenLogLevels: Set) => { + handleToggleLogLevel = (hiddenLogLevels: LogLevel[]) => { const { exploreId } = this.props; this.props.toggleLogLevelAction({ exploreId, @@ -93,6 +93,7 @@ export class LogsContainer extends Component { nextProps.loading !== this.props.loading || nextProps.dedupStrategy !== this.props.dedupStrategy || nextProps.logsHighlighterExpressions !== this.props.logsHighlighterExpressions || + nextProps.hiddenLogLevels !== this.props.hiddenLogLevels || nextProps.scanning !== this.props.scanning || nextProps.isLive !== this.props.isLive ); @@ -139,7 +140,7 @@ export class LogsContainer extends Component { onStartScanning={onStartScanning} onStopScanning={onStopScanning} onDedupStrategyChange={this.handleDedupStrategyChange} - onToggleLogLevel={this.hangleToggleLogLevel} + onToggleLogLevel={this.handleToggleLogLevel} absoluteRange={absoluteRange} timeZone={timeZone} scanning={scanning} diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index cc230bc2d4f0e..4d9d7c9ad27b8 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -201,7 +201,7 @@ export interface UpdateDatasourceInstancePayload { export interface ToggleLogLevelPayload { exploreId: ExploreId; - hiddenLogLevels: Set; + hiddenLogLevels: LogLevel[]; } export interface QueriesImportedPayload { From 0833f26b4337089af99b1b731d7e19588333e061 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 28 Jun 2019 18:38:16 +0200 Subject: [PATCH 08/17] Elasticsearch: Fix default max concurrent shard requests (#17770) Elasticsearch v7.0 changed the behavior of max concurrent shard requests and the default to 5. v5.6 and before 7.0 the default is 256. This adds some additional behavior given certain version is selected when configure the datasource to set default max concurrent shard requests. Changing from a version pre-v7.0+ to v7.0+ sets max concurrent shard requests to 5. Changing from a version v7.0+ to a pre-v7.0 sets max concurrent shard requests to 256. Fixes #17454 --- docs/sources/installation/upgrading.md | 7 +++++ .../datasource/elasticsearch/config_ctrl.ts | 9 ++++++- .../datasource/elasticsearch/datasource.ts | 13 ++++++++++ .../elasticsearch/partials/config.html | 2 +- .../elasticsearch/specs/datasource.test.ts | 26 ++++++++++++++++++- 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/docs/sources/installation/upgrading.md b/docs/sources/installation/upgrading.md index e605b9bcadfc4..027af100f7b94 100644 --- a/docs/sources/installation/upgrading.md +++ b/docs/sources/installation/upgrading.md @@ -175,3 +175,10 @@ this new setting. In 6.2 we completely removed the backend session storage since we replaced the previous login session implementation with an auth token. If you are using Auth proxy with LDAP an shared cached is used in Grafana so you might want configure [remote_cache] instead. If not Grafana will fallback to using the database as an shared cache. + +### Upgrading Elasticsearch to v7.0+ + +The semantics of `max concurrent shard requests` changed in Elasticsearch v7.0, see [release notes](https://www.elastic.co/guide/en/elasticsearch/reference/7.0/breaking-changes-7.0.html#semantics-changed-max-concurrent-shared-requests) for reference. + +If you upgrade Elasticsearch to v7.0+ you should make sure to update the datasource configuration in Grafana so that version +is `7.0+` and `max concurrent shard requests` properly configured. 256 was the default in pre v7.0 versions. In v7.0 and above 5 is the default. diff --git a/public/app/plugins/datasource/elasticsearch/config_ctrl.ts b/public/app/plugins/datasource/elasticsearch/config_ctrl.ts index adc266fa6dfe8..21204035ddb86 100644 --- a/public/app/plugins/datasource/elasticsearch/config_ctrl.ts +++ b/public/app/plugins/datasource/elasticsearch/config_ctrl.ts @@ -1,8 +1,11 @@ import _ from 'lodash'; +import { ElasticsearchOptions } from './types'; +import { DataSourceInstanceSettings } from '@grafana/ui'; +import { getMaxConcurrenShardRequestOrDefault } from './datasource'; export class ElasticConfigCtrl { static templateUrl = 'public/app/plugins/datasource/elasticsearch/partials/config.html'; - current: any; + current: DataSourceInstanceSettings; /** @ngInject */ constructor($scope) { @@ -44,4 +47,8 @@ export class ElasticConfigCtrl { this.current.database = def.example || 'es-index-name'; } } + + versionChanged() { + this.current.jsonData.maxConcurrentShardRequests = getMaxConcurrenShardRequestOrDefault(this.current.jsonData); + } } diff --git a/public/app/plugins/datasource/elasticsearch/datasource.ts b/public/app/plugins/datasource/elasticsearch/datasource.ts index ea9389bf9dc1c..dd69b7fbf3ddd 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.ts @@ -537,3 +537,16 @@ export class ElasticDatasource extends DataSourceApi= 70) { + return 5; + } + + const defaultMaxConcurrentShardRequests = options.esVersion >= 70 ? 5 : 256; + return options.maxConcurrentShardRequests || defaultMaxConcurrentShardRequests; +} diff --git a/public/app/plugins/datasource/elasticsearch/partials/config.html b/public/app/plugins/datasource/elasticsearch/partials/config.html index 431933aa7bb91..3ec80683f8b99 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/config.html +++ b/public/app/plugins/datasource/elasticsearch/partials/config.html @@ -26,7 +26,7 @@

Elasticsearch details

Version - +
diff --git a/public/app/plugins/datasource/elasticsearch/specs/datasource.test.ts b/public/app/plugins/datasource/elasticsearch/specs/datasource.test.ts index ecfb99a160596..75a34e48d52bb 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/datasource.test.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/datasource.test.ts @@ -1,7 +1,7 @@ import angular, { IQService } from 'angular'; import * as dateMath from '@grafana/ui/src/utils/datemath'; import _ from 'lodash'; -import { ElasticDatasource } from '../datasource'; +import { ElasticDatasource, getMaxConcurrenShardRequestOrDefault } from '../datasource'; import { toUtc, dateTime } from '@grafana/ui/src/utils/moment_wrapper'; import { BackendSrv } from 'app/core/services/backend_srv'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; @@ -646,3 +646,27 @@ describe('ElasticDatasource', function(this: any) { }); }); }); + +describe('getMaxConcurrenShardRequestOrDefault', () => { + const testCases = [ + { version: 50, expectedMaxConcurrentShardRequests: 256 }, + { version: 50, maxConcurrentShardRequests: 50, expectedMaxConcurrentShardRequests: 50 }, + { version: 56, expectedMaxConcurrentShardRequests: 256 }, + { version: 56, maxConcurrentShardRequests: 256, expectedMaxConcurrentShardRequests: 256 }, + { version: 56, maxConcurrentShardRequests: 5, expectedMaxConcurrentShardRequests: 256 }, + { version: 56, maxConcurrentShardRequests: 200, expectedMaxConcurrentShardRequests: 200 }, + { version: 70, expectedMaxConcurrentShardRequests: 5 }, + { version: 70, maxConcurrentShardRequests: 256, expectedMaxConcurrentShardRequests: 5 }, + { version: 70, maxConcurrentShardRequests: 5, expectedMaxConcurrentShardRequests: 5 }, + { version: 70, maxConcurrentShardRequests: 6, expectedMaxConcurrentShardRequests: 6 }, + ]; + + testCases.forEach(tc => { + it(`version = ${tc.version}, maxConcurrentShardRequests = ${tc.maxConcurrentShardRequests}`, () => { + const options = { esVersion: tc.version, maxConcurrentShardRequests: tc.maxConcurrentShardRequests }; + expect(getMaxConcurrenShardRequestOrDefault(options as ElasticsearchOptions)).toBe( + tc.expectedMaxConcurrentShardRequests + ); + }); + }); +}); From d94bdb930f3772d118970b477682a0c877185077 Mon Sep 17 00:00:00 2001 From: Eduard Sergeev Date: Mon, 1 Jul 2019 17:42:36 +1000 Subject: [PATCH 09/17] Templating: Correctly display __text in multi-values variable (#17840) Fixes #17839: __value is displayed instead of __text when single item is selected --- public/app/features/templating/specs/variable_srv.test.ts | 2 +- public/app/features/templating/variable_srv.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/templating/specs/variable_srv.test.ts b/public/app/features/templating/specs/variable_srv.test.ts index 1dcaa242dfe57..b9ab0bd887fe8 100644 --- a/public/app/features/templating/specs/variable_srv.test.ts +++ b/public/app/features/templating/specs/variable_srv.test.ts @@ -601,7 +601,7 @@ describe('VariableSrv', function(this: any) { it('sets single value as array if multi choice', async () => { const [setValueMock, setFromUrl] = setupSetFromUrlTest(ctx, { multi: true }); await setFromUrl('one'); - expect(setValueMock).toHaveBeenCalledWith({ text: 'one', value: ['one'] }); + expect(setValueMock).toHaveBeenCalledWith({ text: ['one'], value: ['one'] }); }); it('sets both text and value as array if multiple values in url', async () => { diff --git a/public/app/features/templating/variable_srv.ts b/public/app/features/templating/variable_srv.ts index d33e100f276c9..784e9c1ebd151 100644 --- a/public/app/features/templating/variable_srv.ts +++ b/public/app/features/templating/variable_srv.ts @@ -261,7 +261,7 @@ export class VariableSrv { if (variable.multi) { // In case variable is multiple choice, we cast to array to preserve the same behaviour as when selecting // the option directly, which will return even single value in an array. - option = { ...option, value: _.castArray(option.value) }; + option = { text: _.castArray(option.text), value: _.castArray(option.value) }; } return variable.setValue(option); From bd4a7ddf3a52afc1ca185a881215832243352cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 1 Jul 2019 10:16:33 +0200 Subject: [PATCH 10/17] TimePicker: Style and responsive fixes, restored dashboard settings (#17822) * TimePicker: Restore time picker settings * CleanUp: removed unuused angular-ui (calendar) components * Fix angular boot dependency list * TimePicker: set time to 23:59:00 when setting To time using calendar --- .../src/components/Select/_Select.scss | 4 + .../src/components/TimePicker/TimePicker.tsx | 4 +- .../TimePicker/TimePickerPopover.tsx | 3 + .../components/TimePicker/_TimePicker.scss | 66 +- .../src/themes/_variables.scss.tmpl.ts | 1 - public/app/app.ts | 3 - .../DashNav/DashNavTimeControls.tsx | 4 +- .../DashboardSettings/TimePickerSettings.ts | 71 + .../components/DashboardSettings/index.ts | 1 + .../app/features/explore/ExploreToolbar.tsx | 2 +- public/app/routes/GrafanaCtrl.ts | 22 - public/sass/_grafana.scss | 2 - public/sass/_old_responsive.scss | 13 - public/sass/_variables.generated.scss | 1 - public/sass/components/_navbar.scss | 2 +- public/sass/components/_timepicker.scss | 151 - public/sass/pages/_explore.scss | 15 +- public/vendor/angular-ui/ui-bootstrap-tpls.js | 1274 ------ public/vendor/jquery-ui/custom.js | 4034 ----------------- 19 files changed, 140 insertions(+), 5533 deletions(-) create mode 100644 public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.ts delete mode 100644 public/sass/components/_timepicker.scss delete mode 100644 public/vendor/angular-ui/ui-bootstrap-tpls.js delete mode 100644 public/vendor/jquery-ui/custom.js diff --git a/packages/grafana-ui/src/components/Select/_Select.scss b/packages/grafana-ui/src/components/Select/_Select.scss index e4b493055a165..f9c635053dda7 100644 --- a/packages/grafana-ui/src/components/Select/_Select.scss +++ b/packages/grafana-ui/src/components/Select/_Select.scss @@ -193,3 +193,7 @@ $select-input-bg-disabled: $input-bg-disabled; .gf-form-select-box-button-select { height: auto; } + +.select-button { + display: flex; +} diff --git a/packages/grafana-ui/src/components/TimePicker/TimePicker.tsx b/packages/grafana-ui/src/components/TimePicker/TimePicker.tsx index 454abfe4e0dd7..9195e2c824f9b 100644 --- a/packages/grafana-ui/src/components/TimePicker/TimePicker.tsx +++ b/packages/grafana-ui/src/components/TimePicker/TimePicker.tsx @@ -178,9 +178,7 @@ export class TimePicker extends PureComponent { const TimePickerTooltipContent = ({ timeRange }: { timeRange: TimeRange }) => ( <> {timeRange.from.format(TIME_FORMAT)} -
- to -
+
to
{timeRange.to.format(TIME_FORMAT)} ); diff --git a/packages/grafana-ui/src/components/TimePicker/TimePickerPopover.tsx b/packages/grafana-ui/src/components/TimePicker/TimePickerPopover.tsx index 0f255df51b0e1..2d94d4feab464 100644 --- a/packages/grafana-ui/src/components/TimePicker/TimePickerPopover.tsx +++ b/packages/grafana-ui/src/components/TimePicker/TimePickerPopover.tsx @@ -50,6 +50,9 @@ export class TimePickerPopover extends Component { }; onToCalendarChanged = (value: DateTime) => { + value.set('h', 23); + value.set('m', 59); + value.set('s', 0); this.setState({ to: value }); }; diff --git a/packages/grafana-ui/src/components/TimePicker/_TimePicker.scss b/packages/grafana-ui/src/components/TimePicker/_TimePicker.scss index cc63a2cd19683..414f996855729 100644 --- a/packages/grafana-ui/src/components/TimePicker/_TimePicker.scss +++ b/packages/grafana-ui/src/components/TimePicker/_TimePicker.scss @@ -7,10 +7,6 @@ } } -.time-picker-popover-popper { - z-index: $zindex-timepicker-popover; -} - .time-picker-utc { color: $orange; font-size: 75%; @@ -30,10 +26,11 @@ color: $popover-color; box-shadow: $popover-shadow; position: absolute; + z-index: $zindex-dropdown; flex-direction: column; max-width: 600px; - top: 48px; - right: 20px; + top: 50px; + right: 0px; .time-picker-popover-body { display: flex; @@ -188,11 +185,6 @@ $arrowPadding: $arrowPaddingToBorder * 3; @include media-breakpoint-down(md) { .time-picker-popover { - margin-left: $spacer; - display: flex; - flex-flow: column nowrap; - max-width: 400px; - .time-picker-popover-title { font-size: $font-size-md; } @@ -217,6 +209,56 @@ $arrowPadding: $arrowPaddingToBorder * 3; } .time-picker-calendar { - width: 100%; + width: 320px; + } +} + +.time-picker-button-select { + .select-button-value { + display: none; + + @include media-breakpoint-up(sm) { + display: inline-block; + max-width: 100px; + overflow: hidden; + } + + @include media-breakpoint-up(md) { + display: inline-block; + max-width: 150px; + overflow: hidden; + } + + @include media-breakpoint-up(lg) { + max-width: 300px; + } + } +} + +// special rules for when within explore toolbar in split +.explore-toolbar.splitted { + .time-picker-button-select { + .select-button-value { + display: none; + + @include media-breakpoint-up(xl) { + display: inline-block; + max-width: 100px; + } + + @media only screen and (max-width: 1545px) { + max-width: 300px; + } + } + } +} + +.dashboard-timepicker-wrapper { + position: relative; + display: flex; + + .gf-form-select-box__menu { + right: 0; + left: unset; } } diff --git a/packages/grafana-ui/src/themes/_variables.scss.tmpl.ts b/packages/grafana-ui/src/themes/_variables.scss.tmpl.ts index d4a4dd6f016f2..f8e597ae4e836 100644 --- a/packages/grafana-ui/src/themes/_variables.scss.tmpl.ts +++ b/packages/grafana-ui/src/themes/_variables.scss.tmpl.ts @@ -171,7 +171,6 @@ $zindex-tooltip: ${theme.zIndex.tooltip}; $zindex-modal-backdrop: ${theme.zIndex.modalBackdrop}; $zindex-modal: ${theme.zIndex.modal}; $zindex-typeahead: ${theme.zIndex.typeahead}; -$zindex-timepicker-popover: 1070; // Buttons // diff --git a/public/app/app.ts b/public/app/app.ts index bd819a7a43090..9bc44616f2e6f 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -11,7 +11,6 @@ import 'react'; import 'react-dom'; import 'vendor/bootstrap/bootstrap'; -import 'vendor/angular-ui/ui-bootstrap-tpls'; import 'vendor/angular-other/angular-strap'; import $ from 'jquery'; @@ -125,8 +124,6 @@ export class GrafanaApp { 'ang-drag-drop', 'grafana', 'pasvaz.bindonce', - 'ui.bootstrap', - 'ui.bootstrap.tpls', 'react', ]; diff --git a/public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx b/public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx index 401e9347ec548..6e24e6db24600 100644 --- a/public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx @@ -95,7 +95,7 @@ export class DashNavTimeControls extends Component { const timeZone = dashboard.getTimezone(); return ( - <> +
{ intervals={intervals} tooltip="Refresh dashboard" /> - +
); } } diff --git a/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.ts b/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.ts new file mode 100644 index 0000000000000..f106dfd88f28e --- /dev/null +++ b/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.ts @@ -0,0 +1,71 @@ +import coreModule from 'app/core/core_module'; +import { DashboardModel } from 'app/features/dashboard/state'; + +export class TimePickerCtrl { + panel: any; + dashboard: DashboardModel; + + constructor() { + this.panel = this.dashboard.timepicker; + this.panel.refresh_intervals = this.panel.refresh_intervals || [ + '5s', + '10s', + '30s', + '1m', + '5m', + '15m', + '30m', + '1h', + '2h', + '1d', + ]; + } +} + +const template = ` +
+
Time Options
+ +
+
+ +
+ +
+
+ +
+ Auto-refresh + +
+
+ Now delay now- + +
+ + + +
+
+`; + +export function TimePickerSettings() { + return { + restrict: 'E', + template: template, + controller: TimePickerCtrl, + bindToController: true, + controllerAs: 'ctrl', + scope: { + dashboard: '=', + }, + }; +} + +coreModule.directive('gfTimePickerSettings', TimePickerSettings); diff --git a/public/app/features/dashboard/components/DashboardSettings/index.ts b/public/app/features/dashboard/components/DashboardSettings/index.ts index 0a89feada33ab..504e62e150414 100644 --- a/public/app/features/dashboard/components/DashboardSettings/index.ts +++ b/public/app/features/dashboard/components/DashboardSettings/index.ts @@ -1,2 +1,3 @@ export { SettingsCtrl } from './SettingsCtrl'; export { DashboardSettings } from './DashboardSettings'; +export { TimePickerSettings } from './TimePickerSettings'; diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index ed8f27d561f25..1b81d42181e6e 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -198,7 +198,7 @@ export class UnConnectedExploreToolbar extends PureComponent { })}
) : null} -
+
0 && target.parents('.graph-legend').length === 0) { popover.hide(); } - - // hide time picker - const timePickerDropDownIsOpen = elem.find('.gf-timepicker-dropdown').length > 0; - if (timePickerDropDownIsOpen) { - const targetIsInTimePickerDropDown = target.parents('.gf-timepicker-dropdown').length > 0; - const targetIsInTimePickerNav = target.parents('.gf-timepicker-nav').length > 0; - const targetIsDatePickerRowBtn = target.parents('td[id^="datepicker-"]').length > 0; - const targetIsDatePickerHeaderBtn = target.parents('button[id^="datepicker-"]').length > 0; - - if ( - targetIsInTimePickerNav || - targetIsInTimePickerDropDown || - targetIsDatePickerRowBtn || - targetIsDatePickerHeaderBtn - ) { - return; - } - - scope.$apply(() => { - scope.appEvent('closeTimepicker'); - }); - } }); }, }; diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 16618da6486cd..c94d6b7de70cf 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -2,7 +2,6 @@ @import '../../node_modules/react-table/react-table.css'; // VENDOR -@import '../vendor/css/timepicker.css'; @import '../vendor/css/rc-cascader.scss'; // MIXINS @@ -67,7 +66,6 @@ @import 'components/gf-form'; @import 'components/sidemenu'; @import 'components/navbar'; -@import 'components/timepicker'; @import 'components/filter-controls'; @import 'components/filter-list'; @import 'components/filter-table'; diff --git a/public/sass/_old_responsive.scss b/public/sass/_old_responsive.scss index cfe74d03bb1d3..55423fe1f7255 100644 --- a/public/sass/_old_responsive.scss +++ b/public/sass/_old_responsive.scss @@ -6,10 +6,6 @@ max-width: 200px; } -.gf-timepicker-nav-btn { - max-width: 120px; -} - .navbar-buttons--tv, .navbar-buttons--actions { display: none; @@ -30,9 +26,6 @@ .navbar-page-btn { max-width: 250px; } - .gf-timepicker-nav-btn { - max-width: 200px; - } } @include media-breakpoint-up(md) { @@ -43,9 +36,6 @@ .navbar-page-btn { max-width: 325px; } - .gf-timepicker-nav-btn { - max-width: 240px; - } } @include media-breakpoint-up(lg) { @@ -55,9 +45,6 @@ .navbar-page-btn { max-width: 450px; } - .gf-timepicker-nav-btn { - max-width: none; - } } @include media-breakpoint-up(xl) { diff --git a/public/sass/_variables.generated.scss b/public/sass/_variables.generated.scss index 8ef586b72f949..09f87e7d8ecbc 100644 --- a/public/sass/_variables.generated.scss +++ b/public/sass/_variables.generated.scss @@ -174,7 +174,6 @@ $zindex-tooltip: 1030; $zindex-modal-backdrop: 1040; $zindex-modal: 1050; $zindex-typeahead: 1060; -$zindex-timepicker-popover: 1070; // Buttons // diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index 434ecbacb1e44..128a1e6246674 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -35,7 +35,7 @@ .navbar-button--settings, .navbar-page-btn .fa-caret-down, .refresh-picker, - .gf-timepicker-nav { + .time-picker { display: none; } diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss deleted file mode 100644 index d25d85b3d742c..0000000000000 --- a/public/sass/components/_timepicker.scss +++ /dev/null @@ -1,151 +0,0 @@ -.timepicker-timestring { - font-weight: normal; -} - -.gf-timepicker-nav { - flex-wrap: nowrap; - display: flex; -} - -.gf-timepicker-nav-btn { - text-overflow: ellipsis; - overflow: hidden; - - .fa-clock-o { - margin-right: 4px; - } -} - -.gf-timepicker-dropdown { - background-color: $page-bg; - border-radius: 0 0 0 4px; - box-shadow: $search-shadow; - z-index: $zindex-dropdown; - display: flex; - flex-direction: column; - position: absolute; - left: 20px; - right: 20px; - top: $navbarHeight; - - @include media-breakpoint-up(md) { - left: auto; - width: 550px; - } - - .popover-box { - max-width: 100%; - - &:first-child { - border-radius: $border-radius $border-radius 0 0; - border-bottom: 0; - } - - &:last-child { - border-radius: 0 0 $border-radius $border-radius; - } - } -} - -.gf-timepicker-btn-apply { - margin: 0 0 0 15px; -} - -.gf-timepicker-utc { - color: $orange; - font-size: 75%; - padding: 3px; - border-radius: 2px; - font-weight: 500; - margin-left: 4px; -} - -.gf-timepicker-relative-section { - min-height: 237px; - float: left; - - ul { - list-style: none; - float: left; - margin: 0 30px 10px 0px; - - li { - line-height: 22px; - } - - li.active { - border-bottom: 1px solid $blue; - margin: 3px 0; - font-weight: 500; - } - } -} - -.gf-timepicker-component { - padding: $spacer/2 0 $spacer 0; - - td { - padding: 1px; - } - - button { - @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl); - background-image: none; - border: none; - color: $text-color; - - &.active span { - color: $blue; - font-weight: bold; - } - - .text-info { - color: $orange; - font-weight: bold; - } - - &.btn-sm { - font-size: $font-size-sm; - padding: 5px 11px; - } - - &:hover { - color: $text-color-strong; - } - - &[disabled] { - color: $text-color; - } - } -} - -.input-datetime-format { - color: $link-color-disabled; -} - -.fa { - display: inline-block; - font: normal normal normal 14px/1 FontAwesome; - font-size: inherit; - text-rendering: auto; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.fa-chevron-left::before { - content: '\f053'; -} - -.fa-chevron-right::before { - content: '\f054'; -} - -.glyphicon-chevron-right { - @extend .fa; - @extend .fa-chevron-right; -} - -.glyphicon-chevron-left { - @extend .fa; - @extend .fa-chevron-left; -} diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index be33165ba37e1..a3c6b12351a52 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -10,14 +10,6 @@ transform: rotate(90deg); } -.timepicker { - display: flex; -} - -.timepicker-rangestring { - margin-left: 0.5em; -} - .datasource-picker { .ds-picker { min-width: 200px; @@ -91,17 +83,14 @@ display: flex; flex-flow: row wrap; align-items: center; - justify-content: space-between; + justify-content: flex-end; } .explore-toolbar-content-item { + display: flex; padding: 10px 2px; } -.explore-toolbar-content-item.timepicker { - z-index: $zindex-timepicker-popover; -} - .explore-toolbar-content-item:first-child { padding-left: $dashboard-padding; margin-right: auto; diff --git a/public/vendor/angular-ui/ui-bootstrap-tpls.js b/public/vendor/angular-ui/ui-bootstrap-tpls.js deleted file mode 100644 index ad6f3b4b4bc22..0000000000000 --- a/public/vendor/angular-ui/ui-bootstrap-tpls.js +++ /dev/null @@ -1,1274 +0,0 @@ -/* - * angular-ui-bootstrap - * http://angular-ui.github.io/bootstrap/ - - * Version: 0.13.4 - 2015-09-03 - * License: MIT - */ -angular.module("ui.bootstrap", ["ui.bootstrap.tpls","ui.bootstrap.position","ui.bootstrap.dateparser","ui.bootstrap.datepicker"]); -angular.module("ui.bootstrap.tpls", ["template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html"]); -angular.module('ui.bootstrap.position', []) - -/** - * A set of utility methods that can be use to retrieve position of DOM elements. - * It is meant to be used where we need to absolute-position DOM elements in - * relation to other, existing elements (this is the case for tooltips, popovers, - * typeahead suggestions etc.). - */ - .factory('$position', ['$document', '$window', function($document, $window) { - function getStyle(el, cssprop) { - if (el.currentStyle) { //IE - return el.currentStyle[cssprop]; - } else if ($window.getComputedStyle) { - return $window.getComputedStyle(el)[cssprop]; - } - // finally try and get inline style - return el.style[cssprop]; - } - - /** - * Checks if a given element is statically positioned - * @param element - raw DOM element - */ - function isStaticPositioned(element) { - return (getStyle(element, 'position') || 'static' ) === 'static'; - } - - /** - * returns the closest, non-statically positioned parentOffset of a given element - * @param element - */ - var parentOffsetEl = function(element) { - var docDomEl = $document[0]; - var offsetParent = element.offsetParent || docDomEl; - while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { - offsetParent = offsetParent.offsetParent; - } - return offsetParent || docDomEl; - }; - - return { - /** - * Provides read-only equivalent of jQuery's position function: - * http://api.jquery.com/position/ - */ - position: function(element) { - var elBCR = this.offset(element); - var offsetParentBCR = { top: 0, left: 0 }; - var offsetParentEl = parentOffsetEl(element[0]); - if (offsetParentEl != $document[0]) { - offsetParentBCR = this.offset(angular.element(offsetParentEl)); - offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; - offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; - } - - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: boundingClientRect.width || element.prop('offsetWidth'), - height: boundingClientRect.height || element.prop('offsetHeight'), - top: elBCR.top - offsetParentBCR.top, - left: elBCR.left - offsetParentBCR.left - }; - }, - - /** - * Provides read-only equivalent of jQuery's offset function: - * http://api.jquery.com/offset/ - */ - offset: function(element) { - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: boundingClientRect.width || element.prop('offsetWidth'), - height: boundingClientRect.height || element.prop('offsetHeight'), - top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), - left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) - }; - }, - - /** - * Provides coordinates for the targetEl in relation to hostEl - */ - positionElements: function(hostEl, targetEl, positionStr, appendToBody) { - var positionStrParts = positionStr.split('-'); - var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; - - var hostElPos, - targetElWidth, - targetElHeight, - targetElPos; - - hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); - - targetElWidth = targetEl.prop('offsetWidth'); - targetElHeight = targetEl.prop('offsetHeight'); - - var shiftWidth = { - center: function() { - return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; - }, - left: function() { - return hostElPos.left; - }, - right: function() { - return hostElPos.left + hostElPos.width; - } - }; - - var shiftHeight = { - center: function() { - return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; - }, - top: function() { - return hostElPos.top; - }, - bottom: function() { - return hostElPos.top + hostElPos.height; - } - }; - - switch (pos0) { - case 'right': - targetElPos = { - top: shiftHeight[pos1](), - left: shiftWidth[pos0]() - }; - break; - case 'left': - targetElPos = { - top: shiftHeight[pos1](), - left: hostElPos.left - targetElWidth - }; - break; - case 'bottom': - targetElPos = { - top: shiftHeight[pos0](), - left: shiftWidth[pos1]() - }; - break; - default: - targetElPos = { - top: hostElPos.top - targetElHeight, - left: shiftWidth[pos1]() - }; - break; - } - - return targetElPos; - } - }; - }]); - -angular.module('ui.bootstrap.dateparser', []) - -.service('dateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) { - // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js - var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; - - this.parsers = {}; - - var formatCodeToRegex = { - 'yyyy': { - regex: '\\d{4}', - apply: function(value) { this.year = +value; } - }, - 'yy': { - regex: '\\d{2}', - apply: function(value) { this.year = +value + 2000; } - }, - 'y': { - regex: '\\d{1,4}', - apply: function(value) { this.year = +value; } - }, - 'MMMM': { - regex: $locale.DATETIME_FORMATS.MONTH.join('|'), - apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } - }, - 'MMM': { - regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), - apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } - }, - 'MM': { - regex: '0[1-9]|1[0-2]', - apply: function(value) { this.month = value - 1; } - }, - 'M': { - regex: '[1-9]|1[0-2]', - apply: function(value) { this.month = value - 1; } - }, - 'dd': { - regex: '[0-2][0-9]{1}|3[0-1]{1}', - apply: function(value) { this.date = +value; } - }, - 'd': { - regex: '[1-2]?[0-9]{1}|3[0-1]{1}', - apply: function(value) { this.date = +value; } - }, - 'EEEE': { - regex: $locale.DATETIME_FORMATS.DAY.join('|') - }, - 'EEE': { - regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') - }, - 'HH': { - regex: '(?:0|1)[0-9]|2[0-3]', - apply: function(value) { this.hours = +value; } - }, - 'hh': { - regex: '0[0-9]|1[0-2]', - apply: function(value) { this.hours = +value; } - }, - 'H': { - regex: '1?[0-9]|2[0-3]', - apply: function(value) { this.hours = +value; } - }, - 'h': { - regex: '[0-9]|1[0-2]', - apply: function(value) { this.hours = +value; } - }, - 'mm': { - regex: '[0-5][0-9]', - apply: function(value) { this.minutes = +value; } - }, - 'm': { - regex: '[0-9]|[1-5][0-9]', - apply: function(value) { this.minutes = +value; } - }, - 'sss': { - regex: '[0-9][0-9][0-9]', - apply: function(value) { this.milliseconds = +value; } - }, - 'ss': { - regex: '[0-5][0-9]', - apply: function(value) { this.seconds = +value; } - }, - 's': { - regex: '[0-9]|[1-5][0-9]', - apply: function(value) { this.seconds = +value; } - }, - 'a': { - regex: $locale.DATETIME_FORMATS.AMPMS.join('|'), - apply: function(value) { - if (this.hours === 12) { - this.hours = 0; - } - - if (value === 'PM') { - this.hours += 12; - } - } - } - }; - - function createParser(format) { - var map = [], regex = format.split(''); - - angular.forEach(formatCodeToRegex, function(data, code) { - var index = format.indexOf(code); - - if (index > -1) { - format = format.split(''); - - regex[index] = '(' + data.regex + ')'; - format[index] = '$'; // Custom symbol to define consumed part of format - for (var i = index + 1, n = index + code.length; i < n; i++) { - regex[i] = ''; - format[i] = '$'; - } - format = format.join(''); - - map.push({ index: index, apply: data.apply }); - } - }); - - return { - regex: new RegExp('^' + regex.join('') + '$'), - map: orderByFilter(map, 'index') - }; - } - - this.parse = function(input, format, baseDate) { - if (!angular.isString(input) || !format) { - return input; - } - - format = $locale.DATETIME_FORMATS[format] || format; - format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&'); - - if (!this.parsers[format]) { - this.parsers[format] = createParser(format); - } - - var parser = this.parsers[format], - regex = parser.regex, - map = parser.map, - results = input.match(regex); - - if (results && results.length) { - var fields, dt; - if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) { - fields = { - year: baseDate.getFullYear(), - month: baseDate.getMonth(), - date: baseDate.getDate(), - hours: baseDate.getHours(), - minutes: baseDate.getMinutes(), - seconds: baseDate.getSeconds(), - milliseconds: baseDate.getMilliseconds() - }; - } else { - if (baseDate) { - $log.warn('dateparser:', 'baseDate is not a valid date'); - } - fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }; - } - - for (var i = 1, n = results.length; i < n; i++) { - var mapper = map[i-1]; - if (mapper.apply) { - mapper.apply.call(fields, results[i]); - } - } - - if (isValid(fields.year, fields.month, fields.date)) { - dt = new Date(fields.year, fields.month, fields.date, - fields.hours, fields.minutes, fields.seconds, - fields.milliseconds || 0); - } - - return dt; - } - }; - - // Check if date is valid for specific month (and year for February). - // Month: 0 = Jan, 1 = Feb, etc - function isValid(year, month, date) { - if (date < 1) { - return false; - } - - if (month === 1 && date > 28) { - return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); - } - - if (month === 3 || month === 5 || month === 8 || month === 10) { - return date < 31; - } - - return true; - } -}]); - -angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position']) - -.value('$datepickerSuppressError', false) - -.constant('datepickerConfig', { - formatDay: 'dd', - formatMonth: 'MMMM', - formatYear: 'yyyy', - formatDayHeader: 'EEE', - formatDayTitle: 'MMMM yyyy', - formatMonthTitle: 'yyyy', - datepickerMode: 'day', - minMode: 'day', - maxMode: 'year', - showWeeks: true, - startingDay: 0, - yearRange: 20, - minDate: null, - maxDate: null, - shortcutPropagation: false -}) - -.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'datepickerConfig', '$datepickerSuppressError', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError) { - var self = this, - ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; - - // Modes chain - this.modes = ['day', 'month', 'year']; - - // Configuration attributes - angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', - 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) { - self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; - }); - - // Watchable date attributes - angular.forEach(['minDate', 'maxDate'], function(key) { - if ($attrs[key]) { - $scope.$parent.$watch($parse($attrs[key]), function(value) { - self[key] = value ? new Date(value) : null; - self.refreshView(); - }); - } else { - self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; - } - }); - - angular.forEach(['minMode', 'maxMode'], function(key) { - if ($attrs[key]) { - $scope.$parent.$watch($parse($attrs[key]), function(value) { - self[key] = angular.isDefined(value) ? value : $attrs[key]; - $scope[key] = self[key]; - if ((key == 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key])) || (key == 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key]))) { - $scope.datepickerMode = self[key]; - } - }); - } else { - self[key] = datepickerConfig[key] || null; - $scope[key] = self[key]; - } - }); - - $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; - $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); - - if (angular.isDefined($attrs.initDate)) { - this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date(); - $scope.$parent.$watch($attrs.initDate, function(initDate) { - if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { - self.activeDate = initDate; - self.refreshView(); - } - }); - } else { - this.activeDate = new Date(); - } - - $scope.isActive = function(dateObject) { - if (self.compare(dateObject.date, self.activeDate) === 0) { - $scope.activeDateId = dateObject.uid; - return true; - } - return false; - }; - - this.init = function(ngModelCtrl_) { - ngModelCtrl = ngModelCtrl_; - - ngModelCtrl.$render = function() { - self.render(); - }; - }; - - this.render = function() { - if (ngModelCtrl.$viewValue) { - var date = new Date(ngModelCtrl.$viewValue), - isValid = !isNaN(date); - - if (isValid) { - this.activeDate = date; - } else if (!$datepickerSuppressError) { - $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); - } - } - this.refreshView(); - }; - - this.refreshView = function() { - if (this.element) { - this._refreshView(); - - var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; - ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date))); - } - }; - - this.createDateObject = function(date, format) { - var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; - return { - date: date, - label: dateFilter(date, format), - selected: model && this.compare(date, model) === 0, - disabled: this.isDisabled(date), - current: this.compare(date, new Date()) === 0, - customClass: this.customClass(date) - }; - }; - - this.isDisabled = function(date) { - return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}))); - }; - - this.customClass = function(date) { - return $scope.customClass({date: date, mode: $scope.datepickerMode}); - }; - - // Split array into smaller arrays - this.split = function(arr, size) { - var arrays = []; - while (arr.length > 0) { - arrays.push(arr.splice(0, size)); - } - return arrays; - }; - - // Fix a hard-reprodusible bug with timezones - // The bug depends on OS, browser, current timezone and current date - // i.e. - // var date = new Date(2014, 0, 1); - // console.log(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours()); - // can result in "2013 11 31 23" because of the bug. - this.fixTimeZone = function(date) { - var hours = date.getHours(); - date.setHours(hours === 23 ? hours + 2 : 0); - }; - - $scope.select = function(date) { - if ($scope.datepickerMode === self.minMode) { - var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0); - dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); - ngModelCtrl.$setViewValue(dt); - ngModelCtrl.$render(); - } else { - self.activeDate = date; - $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1]; - } - }; - - $scope.move = function(direction) { - var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), - month = self.activeDate.getMonth() + direction * (self.step.months || 0); - self.activeDate.setFullYear(year, month, 1); - self.refreshView(); - }; - - $scope.toggleMode = function(direction) { - direction = direction || 1; - - if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) { - return; - } - - $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction]; - }; - - // Key event mapper - $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' }; - - var focusElement = function() { - self.element[0].focus(); - }; - - // Listen for focus requests from popup directive - $scope.$on('datepicker.focus', focusElement); - - $scope.keydown = function(evt) { - var key = $scope.keys[evt.which]; - - if (!key || evt.shiftKey || evt.altKey) { - return; - } - - evt.preventDefault(); - if (!self.shortcutPropagation) { - evt.stopPropagation(); - } - - if (key === 'enter' || key === 'space') { - if (self.isDisabled(self.activeDate)) { - return; // do nothing - } - $scope.select(self.activeDate); - focusElement(); - } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { - $scope.toggleMode(key === 'up' ? 1 : -1); - focusElement(); - } else { - self.handleKeyDown(key, evt); - self.refreshView(); - } - }; -}]) - -.directive('datepicker', function() { - return { - restrict: 'EA', - replace: true, - templateUrl: function(element, attrs) { - return attrs.templateUrl || 'template/datepicker/datepicker.html'; - }, - scope: { - datepickerMode: '=?', - dateDisabled: '&', - customClass: '&', - shortcutPropagation: '&?' - }, - require: ['datepicker', '^ngModel'], - controller: 'DatepickerController', - controllerAs: 'datepicker', - link: function(scope, element, attrs, ctrls) { - var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - - datepickerCtrl.init(ngModelCtrl); - } - }; -}) - -.directive('daypicker', ['dateFilter', function(dateFilter) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/datepicker/day.html', - require: '^datepicker', - link: function(scope, element, attrs, ctrl) { - scope.showWeeks = ctrl.showWeeks; - - ctrl.step = { months: 1 }; - ctrl.element = element; - - var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - function getDaysInMonth(year, month) { - return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; - } - - function getDates(startDate, n) { - var dates = new Array(n), current = new Date(startDate), i = 0, date; - while (i < n) { - date = new Date(current); - ctrl.fixTimeZone(date); - dates[i++] = date; - current.setDate(current.getDate() + 1); - } - return dates; - } - - ctrl._refreshView = function() { - var year = ctrl.activeDate.getFullYear(), - month = ctrl.activeDate.getMonth(), - firstDayOfMonth = new Date(year, month, 1), - difference = ctrl.startingDay - firstDayOfMonth.getDay(), - numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, - firstDate = new Date(firstDayOfMonth); - - if (numDisplayedFromPreviousMonth > 0) { - firstDate.setDate(-numDisplayedFromPreviousMonth + 1); - } - - // 42 is the number of days on a six-month calendar - var days = getDates(firstDate, 42); - for (var i = 0; i < 42; i ++) { - days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), { - secondary: days[i].getMonth() !== month, - uid: scope.uniqueId + '-' + i - }); - } - - scope.labels = new Array(7); - for (var j = 0; j < 7; j++) { - scope.labels[j] = { - abbr: dateFilter(days[j].date, ctrl.formatDayHeader), - full: dateFilter(days[j].date, 'EEEE') - }; - } - - scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle); - scope.rows = ctrl.split(days, 7); - - if (scope.showWeeks) { - scope.weekNumbers = []; - var thursdayIndex = (4 + 7 - ctrl.startingDay) % 7, - numWeeks = scope.rows.length; - for (var curWeek = 0; curWeek < numWeeks; curWeek++) { - scope.weekNumbers.push( - getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date)); - } - } - }; - - ctrl.compare = function(date1, date2) { - return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate())); - }; - - function getISO8601WeekNumber(date) { - var checkDate = new Date(date); - checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday - var time = checkDate.getTime(); - checkDate.setMonth(0); // Compare with Jan 1 - checkDate.setDate(1); - return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; - } - - ctrl.handleKeyDown = function(key, evt) { - var date = ctrl.activeDate.getDate(); - - if (key === 'left') { - date = date - 1; // up - } else if (key === 'up') { - date = date - 7; // down - } else if (key === 'right') { - date = date + 1; // down - } else if (key === 'down') { - date = date + 7; - } else if (key === 'pageup' || key === 'pagedown') { - var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); - ctrl.activeDate.setMonth(month, 1); - date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date); - } else if (key === 'home') { - date = 1; - } else if (key === 'end') { - date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()); - } - ctrl.activeDate.setDate(date); - }; - - ctrl.refreshView(); - } - }; -}]) - -.directive('monthpicker', ['dateFilter', function(dateFilter) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/datepicker/month.html', - require: '^datepicker', - link: function(scope, element, attrs, ctrl) { - ctrl.step = { years: 1 }; - ctrl.element = element; - - ctrl._refreshView = function() { - var months = new Array(12), - year = ctrl.activeDate.getFullYear(), - date; - - for (var i = 0; i < 12; i++) { - date = new Date(year, i, 1); - ctrl.fixTimeZone(date); - months[i] = angular.extend(ctrl.createDateObject(date, ctrl.formatMonth), { - uid: scope.uniqueId + '-' + i - }); - } - - scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle); - scope.rows = ctrl.split(months, 3); - }; - - ctrl.compare = function(date1, date2) { - return new Date(date1.getFullYear(), date1.getMonth()) - new Date(date2.getFullYear(), date2.getMonth()); - }; - - ctrl.handleKeyDown = function(key, evt) { - var date = ctrl.activeDate.getMonth(); - - if (key === 'left') { - date = date - 1; // up - } else if (key === 'up') { - date = date - 3; // down - } else if (key === 'right') { - date = date + 1; // down - } else if (key === 'down') { - date = date + 3; - } else if (key === 'pageup' || key === 'pagedown') { - var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); - ctrl.activeDate.setFullYear(year); - } else if (key === 'home') { - date = 0; - } else if (key === 'end') { - date = 11; - } - ctrl.activeDate.setMonth(date); - }; - - ctrl.refreshView(); - } - }; -}]) - -.directive('yearpicker', ['dateFilter', function(dateFilter) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/datepicker/year.html', - require: '^datepicker', - link: function(scope, element, attrs, ctrl) { - var range = ctrl.yearRange; - - ctrl.step = { years: range }; - ctrl.element = element; - - function getStartingYear( year ) { - return parseInt((year - 1) / range, 10) * range + 1; - } - - ctrl._refreshView = function() { - var years = new Array(range), date; - - for (var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++) { - date = new Date(start + i, 0, 1); - ctrl.fixTimeZone(date); - years[i] = angular.extend(ctrl.createDateObject(date, ctrl.formatYear), { - uid: scope.uniqueId + '-' + i - }); - } - - scope.title = [years[0].label, years[range - 1].label].join(' - '); - scope.rows = ctrl.split(years, 5); - }; - - ctrl.compare = function(date1, date2) { - return date1.getFullYear() - date2.getFullYear(); - }; - - ctrl.handleKeyDown = function(key, evt) { - var date = ctrl.activeDate.getFullYear(); - - if (key === 'left') { - date = date - 1; // up - } else if (key === 'up') { - date = date - 5; // down - } else if (key === 'right') { - date = date + 1; // down - } else if (key === 'down') { - date = date + 5; - } else if (key === 'pageup' || key === 'pagedown') { - date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years; - } else if (key === 'home') { - date = getStartingYear(ctrl.activeDate.getFullYear()); - } else if (key === 'end') { - date = getStartingYear(ctrl.activeDate.getFullYear()) + range - 1; - } - ctrl.activeDate.setFullYear(date); - }; - - ctrl.refreshView(); - } - }; -}]) - -.constant('datepickerPopupConfig', { - datepickerPopup: 'yyyy-MM-dd', - datepickerPopupTemplateUrl: 'template/datepicker/popup.html', - datepickerTemplateUrl: 'template/datepicker/datepicker.html', - html5Types: { - date: 'yyyy-MM-dd', - 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss', - 'month': 'yyyy-MM' - }, - currentText: 'Today', - clearText: 'Clear', - closeText: 'Done', - closeOnDateSelection: true, - appendToBody: false, - showButtonBar: true, - onOpenFocus: true -}) - -.directive('datepickerPopup', ['$compile', '$parse', '$document', '$rootScope', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', '$timeout', -function($compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) { - return { - restrict: 'EA', - require: 'ngModel', - scope: { - isOpen: '=?', - currentText: '@', - clearText: '@', - closeText: '@', - dateDisabled: '&', - customClass: '&' - }, - link: function(scope, element, attrs, ngModel) { - var dateFormat, - closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, - appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody, - onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus, - datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl, - datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl, - cache = {}; - - scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; - - scope.getText = function(key) { - return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; - }; - - scope.isDisabled = function(date) { - if (date === 'today') { - date = new Date(); - } - - return ((scope.watchData.minDate && scope.compare(date, cache.minDate) < 0) || - (scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0)); - }; - - scope.compare = function(date1, date2) { - return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate())); - }; - - var isHtml5DateInput = false; - if (datepickerPopupConfig.html5Types[attrs.type]) { - dateFormat = datepickerPopupConfig.html5Types[attrs.type]; - isHtml5DateInput = true; - } else { - dateFormat = attrs.datepickerPopup || datepickerPopupConfig.datepickerPopup; - attrs.$observe('datepickerPopup', function(value, oldValue) { - var newDateFormat = value || datepickerPopupConfig.datepickerPopup; - // Invalidate the $modelValue to ensure that formatters re-run - // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764 - if (newDateFormat !== dateFormat) { - dateFormat = newDateFormat; - ngModel.$modelValue = null; - - if (!dateFormat) { - throw new Error('datepickerPopup must have a date format specified.'); - } - } - }); - } - - if (!dateFormat) { - throw new Error('datepickerPopup must have a date format specified.'); - } - - if (isHtml5DateInput && attrs.datepickerPopup) { - throw new Error('HTML5 date input types do not support custom formats.'); - } - - // popup element used to display calendar - var popupEl = angular.element('
'); - popupEl.attr({ - 'ng-model': 'date', - 'ng-change': 'dateSelection(date)', - 'template-url': datepickerPopupTemplateUrl - }); - - function cameltoDash(string) { - return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); - } - - // datepicker element - var datepickerEl = angular.element(popupEl.children()[0]); - datepickerEl.attr('template-url', datepickerTemplateUrl); - - if (isHtml5DateInput) { - if (attrs.type === 'month') { - datepickerEl.attr('datepicker-mode', '"month"'); - datepickerEl.attr('min-mode', 'month'); - } - } - - if (attrs.datepickerOptions) { - var options = scope.$parent.$eval(attrs.datepickerOptions); - if (options && options.initDate) { - scope.initDate = options.initDate; - datepickerEl.attr('init-date', 'initDate'); - delete options.initDate; - } - angular.forEach(options, function(value, option) { - datepickerEl.attr( cameltoDash(option), value ); - }); - } - - scope.watchData = {}; - angular.forEach(['minMode', 'maxMode', 'minDate', 'maxDate', 'datepickerMode', 'initDate', 'shortcutPropagation'], function(key) { - if (attrs[key]) { - var getAttribute = $parse(attrs[key]); - scope.$parent.$watch(getAttribute, function(value) { - scope.watchData[key] = value; - if (key === 'minDate' || key === 'maxDate') { - cache[key] = new Date(value); - } - }); - datepickerEl.attr(cameltoDash(key), 'watchData.' + key); - - // Propagate changes from datepicker to outside - if (key === 'datepickerMode') { - var setAttribute = getAttribute.assign; - scope.$watch('watchData.' + key, function(value, oldvalue) { - if (angular.isFunction(setAttribute) && value !== oldvalue) { - setAttribute(scope.$parent, value); - } - }); - } - } - }); - if (attrs.dateDisabled) { - datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); - } - - if (attrs.showWeeks) { - datepickerEl.attr('show-weeks', attrs.showWeeks); - } - - if (attrs.customClass) { - datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })'); - } - - function parseDate(viewValue) { - if (angular.isNumber(viewValue)) { - // presumably timestamp to date object - viewValue = new Date(viewValue); - } - - if (!viewValue) { - return null; - } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { - return viewValue; - } else if (angular.isString(viewValue)) { - var date = dateParser.parse(viewValue, dateFormat, scope.date); - if (isNaN(date)) { - return undefined; - } else { - return date; - } - } else { - return undefined; - } - } - - function validator(modelValue, viewValue) { - var value = modelValue || viewValue; - - if (!attrs.ngRequired && !value) { - return true; - } - - if (angular.isNumber(value)) { - value = new Date(value); - } - if (!value) { - return true; - } else if (angular.isDate(value) && !isNaN(value)) { - return true; - } else if (angular.isString(value)) { - var date = dateParser.parse(value, dateFormat); - return !isNaN(date); - } else { - return false; - } - } - - if (!isHtml5DateInput) { - // Internal API to maintain the correct ng-invalid-[key] class - ngModel.$$parserName = 'date'; - ngModel.$validators.date = validator; - ngModel.$parsers.unshift(parseDate); - ngModel.$formatters.push(function(value) { - scope.date = value; - return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat); - }); - } else { - ngModel.$formatters.push(function(value) { - scope.date = value; - return value; - }); - } - - // Inner change - scope.dateSelection = function(dt) { - if (angular.isDefined(dt)) { - scope.date = dt; - } - var date = scope.date ? dateFilter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function - element.val(date); - ngModel.$setViewValue(date); - - if (closeOnDateSelection) { - scope.isOpen = false; - element[0].focus(); - } - }; - - // Detect changes in the view from the text box - ngModel.$viewChangeListeners.push(function() { - scope.date = dateParser.parse(ngModel.$viewValue, dateFormat, scope.date); - }); - - var documentClickBind = function(event) { - if (scope.isOpen && !(element[0].contains(event.target) || popupEl[0].contains(event.target))) { - scope.$apply(function() { - scope.isOpen = false; - }); - } - }; - - var inputKeydownBind = function(evt) { - if (evt.which === 27 && scope.isOpen) { - evt.preventDefault(); - evt.stopPropagation(); - scope.$apply(function() { - scope.isOpen = false; - }); - element[0].focus(); - } else if (evt.which === 40 && !scope.isOpen) { - evt.preventDefault(); - evt.stopPropagation(); - scope.$apply(function() { - scope.isOpen = true; - }); - } - }; - element.bind('keydown', inputKeydownBind); - - scope.keydown = function(evt) { - if (evt.which === 27) { - scope.isOpen = false; - element[0].focus(); - } - }; - - scope.$watch('isOpen', function(value) { - if (value) { - scope.position = appendToBody ? $position.offset(element) : $position.position(element); - scope.position.top = scope.position.top + element.prop('offsetHeight'); - - $timeout(function() { - if (onOpenFocus) { - scope.$broadcast('datepicker.focus'); - } - $document.bind('click', documentClickBind); - }, 0, false); - } else { - $document.unbind('click', documentClickBind); - } - }); - - scope.select = function(date) { - if (date === 'today') { - var today = new Date(); - if (angular.isDate(scope.date)) { - date = new Date(scope.date); - date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); - } else { - date = new Date(today.setHours(0, 0, 0, 0)); - } - } - scope.dateSelection(date); - }; - - scope.close = function() { - scope.isOpen = false; - element[0].focus(); - }; - - var $popup = $compile(popupEl)(scope); - // Prevent jQuery cache memory leak (template is now redundant after linking) - popupEl.remove(); - - if (appendToBody) { - $document.find('body').append($popup); - } else { - element.after($popup); - } - - scope.$on('$destroy', function() { - if (scope.isOpen === true) { - if (!$rootScope.$$phase) { - scope.$apply(function() { - scope.isOpen = false; - }); - } - } - - $popup.remove(); - element.unbind('keydown', inputKeydownBind); - $document.unbind('click', documentClickBind); - }); - } - }; -}]) - -.directive('datepickerPopupWrap', function() { - return { - restrict:'EA', - replace: true, - transclude: true, - templateUrl: function(element, attrs) { - return attrs.templateUrl || 'template/datepicker/popup.html'; - } - }; -}); - - -angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/datepicker/datepicker.html", - "
\n" + - " \n" + - " \n" + - " \n" + - "
"); -}]); - -angular.module("template/datepicker/day.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/datepicker/day.html", - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
{{::label.abbr}}
{{ weekNumbers[$index] }}\n" + - " \n" + - "
\n" + - ""); -}]); - -angular.module("template/datepicker/month.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/datepicker/month.html", - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
\n" + - " \n" + - "
\n" + - ""); -}]); - -angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/datepicker/popup.html", - "
    \n" + - "
  • \n" + - "
  • \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
  • \n" + - "
\n" + - ""); -}]); - -angular.module("template/datepicker/year.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/datepicker/year.html", - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
\n" + - " \n" + - "
\n" + - ""); -}]); - diff --git a/public/vendor/jquery-ui/custom.js b/public/vendor/jquery-ui/custom.js deleted file mode 100644 index aa3e5d8b6bafb..0000000000000 --- a/public/vendor/jquery-ui/custom.js +++ /dev/null @@ -1,4034 +0,0 @@ -/*! jQuery UI - v1.12.1 - 2017-06-13 -* http://jqueryui.com -* Includes: widget.js, data.js, disable-selection.js, scroll-parent.js, widgets/draggable.js, widgets/droppable.js, widgets/resizable.js, widgets/mouse.js -* Copyright jQuery Foundation and other contributors; Licensed MIT */ - -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - - // AMD. Register as an anonymous module. - define([ "jquery" ], factory ); - } else { - - // Browser globals - factory( jQuery ); - } -}(function( $ ) { - -$.ui = $.ui || {}; - -var version = $.ui.version = "1.12.1"; - - -/*! - * jQuery UI Widget 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: Widget -//>>group: Core -//>>description: Provides a factory for creating stateful widgets with a common API. -//>>docs: http://api.jqueryui.com/jQuery.widget/ -//>>demos: http://jqueryui.com/widget/ - - - -var widgetUuid = 0; -var widgetSlice = Array.prototype.slice; - -$.cleanData = ( function( orig ) { - return function( elems ) { - var events, elem, i; - for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { - try { - - // Only trigger remove when necessary to save time - events = $._data( elem, "events" ); - if ( events && events.remove ) { - $( elem ).triggerHandler( "remove" ); - } - - // Http://bugs.jquery.com/ticket/8235 - } catch ( e ) {} - } - orig( elems ); - }; -} )( $.cleanData ); - -$.widget = function( name, base, prototype ) { - var existingConstructor, constructor, basePrototype; - - // ProxiedPrototype allows the provided prototype to remain unmodified - // so that it can be used as a mixin for multiple widgets (#8876) - var proxiedPrototype = {}; - - var namespace = name.split( "." )[ 0 ]; - name = name.split( "." )[ 1 ]; - var fullName = namespace + "-" + name; - - if ( !prototype ) { - prototype = base; - base = $.Widget; - } - - if ( $.isArray( prototype ) ) { - prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); - } - - // Create selector for plugin - $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { - return !!$.data( elem, fullName ); - }; - - $[ namespace ] = $[ namespace ] || {}; - existingConstructor = $[ namespace ][ name ]; - constructor = $[ namespace ][ name ] = function( options, element ) { - - // Allow instantiation without "new" keyword - if ( !this._createWidget ) { - return new constructor( options, element ); - } - - // Allow instantiation without initializing for simple inheritance - // must use "new" keyword (the code above always passes args) - if ( arguments.length ) { - this._createWidget( options, element ); - } - }; - - // Extend with the existing constructor to carry over any static properties - $.extend( constructor, existingConstructor, { - version: prototype.version, - - // Copy the object used to create the prototype in case we need to - // redefine the widget later - _proto: $.extend( {}, prototype ), - - // Track widgets that inherit from this widget in case this widget is - // redefined after a widget inherits from it - _childConstructors: [] - } ); - - basePrototype = new base(); - - // We need to make the options hash a property directly on the new instance - // otherwise we'll modify the options hash on the prototype that we're - // inheriting from - basePrototype.options = $.widget.extend( {}, basePrototype.options ); - $.each( prototype, function( prop, value ) { - if ( !$.isFunction( value ) ) { - proxiedPrototype[ prop ] = value; - return; - } - proxiedPrototype[ prop ] = ( function() { - function _super() { - return base.prototype[ prop ].apply( this, arguments ); - } - - function _superApply( args ) { - return base.prototype[ prop ].apply( this, args ); - } - - return function() { - var __super = this._super; - var __superApply = this._superApply; - var returnValue; - - this._super = _super; - this._superApply = _superApply; - - returnValue = value.apply( this, arguments ); - - this._super = __super; - this._superApply = __superApply; - - return returnValue; - }; - } )(); - } ); - constructor.prototype = $.widget.extend( basePrototype, { - - // TODO: remove support for widgetEventPrefix - // always use the name + a colon as the prefix, e.g., draggable:start - // don't prefix for widgets that aren't DOM-based - widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name - }, proxiedPrototype, { - constructor: constructor, - namespace: namespace, - widgetName: name, - widgetFullName: fullName - } ); - - // If this widget is being redefined then we need to find all widgets that - // are inheriting from it and redefine all of them so that they inherit from - // the new version of this widget. We're essentially trying to replace one - // level in the prototype chain. - if ( existingConstructor ) { - $.each( existingConstructor._childConstructors, function( i, child ) { - var childPrototype = child.prototype; - - // Redefine the child widget using the same prototype that was - // originally used, but inherit from the new version of the base - $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, - child._proto ); - } ); - - // Remove the list of existing child constructors from the old constructor - // so the old child constructors can be garbage collected - delete existingConstructor._childConstructors; - } else { - base._childConstructors.push( constructor ); - } - - $.widget.bridge( name, constructor ); - - return constructor; -}; - -$.widget.extend = function( target ) { - var input = widgetSlice.call( arguments, 1 ); - var inputIndex = 0; - var inputLength = input.length; - var key; - var value; - - for ( ; inputIndex < inputLength; inputIndex++ ) { - for ( key in input[ inputIndex ] ) { - value = input[ inputIndex ][ key ]; - if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { - - // Clone objects - if ( $.isPlainObject( value ) ) { - target[ key ] = $.isPlainObject( target[ key ] ) ? - $.widget.extend( {}, target[ key ], value ) : - - // Don't extend strings, arrays, etc. with objects - $.widget.extend( {}, value ); - - // Copy everything else by reference - } else { - target[ key ] = value; - } - } - } - } - return target; -}; - -$.widget.bridge = function( name, object ) { - var fullName = object.prototype.widgetFullName || name; - $.fn[ name ] = function( options ) { - var isMethodCall = typeof options === "string"; - var args = widgetSlice.call( arguments, 1 ); - var returnValue = this; - - if ( isMethodCall ) { - - // If this is an empty collection, we need to have the instance method - // return undefined instead of the jQuery instance - if ( !this.length && options === "instance" ) { - returnValue = undefined; - } else { - this.each( function() { - var methodValue; - var instance = $.data( this, fullName ); - - if ( options === "instance" ) { - returnValue = instance; - return false; - } - - if ( !instance ) { - return $.error( "cannot call methods on " + name + - " prior to initialization; " + - "attempted to call method '" + options + "'" ); - } - - if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { - return $.error( "no such method '" + options + "' for " + name + - " widget instance" ); - } - - methodValue = instance[ options ].apply( instance, args ); - - if ( methodValue !== instance && methodValue !== undefined ) { - returnValue = methodValue && methodValue.jquery ? - returnValue.pushStack( methodValue.get() ) : - methodValue; - return false; - } - } ); - } - } else { - - // Allow multiple hashes to be passed on init - if ( args.length ) { - options = $.widget.extend.apply( null, [ options ].concat( args ) ); - } - - this.each( function() { - var instance = $.data( this, fullName ); - if ( instance ) { - instance.option( options || {} ); - if ( instance._init ) { - instance._init(); - } - } else { - $.data( this, fullName, new object( options, this ) ); - } - } ); - } - - return returnValue; - }; -}; - -$.Widget = function( /* options, element */ ) {}; -$.Widget._childConstructors = []; - -$.Widget.prototype = { - widgetName: "widget", - widgetEventPrefix: "", - defaultElement: "
", - - options: { - classes: {}, - disabled: false, - - // Callbacks - create: null - }, - - _createWidget: function( options, element ) { - element = $( element || this.defaultElement || this )[ 0 ]; - this.element = $( element ); - this.uuid = widgetUuid++; - this.eventNamespace = "." + this.widgetName + this.uuid; - - this.bindings = $(); - this.hoverable = $(); - this.focusable = $(); - this.classesElementLookup = {}; - - if ( element !== this ) { - $.data( element, this.widgetFullName, this ); - this._on( true, this.element, { - remove: function( event ) { - if ( event.target === element ) { - this.destroy(); - } - } - } ); - this.document = $( element.style ? - - // Element within the document - element.ownerDocument : - - // Element is window or document - element.document || element ); - this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow ); - } - - this.options = $.widget.extend( {}, - this.options, - this._getCreateOptions(), - options ); - - this._create(); - - if ( this.options.disabled ) { - this._setOptionDisabled( this.options.disabled ); - } - - this._trigger( "create", null, this._getCreateEventData() ); - this._init(); - }, - - _getCreateOptions: function() { - return {}; - }, - - _getCreateEventData: $.noop, - - _create: $.noop, - - _init: $.noop, - - destroy: function() { - var that = this; - - this._destroy(); - $.each( this.classesElementLookup, function( key, value ) { - that._removeClass( value, key ); - } ); - - // We can probably remove the unbind calls in 2.0 - // all event bindings should go through this._on() - this.element - .off( this.eventNamespace ) - .removeData( this.widgetFullName ); - this.widget() - .off( this.eventNamespace ) - .removeAttr( "aria-disabled" ); - - // Clean up events and states - this.bindings.off( this.eventNamespace ); - }, - - _destroy: $.noop, - - widget: function() { - return this.element; - }, - - option: function( key, value ) { - var options = key; - var parts; - var curOption; - var i; - - if ( arguments.length === 0 ) { - - // Don't return a reference to the internal hash - return $.widget.extend( {}, this.options ); - } - - if ( typeof key === "string" ) { - - // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } - options = {}; - parts = key.split( "." ); - key = parts.shift(); - if ( parts.length ) { - curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); - for ( i = 0; i < parts.length - 1; i++ ) { - curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; - curOption = curOption[ parts[ i ] ]; - } - key = parts.pop(); - if ( arguments.length === 1 ) { - return curOption[ key ] === undefined ? null : curOption[ key ]; - } - curOption[ key ] = value; - } else { - if ( arguments.length === 1 ) { - return this.options[ key ] === undefined ? null : this.options[ key ]; - } - options[ key ] = value; - } - } - - this._setOptions( options ); - - return this; - }, - - _setOptions: function( options ) { - var key; - - for ( key in options ) { - this._setOption( key, options[ key ] ); - } - - return this; - }, - - _setOption: function( key, value ) { - if ( key === "classes" ) { - this._setOptionClasses( value ); - } - - this.options[ key ] = value; - - if ( key === "disabled" ) { - this._setOptionDisabled( value ); - } - - return this; - }, - - _setOptionClasses: function( value ) { - var classKey, elements, currentElements; - - for ( classKey in value ) { - currentElements = this.classesElementLookup[ classKey ]; - if ( value[ classKey ] === this.options.classes[ classKey ] || - !currentElements || - !currentElements.length ) { - continue; - } - - // We are doing this to create a new jQuery object because the _removeClass() call - // on the next line is going to destroy the reference to the current elements being - // tracked. We need to save a copy of this collection so that we can add the new classes - // below. - elements = $( currentElements.get() ); - this._removeClass( currentElements, classKey ); - - // We don't use _addClass() here, because that uses this.options.classes - // for generating the string of classes. We want to use the value passed in from - // _setOption(), this is the new value of the classes option which was passed to - // _setOption(). We pass this value directly to _classes(). - elements.addClass( this._classes( { - element: elements, - keys: classKey, - classes: value, - add: true - } ) ); - } - }, - - _setOptionDisabled: function( value ) { - this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value ); - - // If the widget is becoming disabled, then nothing is interactive - if ( value ) { - this._removeClass( this.hoverable, null, "ui-state-hover" ); - this._removeClass( this.focusable, null, "ui-state-focus" ); - } - }, - - enable: function() { - return this._setOptions( { disabled: false } ); - }, - - disable: function() { - return this._setOptions( { disabled: true } ); - }, - - _classes: function( options ) { - var full = []; - var that = this; - - options = $.extend( { - element: this.element, - classes: this.options.classes || {} - }, options ); - - function processClassString( classes, checkOption ) { - var current, i; - for ( i = 0; i < classes.length; i++ ) { - current = that.classesElementLookup[ classes[ i ] ] || $(); - if ( options.add ) { - current = $( $.unique( current.get().concat( options.element.get() ) ) ); - } else { - current = $( current.not( options.element ).get() ); - } - that.classesElementLookup[ classes[ i ] ] = current; - full.push( classes[ i ] ); - if ( checkOption && options.classes[ classes[ i ] ] ) { - full.push( options.classes[ classes[ i ] ] ); - } - } - } - - this._on( options.element, { - "remove": "_untrackClassesElement" - } ); - - if ( options.keys ) { - processClassString( options.keys.match( /\S+/g ) || [], true ); - } - if ( options.extra ) { - processClassString( options.extra.match( /\S+/g ) || [] ); - } - - return full.join( " " ); - }, - - _untrackClassesElement: function( event ) { - var that = this; - $.each( that.classesElementLookup, function( key, value ) { - if ( $.inArray( event.target, value ) !== -1 ) { - that.classesElementLookup[ key ] = $( value.not( event.target ).get() ); - } - } ); - }, - - _removeClass: function( element, keys, extra ) { - return this._toggleClass( element, keys, extra, false ); - }, - - _addClass: function( element, keys, extra ) { - return this._toggleClass( element, keys, extra, true ); - }, - - _toggleClass: function( element, keys, extra, add ) { - add = ( typeof add === "boolean" ) ? add : extra; - var shift = ( typeof element === "string" || element === null ), - options = { - extra: shift ? keys : extra, - keys: shift ? element : keys, - element: shift ? this.element : element, - add: add - }; - options.element.toggleClass( this._classes( options ), add ); - return this; - }, - - _on: function( suppressDisabledCheck, element, handlers ) { - var delegateElement; - var instance = this; - - // No suppressDisabledCheck flag, shuffle arguments - if ( typeof suppressDisabledCheck !== "boolean" ) { - handlers = element; - element = suppressDisabledCheck; - suppressDisabledCheck = false; - } - - // No element argument, shuffle and use this.element - if ( !handlers ) { - handlers = element; - element = this.element; - delegateElement = this.widget(); - } else { - element = delegateElement = $( element ); - this.bindings = this.bindings.add( element ); - } - - $.each( handlers, function( event, handler ) { - function handlerProxy() { - - // Allow widgets to customize the disabled handling - // - disabled as an array instead of boolean - // - disabled class as method for disabling individual parts - if ( !suppressDisabledCheck && - ( instance.options.disabled === true || - $( this ).hasClass( "ui-state-disabled" ) ) ) { - return; - } - return ( typeof handler === "string" ? instance[ handler ] : handler ) - .apply( instance, arguments ); - } - - // Copy the guid so direct unbinding works - if ( typeof handler !== "string" ) { - handlerProxy.guid = handler.guid = - handler.guid || handlerProxy.guid || $.guid++; - } - - var match = event.match( /^([\w:-]*)\s*(.*)$/ ); - var eventName = match[ 1 ] + instance.eventNamespace; - var selector = match[ 2 ]; - - if ( selector ) { - delegateElement.on( eventName, selector, handlerProxy ); - } else { - element.on( eventName, handlerProxy ); - } - } ); - }, - - _off: function( element, eventName ) { - eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + - this.eventNamespace; - element.off( eventName ).off( eventName ); - - // Clear the stack to avoid memory leaks (#10056) - this.bindings = $( this.bindings.not( element ).get() ); - this.focusable = $( this.focusable.not( element ).get() ); - this.hoverable = $( this.hoverable.not( element ).get() ); - }, - - _delay: function( handler, delay ) { - function handlerProxy() { - return ( typeof handler === "string" ? instance[ handler ] : handler ) - .apply( instance, arguments ); - } - var instance = this; - return setTimeout( handlerProxy, delay || 0 ); - }, - - _hoverable: function( element ) { - this.hoverable = this.hoverable.add( element ); - this._on( element, { - mouseenter: function( event ) { - this._addClass( $( event.currentTarget ), null, "ui-state-hover" ); - }, - mouseleave: function( event ) { - this._removeClass( $( event.currentTarget ), null, "ui-state-hover" ); - } - } ); - }, - - _focusable: function( element ) { - this.focusable = this.focusable.add( element ); - this._on( element, { - focusin: function( event ) { - this._addClass( $( event.currentTarget ), null, "ui-state-focus" ); - }, - focusout: function( event ) { - this._removeClass( $( event.currentTarget ), null, "ui-state-focus" ); - } - } ); - }, - - _trigger: function( type, event, data ) { - var prop, orig; - var callback = this.options[ type ]; - - data = data || {}; - event = $.Event( event ); - event.type = ( type === this.widgetEventPrefix ? - type : - this.widgetEventPrefix + type ).toLowerCase(); - - // The original event may come from any element - // so we need to reset the target on the new event - event.target = this.element[ 0 ]; - - // Copy original event properties over to the new event - orig = event.originalEvent; - if ( orig ) { - for ( prop in orig ) { - if ( !( prop in event ) ) { - event[ prop ] = orig[ prop ]; - } - } - } - - this.element.trigger( event, data ); - return !( $.isFunction( callback ) && - callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || - event.isDefaultPrevented() ); - } -}; - -$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { - $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { - if ( typeof options === "string" ) { - options = { effect: options }; - } - - var hasOptions; - var effectName = !options ? - method : - options === true || typeof options === "number" ? - defaultEffect : - options.effect || defaultEffect; - - options = options || {}; - if ( typeof options === "number" ) { - options = { duration: options }; - } - - hasOptions = !$.isEmptyObject( options ); - options.complete = callback; - - if ( options.delay ) { - element.delay( options.delay ); - } - - if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { - element[ method ]( options ); - } else if ( effectName !== method && element[ effectName ] ) { - element[ effectName ]( options.duration, options.easing, callback ); - } else { - element.queue( function( next ) { - $( this )[ method ](); - if ( callback ) { - callback.call( element[ 0 ] ); - } - next(); - } ); - } - }; -} ); - -var widget = $.widget; - - -/*! - * jQuery UI :data 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: :data Selector -//>>group: Core -//>>description: Selects elements which have data stored under the specified key. -//>>docs: http://api.jqueryui.com/data-selector/ - - -var data = $.extend( $.expr[ ":" ], { - data: $.expr.createPseudo ? - $.expr.createPseudo( function( dataName ) { - return function( elem ) { - return !!$.data( elem, dataName ); - }; - } ) : - - // Support: jQuery <1.8 - function( elem, i, match ) { - return !!$.data( elem, match[ 3 ] ); - } -} ); - -/*! - * jQuery UI Disable Selection 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: disableSelection -//>>group: Core -//>>description: Disable selection of text content within the set of matched elements. -//>>docs: http://api.jqueryui.com/disableSelection/ - -// This file is deprecated - - -var disableSelection = $.fn.extend( { - disableSelection: ( function() { - var eventType = "onselectstart" in document.createElement( "div" ) ? - "selectstart" : - "mousedown"; - - return function() { - return this.on( eventType + ".ui-disableSelection", function( event ) { - event.preventDefault(); - } ); - }; - } )(), - - enableSelection: function() { - return this.off( ".ui-disableSelection" ); - } -} ); - - -/*! - * jQuery UI Scroll Parent 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: scrollParent -//>>group: Core -//>>description: Get the closest ancestor element that is scrollable. -//>>docs: http://api.jqueryui.com/scrollParent/ - - - -var scrollParent = $.fn.scrollParent = function( includeHidden ) { - var position = this.css( "position" ), - excludeStaticParent = position === "absolute", - overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, - scrollParent = this.parents().filter( function() { - var parent = $( this ); - if ( excludeStaticParent && parent.css( "position" ) === "static" ) { - return false; - } - return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + - parent.css( "overflow-x" ) ); - } ).eq( 0 ); - - return position === "fixed" || !scrollParent.length ? - $( this[ 0 ].ownerDocument || document ) : - scrollParent; -}; - - - - -// This file is deprecated -var ie = $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); - -/*! - * jQuery UI Mouse 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: Mouse -//>>group: Widgets -//>>description: Abstracts mouse-based interactions to assist in creating certain widgets. -//>>docs: http://api.jqueryui.com/mouse/ - - - -var mouseHandled = false; -$( document ).on( "mouseup", function() { - mouseHandled = false; -} ); - -var widgetsMouse = $.widget( "ui.mouse", { - version: "1.12.1", - options: { - cancel: "input, textarea, button, select, option", - distance: 1, - delay: 0 - }, - _mouseInit: function() { - var that = this; - - this.element - .on( "mousedown." + this.widgetName, function( event ) { - return that._mouseDown( event ); - } ) - .on( "click." + this.widgetName, function( event ) { - if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) { - $.removeData( event.target, that.widgetName + ".preventClickEvent" ); - event.stopImmediatePropagation(); - return false; - } - } ); - - this.started = false; - }, - - // TODO: make sure destroying one instance of mouse doesn't mess with - // other instances of mouse - _mouseDestroy: function() { - this.element.off( "." + this.widgetName ); - if ( this._mouseMoveDelegate ) { - this.document - .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) - .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); - } - }, - - _mouseDown: function( event ) { - - // don't let more than one widget handle mouseStart - if ( mouseHandled ) { - return; - } - - this._mouseMoved = false; - - // We may have missed mouseup (out of window) - ( this._mouseStarted && this._mouseUp( event ) ); - - this._mouseDownEvent = event; - - var that = this, - btnIsLeft = ( event.which === 1 ), - - // event.target.nodeName works around a bug in IE 8 with - // disabled inputs (#7620) - elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ? - $( event.target ).closest( this.options.cancel ).length : false ); - if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) { - return true; - } - - this.mouseDelayMet = !this.options.delay; - if ( !this.mouseDelayMet ) { - this._mouseDelayTimer = setTimeout( function() { - that.mouseDelayMet = true; - }, this.options.delay ); - } - - if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { - this._mouseStarted = ( this._mouseStart( event ) !== false ); - if ( !this._mouseStarted ) { - event.preventDefault(); - return true; - } - } - - // Click event may never have fired (Gecko & Opera) - if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) { - $.removeData( event.target, this.widgetName + ".preventClickEvent" ); - } - - // These delegates are required to keep context - this._mouseMoveDelegate = function( event ) { - return that._mouseMove( event ); - }; - this._mouseUpDelegate = function( event ) { - return that._mouseUp( event ); - }; - - this.document - .on( "mousemove." + this.widgetName, this._mouseMoveDelegate ) - .on( "mouseup." + this.widgetName, this._mouseUpDelegate ); - - event.preventDefault(); - - mouseHandled = true; - return true; - }, - - _mouseMove: function( event ) { - - // Only check for mouseups outside the document if you've moved inside the document - // at least once. This prevents the firing of mouseup in the case of IE<9, which will - // fire a mousemove event if content is placed under the cursor. See #7778 - // Support: IE <9 - if ( this._mouseMoved ) { - - // IE mouseup check - mouseup happened when mouse was out of window - if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && - !event.button ) { - return this._mouseUp( event ); - - // Iframe mouseup check - mouseup occurred in another document - } else if ( !event.which ) { - - // Support: Safari <=8 - 9 - // Safari sets which to 0 if you press any of the following keys - // during a drag (#14461) - if ( event.originalEvent.altKey || event.originalEvent.ctrlKey || - event.originalEvent.metaKey || event.originalEvent.shiftKey ) { - this.ignoreMissingWhich = true; - } else if ( !this.ignoreMissingWhich ) { - return this._mouseUp( event ); - } - } - } - - if ( event.which || event.button ) { - this._mouseMoved = true; - } - - if ( this._mouseStarted ) { - this._mouseDrag( event ); - return event.preventDefault(); - } - - if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { - this._mouseStarted = - ( this._mouseStart( this._mouseDownEvent, event ) !== false ); - ( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) ); - } - - return !this._mouseStarted; - }, - - _mouseUp: function( event ) { - this.document - .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) - .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); - - if ( this._mouseStarted ) { - this._mouseStarted = false; - - if ( event.target === this._mouseDownEvent.target ) { - $.data( event.target, this.widgetName + ".preventClickEvent", true ); - } - - this._mouseStop( event ); - } - - if ( this._mouseDelayTimer ) { - clearTimeout( this._mouseDelayTimer ); - delete this._mouseDelayTimer; - } - - this.ignoreMissingWhich = false; - mouseHandled = false; - event.preventDefault(); - }, - - _mouseDistanceMet: function( event ) { - return ( Math.max( - Math.abs( this._mouseDownEvent.pageX - event.pageX ), - Math.abs( this._mouseDownEvent.pageY - event.pageY ) - ) >= this.options.distance - ); - }, - - _mouseDelayMet: function( /* event */ ) { - return this.mouseDelayMet; - }, - - // These are placeholder methods, to be overriden by extending plugin - _mouseStart: function( /* event */ ) {}, - _mouseDrag: function( /* event */ ) {}, - _mouseStop: function( /* event */ ) {}, - _mouseCapture: function( /* event */ ) { return true; } -} ); - - - - -// $.ui.plugin is deprecated. Use $.widget() extensions instead. -var plugin = $.ui.plugin = { - add: function( module, option, set ) { - var i, - proto = $.ui[ module ].prototype; - for ( i in set ) { - proto.plugins[ i ] = proto.plugins[ i ] || []; - proto.plugins[ i ].push( [ option, set[ i ] ] ); - } - }, - call: function( instance, name, args, allowDisconnected ) { - var i, - set = instance.plugins[ name ]; - - if ( !set ) { - return; - } - - if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || - instance.element[ 0 ].parentNode.nodeType === 11 ) ) { - return; - } - - for ( i = 0; i < set.length; i++ ) { - if ( instance.options[ set[ i ][ 0 ] ] ) { - set[ i ][ 1 ].apply( instance.element, args ); - } - } - } -}; - - - -var safeActiveElement = $.ui.safeActiveElement = function( document ) { - var activeElement; - - // Support: IE 9 only - // IE9 throws an "Unspecified error" accessing document.activeElement from an