From db292b00fceb8bc331c20392348d52c76ad9411f Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Mon, 10 Dec 2018 15:15:37 -0800 Subject: [PATCH 01/22] Move sources to use generated client --- http/cur_swagger.yml | 34 +++++- ui/src/api/api.ts | 115 ++++++++++++++++-- ui/src/logs/containers/logs_page/LogsPage.tsx | 5 +- ui/src/logs/utils/logQuery.test.ts | 5 +- ui/src/shared/apis/v2/queryBuilder.ts | 15 +-- .../components/TimeMachineQueryBuilder.tsx | 5 +- ui/src/shared/containers/SetActiveSource.tsx | 4 - ui/src/sources/actions/index.ts | 13 +- ui/src/sources/apis/index.ts | 53 +++----- .../components/CreateSourceOverlay.tsx | 14 +-- ui/src/sources/components/SourcesListRow.tsx | 4 +- ui/src/types/logs.ts | 3 +- ui/src/types/v2/index.ts | 4 +- ui/src/utils/api.ts | 2 + 14 files changed, 190 insertions(+), 86 deletions(-) diff --git a/http/cur_swagger.yml b/http/cur_swagger.yml index d2507d0d93..f1a6bad409 100644 --- a/http/cur_swagger.yml +++ b/http/cur_swagger.yml @@ -685,6 +685,32 @@ paths: schema: $ref: "#/components/schemas/Error" /sources/{sourceID}: + delete: + tags: + - Sources + summary: Delete a source + parameters: + - in: path + name: sourceID + schema: + type: string + required: true + description: ID of the source + responses: + '204': + description: delete has been accepted + '404': + description: view not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" patch: tags: - Sources @@ -4625,12 +4651,16 @@ components: properties: self: type: string + query: + type: string + health: + type: string + buckets: + type: string id: type: string organizationID: type: string - default: - type: boolean name: type: string type: diff --git a/ui/src/api/api.ts b/ui/src/api/api.ts index aadbc2e93e..09252d55a3 100644 --- a/ui/src/api/api.ts +++ b/ui/src/api/api.ts @@ -2343,12 +2343,6 @@ export namespace Run { * @interface Source */ export interface Source { - /** - * - * @type {boolean} - * @memberof Source - */ - _default?: boolean; /** * * @type {string} @@ -2472,6 +2466,24 @@ export namespace Source { * @interface SourceLinks */ export interface SourceLinks { + /** + * + * @type {string} + * @memberof SourceLinks + */ + buckets?: string; + /** + * + * @type {string} + * @memberof SourceLinks + */ + health?: string; + /** + * + * @type {string} + * @memberof SourceLinks + */ + query?: string; /** * * @type {string} @@ -4769,10 +4781,10 @@ export interface View { id?: string; /** * - * @type {SourceLinks} + * @type {ViewLinks} * @memberof View */ - links?: SourceLinks; + links?: ViewLinks; /** * * @type {string} @@ -4787,6 +4799,20 @@ export interface View { properties?: any; } +/** + * + * @export + * @interface ViewLinks + */ +export interface ViewLinks { + /** + * + * @type {string} + * @memberof ViewLinks + */ + self?: string; +} + /** * * @export @@ -4795,10 +4821,10 @@ export interface View { export interface Views { /** * - * @type {SourceLinks} + * @type {ViewLinks} * @memberof Views */ - links?: SourceLinks; + links?: ViewLinks; /** * * @type {Array} @@ -10040,6 +10066,39 @@ export const SourcesApiAxiosParamCreator = function (configuration?: Configurati options: localVarRequestOptions, }; }, + /** + * + * @summary Delete a source + * @param {string} sourceID ID of the source + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + sourcesSourceIDDelete(sourceID: string, options: any = {}): RequestArgs { + // verify required parameter 'sourceID' is not null or undefined + if (sourceID === null || sourceID === undefined) { + throw new RequiredError('sourceID','Required parameter sourceID was null or undefined when calling sourcesSourceIDDelete.'); + } + const localVarPath = `/sources/{sourceID}` + .replace(`{${"sourceID"}}`, encodeURIComponent(String(sourceID))); + const localVarUrlObj = url.parse(localVarPath, true); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + const localVarRequestOptions = Object.assign({ method: 'DELETE' }, baseOptions, options); + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); + // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 + delete localVarUrlObj.search; + localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers); + + return { + url: url.format(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @summary Get a source @@ -10201,6 +10260,20 @@ export const SourcesApiFp = function(configuration?: Configuration) { return axios.request(axiosRequestArgs); }; }, + /** + * + * @summary Delete a source + * @param {string} sourceID ID of the source + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + sourcesSourceIDDelete(sourceID: string, options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise { + const localVarAxiosArgs = SourcesApiAxiosParamCreator(configuration).sourcesSourceIDDelete(sourceID, options); + return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = Object.assign(localVarAxiosArgs.options, {url: basePath + localVarAxiosArgs.url}) + return axios.request(axiosRequestArgs); + }; + }, /** * * @summary Get a source @@ -10285,6 +10358,16 @@ export const SourcesApiFactory = function (configuration?: Configuration, basePa sourcesSourceIDBucketsGet(sourceID: string, org: string, options?: any) { return SourcesApiFp(configuration).sourcesSourceIDBucketsGet(sourceID, org, options)(axios, basePath); }, + /** + * + * @summary Delete a source + * @param {string} sourceID ID of the source + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + sourcesSourceIDDelete(sourceID: string, options?: any) { + return SourcesApiFp(configuration).sourcesSourceIDDelete(sourceID, options)(axios, basePath); + }, /** * * @summary Get a source @@ -10364,6 +10447,18 @@ export class SourcesApi extends BaseAPI { return SourcesApiFp(this.configuration).sourcesSourceIDBucketsGet(sourceID, org, options)(this.axios, this.basePath); } + /** + * + * @summary Delete a source + * @param {string} sourceID ID of the source + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SourcesApi + */ + public sourcesSourceIDDelete(sourceID: string, options?: any) { + return SourcesApiFp(this.configuration).sourcesSourceIDDelete(sourceID, options)(this.axios, this.basePath); + } + /** * * @summary Get a source diff --git a/ui/src/logs/containers/logs_page/LogsPage.tsx b/ui/src/logs/containers/logs_page/LogsPage.tsx index 3af398f66e..65005c14fb 100644 --- a/ui/src/logs/containers/logs_page/LogsPage.tsx +++ b/ui/src/logs/containers/logs_page/LogsPage.tsx @@ -295,10 +295,7 @@ class LogsPage extends Component { private setCurrentSource = async () => { if (!this.props.currentSource && this.props.sources.length > 0) { - const source = - this.props.sources.find(src => { - return src.default - }) || this.props.sources[0] + const source = this.props.sources[0] return await this.props.getSourceAndPopulateBuckets(source.links.self) } diff --git a/ui/src/logs/utils/logQuery.test.ts b/ui/src/logs/utils/logQuery.test.ts index 772af60c86..f066bef971 100644 --- a/ui/src/logs/utils/logQuery.test.ts +++ b/ui/src/logs/utils/logQuery.test.ts @@ -9,7 +9,8 @@ import {oneline} from 'src/logs/utils/helpers/formatting' import {QueryConfig} from 'src/types' import {Filter, LogQuery} from 'src/types/logs' -import {InfluxLanguage, SourceType} from 'src/types/v2' +import {InfluxLanguage} from 'src/types/v2' +import {Source} from 'src/api' describe('Logs.LogQuery', () => { let config: QueryConfig @@ -39,7 +40,7 @@ describe('Logs.LogQuery', () => { const source = { id: '1', name: 'foo', - type: SourceType.Self, + type: Source.TypeEnum.Self, url: 'test.local', insecureSkipVerify: false, default: true, diff --git a/ui/src/shared/apis/v2/queryBuilder.ts b/ui/src/shared/apis/v2/queryBuilder.ts index 73bf9f7edb..74fba0a08b 100644 --- a/ui/src/shared/apis/v2/queryBuilder.ts +++ b/ui/src/shared/apis/v2/queryBuilder.ts @@ -6,17 +6,18 @@ import {executeQuery, ExecuteFluxQueryResult} from 'src/shared/apis/v2/query' import {parseResponse} from 'src/shared/parsing/flux/response' // Types -import {SourceType, InfluxLanguage} from 'src/types/v2' +import {InfluxLanguage} from 'src/types/v2' +import {Source} from 'src/api' export const SEARCH_DURATION = '30d' export const LIMIT = 200 export async function findBuckets( url: string, - sourceType: SourceType, + sourceType: Source.TypeEnum, searchTerm?: string ) { - if (sourceType === SourceType.V1) { + if (sourceType === Source.TypeEnum.V1) { throw new Error('metaqueries not yet implemented for SourceType.V1') } @@ -28,11 +29,11 @@ export async function findBuckets( export async function findMeasurements( url: string, - sourceType: SourceType, + sourceType: Source.TypeEnum, bucket: string, searchTerm: string = '' ): Promise { - if (sourceType === SourceType.V1) { + if (sourceType === Source.TypeEnum.V1) { throw new Error('metaqueries not yet implemented for SourceType.V1') } @@ -44,12 +45,12 @@ export async function findMeasurements( export async function findFields( url: string, - sourceType: SourceType, + sourceType: Source.TypeEnum, bucket: string, measurements: string[], searchTerm: string = '' ): Promise { - if (sourceType === SourceType.V1) { + if (sourceType === Source.TypeEnum.V1) { throw new Error('metaqueries not yet implemented for SourceType.V1') } diff --git a/ui/src/shared/components/TimeMachineQueryBuilder.tsx b/ui/src/shared/components/TimeMachineQueryBuilder.tsx index 190ae6c2d1..71d0e18e65 100644 --- a/ui/src/shared/components/TimeMachineQueryBuilder.tsx +++ b/ui/src/shared/components/TimeMachineQueryBuilder.tsx @@ -29,7 +29,8 @@ import 'src/shared/components/TimeMachineQueryBuilder.scss' // Types import {RemoteDataState} from 'src/types' -import {AppState, Source, SourceType, BuilderConfig} from 'src/types/v2' +import {AppState, BuilderConfig} from 'src/types/v2' +import {Source} from 'src/api' const EMPTY_FIELDS_MESSAGE = 'Select at least one bucket and measurement' const EMPTY_FUNCTIONS_MESSAGE = 'Select at least one bucket and measurement' @@ -39,7 +40,7 @@ const mergeUnique = (items: string[], selection: string[]) => interface StateProps { queryURL: string - sourceType: SourceType + sourceType: Source.TypeEnum } interface DispatchProps { diff --git a/ui/src/shared/containers/SetActiveSource.tsx b/ui/src/shared/containers/SetActiveSource.tsx index 577cc9fce6..eaaef3c0bd 100644 --- a/ui/src/shared/containers/SetActiveSource.tsx +++ b/ui/src/shared/containers/SetActiveSource.tsx @@ -1,7 +1,6 @@ // Libraries import React, {PureComponent} from 'react' import {connect} from 'react-redux' -import {get} from 'lodash' // Actions import {setActiveSource} from 'src/sources/actions' @@ -51,7 +50,6 @@ class SetActiveSource extends PureComponent { private resolveActiveSource() { const {sources, activeSourceID, onSetActiveSource} = this.props - const defaultSourceID = get(sources.find(s => s.default), 'id') const querySourceID = readQueryParams().sourceID let resolvedSourceID @@ -60,8 +58,6 @@ class SetActiveSource extends PureComponent { resolvedSourceID = activeSourceID } else if (sources.find(s => s.id === querySourceID)) { resolvedSourceID = querySourceID - } else if (defaultSourceID) { - resolvedSourceID = defaultSourceID } else if (sources.length) { resolvedSourceID = sources[0] } else { diff --git a/ui/src/sources/actions/index.ts b/ui/src/sources/actions/index.ts index 1d14fac6e1..c5b3420c65 100644 --- a/ui/src/sources/actions/index.ts +++ b/ui/src/sources/actions/index.ts @@ -10,7 +10,8 @@ import { } from 'src/sources/apis' // Types -import {Source, GetState} from 'src/types/v2' +import {GetState} from 'src/types/v2' +import {Source} from 'src/api' export type Action = | SetActiveSourceAction @@ -68,12 +69,8 @@ export const removeSource = (sourceID: string): RemoveSourceAction => ({ payload: {sourceID}, }) -export const readSources = () => async ( - dispatch: Dispatch, - getState: GetState -) => { - const sourcesLink = getState().links.sources - const sources = await readSourcesAJAX(sourcesLink) +export const readSources = () => async (dispatch: Dispatch) => { + const sources = await readSourcesAJAX() dispatch(setSources(sources)) } @@ -91,7 +88,7 @@ export const createSource = (attrs: Partial) => async ( export const updateSource = (source: Source) => async ( dispatch: Dispatch ) => { - const updatedSource = await updateSourceAJAX(source) + const updatedSource = await updateSourceAJAX(source.id, source) dispatch(setSource(updatedSource)) } diff --git a/ui/src/sources/apis/index.ts b/ui/src/sources/apis/index.ts index d947786389..eb7d6d4f30 100644 --- a/ui/src/sources/apis/index.ts +++ b/ui/src/sources/apis/index.ts @@ -1,57 +1,40 @@ -import AJAX from 'src/utils/ajax' -import {Source} from 'src/types/v2' +import {Source} from 'src/api' +import {sourcesAPI} from 'src/utils/api' -export const readSources = async (url): Promise => { - const {data} = await AJAX({url}) +export const readSources = async (): Promise => { + const {data} = await sourcesAPI.sourcesGet('') return data.sources } -export const readSource = async (url: string): Promise => { - const {data: source} = await AJAX({ - url, - }) +export const readSource = async (id: string): Promise => { + const {data} = await sourcesAPI.sourcesSourceIDGet(id) - return source + return data } export const createSource = async ( - url: string, + org: string, attributes: Partial ): Promise => { - const {data: source} = await AJAX({ - url, - method: 'POST', - data: attributes, - }) + const {data} = await sourcesAPI.sourcesPost(org, attributes) - return source + return data } export const updateSource = async ( - newSource: Partial + id: string, + source: Partial ): Promise => { - const {data: source} = await AJAX({ - url: newSource.links.self, - method: 'PATCH', - data: newSource, - }) + const {data} = await sourcesAPI.sourcesSourceIDPatch(id, source) - return source + return data } -export function deleteSource(source) { - return AJAX({ - url: source.links.self, - method: 'DELETE', - }) +export async function deleteSource(source: Source): Promise { + await sourcesAPI.sourcesSourceIDDelete(source.id) } -export const getSourceHealth = async (url: string): Promise => { - try { - await AJAX({url}) - } catch (error) { - console.error(`Unable to contact source ${url}`, error) - throw error - } +export const getSourceHealth = async (id: string): Promise => { + await sourcesAPI.sourcesSourceIDHealthGet(id) } diff --git a/ui/src/sources/components/CreateSourceOverlay.tsx b/ui/src/sources/components/CreateSourceOverlay.tsx index f8aca6acff..b9b15f76f2 100644 --- a/ui/src/sources/components/CreateSourceOverlay.tsx +++ b/ui/src/sources/components/CreateSourceOverlay.tsx @@ -26,7 +26,7 @@ import {sourceCreationFailed} from 'src/shared/copy/notifications' import 'src/sources/components/CreateSourceOverlay.scss' // Types -import {Source, SourceType} from 'src/types/v2' +import {Source} from 'src/api' import {RemoteDataState} from 'src/types' interface DispatchProps { @@ -49,7 +49,7 @@ class CreateSourceOverlay extends PureComponent { public state: State = { draftSource: { name: '', - type: SourceType.V1, + type: Source.TypeEnum.V1, url: '', }, creationStatus: RemoteDataState.NotStarted, @@ -94,16 +94,16 @@ class CreateSourceOverlay extends PureComponent { v1 v2 @@ -144,7 +144,7 @@ class CreateSourceOverlay extends PureComponent { this.setState({draftSource}) } - private handleChangeType = (type: SourceType) => { + private handleChangeType = (type: Source.TypeEnum) => { const draftSource = { ...this.state.draftSource, type, diff --git a/ui/src/sources/components/SourcesListRow.tsx b/ui/src/sources/components/SourcesListRow.tsx index bd1e1e33b6..d0a60fa187 100644 --- a/ui/src/sources/components/SourcesListRow.tsx +++ b/ui/src/sources/components/SourcesListRow.tsx @@ -20,7 +20,7 @@ import 'src/sources/components/SourcesListRow.scss' // Types import {AppState} from 'src/types/v2' -import {Source, SourceType} from 'src/types/v2' +import {Source} from 'src/api' interface StateProps { activeSourceID: string @@ -43,7 +43,7 @@ const SourcesListRow: SFC = ({ onSetActiveSource, onDeleteSource, }) => { - const canDelete = source.type !== SourceType.Self + const canDelete = source.type !== Source.TypeEnum.Self const isActiveSource = source.id === activeSourceID const onButtonClick = () => onSetActiveSource(source.id) const onDeleteClick = () => onDeleteSource(source.id) diff --git a/ui/src/types/logs.ts b/ui/src/types/logs.ts index b3eeacceb0..9750eba3b3 100644 --- a/ui/src/types/logs.ts +++ b/ui/src/types/logs.ts @@ -1,7 +1,8 @@ import {Index} from 'react-virtualized' -import {Bucket, Source} from 'src/types/v2' +import {Bucket} from 'src/types/v2' import {QueryConfig} from 'src/types' +import {Source} from 'src/api' import {FieldOption, TimeSeriesValue} from 'src/types/v2/dashboards' diff --git a/ui/src/types/v2/index.ts b/ui/src/types/v2/index.ts index 3d41b4db64..7ef09e38a1 100644 --- a/ui/src/types/v2/index.ts +++ b/ui/src/types/v2/index.ts @@ -1,4 +1,4 @@ -import {Source, SourceType} from 'src/types/v2/sources' +import {SourceType} from 'src/types/v2/sources' import {Bucket, RetentionRule, RetentionRuleTypes} from 'src/types/v2/buckets' import {RangeState} from 'src/dashboards/reducers/v2/ranges' import {ViewsState} from 'src/dashboards/reducers/v2/views' @@ -17,7 +17,7 @@ import { InfluxLanguage, } from 'src/types/v2/dashboards' -import {Cell, Dashboard} from 'src/api' +import {Cell, Dashboard, Source} from 'src/api' import {Task} from 'src/types/v2/tasks' import {Member} from 'src/types/v2/members' import {Organization} from 'src/types/v2/orgs' diff --git a/ui/src/utils/api.ts b/ui/src/utils/api.ts index 8c1ea733de..73b62fdaf1 100644 --- a/ui/src/utils/api.ts +++ b/ui/src/utils/api.ts @@ -7,6 +7,7 @@ import { AuthorizationsApi, ViewsApi, WriteApi, + SourcesApi, } from 'src/api' const basePath = '/api/v2' @@ -19,3 +20,4 @@ export const cellsAPI = new CellsApi({basePath}) export const telegrafsAPI = new TelegrafsApi({basePath}) export const authorizationsAPI = new AuthorizationsApi({basePath}) export const writeAPI = new WriteApi({basePath}) +export const sourcesAPI = new SourcesApi({basePath}) From 084d30bc94f7276e6190b1820eace2e78128e136 Mon Sep 17 00:00:00 2001 From: Daniel Campbell Date: Mon, 10 Dec 2018 17:58:23 -0800 Subject: [PATCH 02/22] Fix gridsizer recalculation, add scrolling, progress bar styles, increase max width Co-authored-by: Iris Scholten Co-authored-by: Daniel Campbell --- .../components/grid_sizer/GridSizer.scss | 45 ++++------- .../components/grid_sizer/GridSizer.tsx | 74 +++++++++++------ .../components/wizard/ProgressBar.scss | 5 +- .../components/wizard/WizardFullScreen.scss | 1 + .../wizard/WizardProgressHeader.scss | 3 +- ui/src/clockface/index.ts | 2 + ui/src/onboarding/OnboardingWizard.scss | 16 ++-- .../selectionStep/SelectDataSourceStep.tsx | 4 +- .../selectionStep/StreamingSelector.tsx | 79 ++++++++++++++----- .../components/selectionStep/TypeSelector.tsx | 8 +- ui/src/onboarding/constants/pluginConfigs.ts | 16 ++-- 11 files changed, 153 insertions(+), 100 deletions(-) diff --git a/ui/src/clockface/components/grid_sizer/GridSizer.scss b/ui/src/clockface/components/grid_sizer/GridSizer.scss index 713a4e6864..29aca5ec91 100644 --- a/ui/src/clockface/components/grid_sizer/GridSizer.scss +++ b/ui/src/clockface/components/grid_sizer/GridSizer.scss @@ -3,44 +3,29 @@ ------------------------------------------------------------------------------ */ -$card-select--gutter: 4px; .grid-sizer { width: 100%; - display: inline-block; + display: inline-flex; + justify-content: center; + position: relative; +} + +.grid-sizer--cells { + height: 100%; + position: relative; + overflow: hidden; +} + +.grid-sizer--cells:after { + clear: both; + content: ""; + display: block; } .grid-sizer--cell { float: left; position: relative; - width: calc(100% - #{$card-select--gutter}); - height: calc(100% - #{$card-select--gutter}); - margin: $card-select--gutter / 2; -} - -.grid-sizer--col-2 { - width: calc(50% - #{$card-select--gutter}); - padding-bottom: 50%; -} - -.grid-sizer--col-3 { - width: calc(33.3333% - #{$card-select--gutter}); - padding-bottom: 33.3333%; -} - -.grid-sizer--col-4 { - width: calc(25% - #{$card-select--gutter}); - padding-bottom: 25%; -} - -.grid-sizer--col-5 { - width: calc(20% - #{$card-select--gutter}); - padding-bottom: 20%; -} - -.grid-sizer--col-6 { - width: calc(16.6667% - #{$card-select--gutter}); - padding-bottom: 16.6667%; } .grid-sizer--content { diff --git a/ui/src/clockface/components/grid_sizer/GridSizer.tsx b/ui/src/clockface/components/grid_sizer/GridSizer.tsx index 72fd608a89..bc5c8391ce 100644 --- a/ui/src/clockface/components/grid_sizer/GridSizer.tsx +++ b/ui/src/clockface/components/grid_sizer/GridSizer.tsx @@ -8,68 +8,81 @@ import {ErrorHandling} from 'src/shared/decorators/errors' interface Props { children?: JSX.Element[] cellWidth?: number + recalculateFlag?: string width?: number + wait?: number } interface State { - columns: number + columnStyle: { + width: string + paddingBottom: string + margin: string + } } @ErrorHandling class GridSizer extends PureComponent { public static defaultProps: Partial = { cellWidth: 150, + recalculateFlag: '', width: null, + wait: 0, } + private timeoutID: NodeJS.Timer + private debouncedSizeListener: () => void + private isComponentMounted: boolean + constructor(props) { super(props) this.state = { - columns: null, + columnStyle: null, } - } - public listener = () => { - _.debounce(() => this.setColumns(this.getWidth()), 250) + this.debouncedSizeListener = _.debounce(this.setColumnStyle, 250) } public componentDidMount() { + this.isComponentMounted = true const {width} = this.props - const widthValue = width || this.getWidth() - this.setColumns(widthValue) + this.setColumnStyle() if (!width) { - window.addEventListener('resize', this.listener, false) + window.addEventListener('resize', this.debouncedSizeListener, false) } } - public componentDidUpdate() { - const {width} = this.props - if (width) { - this.setColumns(width) + public componentDidUpdate(prevProps) { + const {recalculateFlag, wait} = this.props + if (prevProps.recalculateFlag !== recalculateFlag) { + this.timeoutID = setTimeout(this.setColumnStyle, wait) } } public componentWillUnmount() { - window.removeEventListener('resize', this.listener, false) + this.isComponentMounted = false + clearInterval(this.timeoutID) + window.removeEventListener('resize', this.debouncedSizeListener, false) } public render() { return (
- {this.sizeChildren} +
{this.sizeChildren}
) } private get sizeChildren() { - const {columns} = this.state + const {columnStyle} = this.state const {children} = this.props - if (columns) { + if (columnStyle) { const wrappedChildren = children.map((child, i) => (
{child}
@@ -82,6 +95,10 @@ class GridSizer extends PureComponent { } private getWidth = () => { + if (!this.isComponentMounted) { + return + } + const ele = document.getElementById('grid_sizer') const computedWidth = window .getComputedStyle(ele, null) @@ -94,13 +111,24 @@ class GridSizer extends PureComponent { return widthValue } - private setColumns = (width: number) => { - const {cellWidth} = this.props - const columns = Math.round(width / cellWidth) + private setColumnStyle = () => { + const {cellWidth, width} = this.props + const actualWidth = width || this.getWidth() + const columns = Math.round(actualWidth / cellWidth) + const columnsPercent = 1 / columns + const calculatedCellWidth = actualWidth * columnsPercent + const gutterWidth = 4 + const columnStyle = { + width: `${calculatedCellWidth - gutterWidth}px`, + margin: `${gutterWidth / 2}px`, + paddingBottom: `${calculatedCellWidth - gutterWidth}px`, + } - this.setState({ - columns, - }) + if (this.isComponentMounted) { + this.setState({ + columnStyle, + }) + } } } diff --git a/ui/src/clockface/components/wizard/ProgressBar.scss b/ui/src/clockface/components/wizard/ProgressBar.scss index 8921088f4c..8751b0fa55 100644 --- a/ui/src/clockface/components/wizard/ProgressBar.scss +++ b/ui/src/clockface/components/wizard/ProgressBar.scss @@ -75,6 +75,7 @@ align-items: center; cursor: pointer; margin: 0 $ix-marg-a; + flex-shrink: 0; } .wizard--progress-button:hover { @@ -84,7 +85,9 @@ } .wizard--progress-connector { - width: 70px; + min-width: 20px; + width: 100%; + max-width: 70px; margin: 0 $ix-marg-a; height: 2px; background-color: $g7-graphite; diff --git a/ui/src/clockface/components/wizard/WizardFullScreen.scss b/ui/src/clockface/components/wizard/WizardFullScreen.scss index 548f3de5aa..59be2cbca0 100644 --- a/ui/src/clockface/components/wizard/WizardFullScreen.scss +++ b/ui/src/clockface/components/wizard/WizardFullScreen.scss @@ -6,6 +6,7 @@ .wizard--full-screen { min-width: 100%; min-height: 100%; + max-height: 100%; padding: $ix-marg-d $ix-marg-e; z-index: 50; display: inline-flex; diff --git a/ui/src/clockface/components/wizard/WizardProgressHeader.scss b/ui/src/clockface/components/wizard/WizardProgressHeader.scss index 5ed55e8331..766ac810c0 100644 --- a/ui/src/clockface/components/wizard/WizardProgressHeader.scss +++ b/ui/src/clockface/components/wizard/WizardProgressHeader.scss @@ -6,8 +6,7 @@ .wizard--progress-header { position: relative; background-color: $g0-obsidian; - width: 80%; - max-width: 1000px; + width: 100%; height: 50px; padding: 0 $ix-marg-c; display: inline-flex; diff --git a/ui/src/clockface/index.ts b/ui/src/clockface/index.ts index 6e929920bd..92b69c6b66 100644 --- a/ui/src/clockface/index.ts +++ b/ui/src/clockface/index.ts @@ -23,6 +23,7 @@ import IndexList from './components/index_views/IndexList' import Context from './components/context_menu/Context' import FormElement from 'src/clockface/components/form_layout/FormElement' import DraggableResizer from 'src/clockface/components/draggable_resizer/DraggableResizer' +import GridSizer from 'src/clockface/components/grid_sizer/GridSizer' // Import Types import { @@ -58,6 +59,7 @@ export { EmptyState, Form, FormElement, + GridSizer, IndexList, Input, InputType, diff --git a/ui/src/onboarding/OnboardingWizard.scss b/ui/src/onboarding/OnboardingWizard.scss index 561ef9acde..887e3e9002 100644 --- a/ui/src/onboarding/OnboardingWizard.scss +++ b/ui/src/onboarding/OnboardingWizard.scss @@ -12,8 +12,7 @@ margin: $ix-marg-d; margin-top: $ix-marg-a; flex-grow: 1; - width: 80%; - max-width: 1000px; + width: 100%; } .wizard-step--container { @@ -26,6 +25,7 @@ text-align: center; background-color: $g3-castle; border-radius: $radius; + padding: 20px; flex: 1 0 100%; transition: flex 0.4s ease; transform: translate3d(0, 0, 0); @@ -200,14 +200,8 @@ text-align: center; } -.wizard-step--grid-container-lg { - max-width: 750px; +.wizard-step--grid-container { + width: 90%; display: block; margin: 0 auto; -} - -.wizard-step--grid-container-sm { - max-width: 500px; - display: block; - margin: 0 auto; -} +} \ No newline at end of file diff --git a/ui/src/onboarding/components/selectionStep/SelectDataSourceStep.tsx b/ui/src/onboarding/components/selectionStep/SelectDataSourceStep.tsx index 21f0ac67d7..094c67152b 100644 --- a/ui/src/onboarding/components/selectionStep/SelectDataSourceStep.tsx +++ b/ui/src/onboarding/components/selectionStep/SelectDataSourceStep.tsx @@ -11,7 +11,7 @@ import { ComponentSize, ComponentStatus, } from 'src/clockface' -import DataSourceTypeSelector from 'src/onboarding/components/selectionStep/TypeSelector' +import TypeSelector from 'src/onboarding/components/selectionStep/TypeSelector' import StreamingDataSourceSelector from 'src/onboarding/components/selectionStep/StreamingSelector' // Actions @@ -107,7 +107,7 @@ class SelectDataSourceStep extends PureComponent { ) } return ( - diff --git a/ui/src/onboarding/components/selectionStep/StreamingSelector.tsx b/ui/src/onboarding/components/selectionStep/StreamingSelector.tsx index b7d19daa53..f21bfb6d8e 100644 --- a/ui/src/onboarding/components/selectionStep/StreamingSelector.tsx +++ b/ui/src/onboarding/components/selectionStep/StreamingSelector.tsx @@ -1,42 +1,83 @@ // Libraries import React, {PureComponent} from 'react' +import uuid from 'uuid' // Components import {ErrorHandling} from 'src/shared/decorators/errors' import CardSelectCard from 'src/clockface/components/card_select/CardSelectCard' -import GridSizer from 'src/clockface/components/grid_sizer/GridSizer' +import {GridSizer} from 'src/clockface' // Constants import {PLUGIN_OPTIONS} from 'src/onboarding/constants/pluginConfigs' // Types import {TelegrafPlugin} from 'src/types/v2/dataLoaders' +import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar' export interface Props { telegrafPlugins: TelegrafPlugin[] onToggleTelegrafPlugin: (telegrafPlugin: string, isSelected: boolean) => void } +interface State { + gridSizerUpdateFlag: string +} + +const ANIMATION_LENGTH = 400 @ErrorHandling -class StreamingDataSourcesSelector extends PureComponent { +class StreamingSelector extends PureComponent { + private scrollMaxHeight = window.innerHeight * 0.45 + constructor(props: Props) { + super(props) + this.state = { + gridSizerUpdateFlag: uuid.v4(), + } + } + + public componentDidUpdate(prevProps) { + const addFirst = + prevProps.telegrafPlugins.length === 0 && + this.props.telegrafPlugins.length > 0 + + const removeLast = + prevProps.telegrafPlugins.length > 0 && + this.props.telegrafPlugins.length === 0 + + if (addFirst || removeLast) { + const gridSizerUpdateFlag = uuid.v4() + this.setState({gridSizerUpdateFlag}) + } + } + public render() { + const {gridSizerUpdateFlag} = this.state + return ( -
- - {PLUGIN_OPTIONS.map(ds => { - return ( - - ) - })} - -
+ +
+ + {PLUGIN_OPTIONS.map(ds => { + return ( + + ) + })} + +
+
) } @@ -57,4 +98,4 @@ class StreamingDataSourcesSelector extends PureComponent { } } -export default StreamingDataSourcesSelector +export default StreamingSelector diff --git a/ui/src/onboarding/components/selectionStep/TypeSelector.tsx b/ui/src/onboarding/components/selectionStep/TypeSelector.tsx index d6753eaa0a..5eb8672a92 100644 --- a/ui/src/onboarding/components/selectionStep/TypeSelector.tsx +++ b/ui/src/onboarding/components/selectionStep/TypeSelector.tsx @@ -4,7 +4,7 @@ import React, {PureComponent} from 'react' // Components import {ErrorHandling} from 'src/shared/decorators/errors' import CardSelectCard from 'src/clockface/components/card_select/CardSelectCard' -import GridSizer from 'src/clockface/components/grid_sizer/GridSizer' +import {GridSizer} from 'src/clockface' // Types import {DataLoaderType} from 'src/types/v2/dataLoaders' @@ -21,10 +21,10 @@ const DATA_SOURCES_OPTIONS = [ ] @ErrorHandling -class DataSourceTypeSelector extends PureComponent { +class TypeSelector extends PureComponent { public render() { return ( -
+
{DATA_SOURCES_OPTIONS.map(ds => { return ( @@ -54,4 +54,4 @@ class DataSourceTypeSelector extends PureComponent { } } -export default DataSourceTypeSelector +export default TypeSelector diff --git a/ui/src/onboarding/constants/pluginConfigs.ts b/ui/src/onboarding/constants/pluginConfigs.ts index d4d7cb64cb..c8a57dd5dc 100644 --- a/ui/src/onboarding/constants/pluginConfigs.ts +++ b/ui/src/onboarding/constants/pluginConfigs.ts @@ -198,17 +198,17 @@ export const PLUGIN_OPTIONS: TelegrafPluginName[] = [ TelegrafPluginInputDisk.NameEnum.Disk, TelegrafPluginInputDiskio.NameEnum.Diskio, TelegrafPluginInputDocker.NameEnum.Docker, - // TelegrafPluginInputFile.NameEnum.File, + TelegrafPluginInputFile.NameEnum.File, TelegrafPluginInputKernel.NameEnum.Kernel, TelegrafPluginInputKubernetes.NameEnum.Kubernetes, TelegrafPluginInputLogParser.NameEnum.Logparser, - // TelegrafPluginInputMem.NameEnum.Mem, - // TelegrafPluginInputNet.NameEnum.Net, - // TelegrafPluginInputNetResponse.NameEnum.NetResponse, - // TelegrafPluginInputNgnix.NameEnum.Ngnix, - // TelegrafPluginInputProcesses.NameEnum.Processes, - // TelegrafPluginInputProcstat.NameEnum.Procstat, - // TelegrafPluginInputPrometheus.NameEnum.Prometheus, + TelegrafPluginInputMem.NameEnum.Mem, + TelegrafPluginInputNet.NameEnum.Net, + TelegrafPluginInputNetResponse.NameEnum.NetResponse, + TelegrafPluginInputNgnix.NameEnum.Ngnix, + TelegrafPluginInputProcesses.NameEnum.Processes, + TelegrafPluginInputProcstat.NameEnum.Procstat, + TelegrafPluginInputPrometheus.NameEnum.Prometheus, TelegrafPluginInputRedis.NameEnum.Redis, TelegrafPluginInputSyslog.NameEnum.Syslog, TelegrafPluginInputSwap.NameEnum.Swap, From 8d071f85fdf8de550d8b4c7754d630e6612421d2 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Tue, 11 Dec 2018 09:40:36 -0700 Subject: [PATCH 03/22] Fix hang on help command. --- cmd/influxd/main.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/influxd/main.go b/cmd/influxd/main.go index d0a5e402ae..4dbf32430b 100644 --- a/cmd/influxd/main.go +++ b/cmd/influxd/main.go @@ -55,6 +55,8 @@ func main() { if err := m.Run(ctx, os.Args[1:]...); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) + } else if !m.running { + os.Exit(1) } <-ctx.Done() @@ -67,8 +69,9 @@ func main() { // Main represents the main program execution. type Main struct { - wg sync.WaitGroup - cancel func() + wg sync.WaitGroup + cancel func() + running bool logLevel string httpBindAddress string @@ -197,6 +200,7 @@ func (m *Main) Run(ctx context.Context, args ...string) error { } func (m *Main) run(ctx context.Context) (err error) { + m.running = true ctx, m.cancel = context.WithCancel(ctx) var lvl zapcore.Level From 43537fe8e6de682244ec29e25269cf551f1a447e Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Tue, 11 Dec 2018 09:42:53 -0700 Subject: [PATCH 04/22] Fix full compaction panic Issuing a full compaction strategy did not appropriately attach a compaction tracker which was later invoked causing a `SIGSEGV`. --- tsdb/tsm1/engine.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tsdb/tsm1/engine.go b/tsdb/tsm1/engine.go index cef63dd471..2cc9a3ff26 100644 --- a/tsdb/tsm1/engine.go +++ b/tsdb/tsm1/engine.go @@ -1554,6 +1554,7 @@ func (e *Engine) fullCompactionStrategy(group CompactionGroup, optimize bool) *c fast: optimize, engine: e, level: 5, + tracker: e.compactionTracker, } if optimize { From eba485f2be5124b37730c8f1b4db1541c392d3f3 Mon Sep 17 00:00:00 2001 From: Edd Robinson Date: Tue, 11 Dec 2018 18:30:59 +0000 Subject: [PATCH 05/22] Fix nil tracker for full compactions --- tsdb/tsm1/engine.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tsdb/tsm1/engine.go b/tsdb/tsm1/engine.go index cef63dd471..2cc9a3ff26 100644 --- a/tsdb/tsm1/engine.go +++ b/tsdb/tsm1/engine.go @@ -1554,6 +1554,7 @@ func (e *Engine) fullCompactionStrategy(group CompactionGroup, optimize bool) *c fast: optimize, engine: e, level: 5, + tracker: e.compactionTracker, } if optimize { From 97d397900e79356e23e4bf94be8912998a74afa4 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Tue, 11 Dec 2018 10:43:22 -0800 Subject: [PATCH 06/22] Fix(ui/dataLoaders): Update config field modifiers --- ui/mocks/dummyData.ts | 11 +- .../onboarding/reducers/dataLoaders.test.ts | 124 +++++++++++++++++- ui/src/onboarding/reducers/dataLoaders.ts | 32 +++-- ui/src/onboarding/utils/pluginConfigs.ts | 10 ++ 4 files changed, 161 insertions(+), 16 deletions(-) diff --git a/ui/mocks/dummyData.ts b/ui/mocks/dummyData.ts index eeff65c0f7..2c13c546f2 100644 --- a/ui/mocks/dummyData.ts +++ b/ui/mocks/dummyData.ts @@ -12,7 +12,7 @@ import {Links} from 'src/types/v2/links' import {Task, TaskStatus} from 'src/types/v2/tasks' import {OnboardingStepProps} from 'src/onboarding/containers/OnboardingWizard' import {ConfigurationState} from 'src/types/v2/dataLoaders' -import {TelegrafPluginInputCpu} from 'src/api' +import {TelegrafPluginInputCpu, TelegrafPluginInputRedis} from 'src/api' export const links: Links = { authorizations: '/api/v2/authorizations', @@ -292,6 +292,15 @@ export const telegrafPlugin = { active: true, } +export const redisPlugin = { + name: TelegrafPluginInputRedis.NameEnum.Redis, + type: TelegrafPluginInputRedis.TypeEnum.Input, + config: { + servers: [], + password: '', + }, +} + export const influxDB2Plugin = { name: 'influxdb_v2', type: 'output', diff --git a/ui/src/onboarding/reducers/dataLoaders.test.ts b/ui/src/onboarding/reducers/dataLoaders.test.ts index 626f385a31..9677990645 100644 --- a/ui/src/onboarding/reducers/dataLoaders.test.ts +++ b/ui/src/onboarding/reducers/dataLoaders.test.ts @@ -8,14 +8,26 @@ import dataLoadersReducer, { import { setDataLoadersType, addTelegrafPlugin, + addConfigValue, + removeConfigValue, removeTelegrafPlugin, setActiveTelegrafPlugin, setTelegrafConfigID, + updateTelegrafPluginConfig, } from 'src/onboarding/actions/dataLoaders' // Types -import {TelegrafPluginInputCpu, TelegrafPluginInputDisk} from 'src/api' -import {DataLoaderType, ConfigurationState} from 'src/types/v2/dataLoaders' +import { + TelegrafPluginInputCpu, + TelegrafPluginInputDisk, + TelegrafPluginInputRedis, +} from 'src/api' +import { + DataLoaderType, + ConfigurationState, + TelegrafPlugin, +} from 'src/types/v2/dataLoaders' +import {redisPlugin} from 'mocks/dummyData' describe('dataLoader reducer', () => { it('can set a type', () => { @@ -129,4 +141,112 @@ describe('dataLoader reducer', () => { expect(actual).toEqual(expected) }) + + it('can update a plugin config field', () => { + const plugin = { + ...redisPlugin, + config: {servers: [], password: ''}, + } + const tp: TelegrafPlugin = { + name: TelegrafPluginInputRedis.NameEnum.Redis, + configured: ConfigurationState.Unconfigured, + active: true, + plugin, + } + const actual = dataLoadersReducer( + {...INITIAL_STATE, telegrafPlugins: [tp]}, + updateTelegrafPluginConfig( + TelegrafPluginInputRedis.NameEnum.Redis, + 'password', + 'pa$$w0rd' + ) + ) + + const expected = { + ...INITIAL_STATE, + telegrafPlugins: [ + { + ...tp, + plugin: { + ...plugin, + config: {servers: [], password: 'pa$$w0rd'}, + }, + }, + ], + } + + expect(actual).toEqual(expected) + }) + + it('can add a plugin config value', () => { + const plugin = { + ...redisPlugin, + config: {servers: ['first'], password: ''}, + } + const tp: TelegrafPlugin = { + name: TelegrafPluginInputRedis.NameEnum.Redis, + configured: ConfigurationState.Unconfigured, + active: true, + plugin, + } + const actual = dataLoadersReducer( + {...INITIAL_STATE, telegrafPlugins: [tp]}, + addConfigValue( + TelegrafPluginInputRedis.NameEnum.Redis, + 'servers', + 'second' + ) + ) + + const expected = { + ...INITIAL_STATE, + telegrafPlugins: [ + { + ...tp, + plugin: { + ...plugin, + config: {servers: ['first', 'second'], password: ''}, + }, + }, + ], + } + + expect(actual).toEqual(expected) + }) + + it('can remove a plugin config value', () => { + const plugin = { + ...redisPlugin, + config: {servers: ['first', 'second'], password: ''}, + } + const tp: TelegrafPlugin = { + name: TelegrafPluginInputRedis.NameEnum.Redis, + configured: ConfigurationState.Unconfigured, + active: true, + plugin, + } + const actual = dataLoadersReducer( + {...INITIAL_STATE, telegrafPlugins: [tp]}, + removeConfigValue( + TelegrafPluginInputRedis.NameEnum.Redis, + 'servers', + 'first' + ) + ) + + const expected = { + ...INITIAL_STATE, + telegrafPlugins: [ + { + ...tp, + plugin: { + ...plugin, + config: {servers: ['second'], password: ''}, + }, + }, + ], + } + + expect(actual).toEqual(expected) + }) }) diff --git a/ui/src/onboarding/reducers/dataLoaders.ts b/ui/src/onboarding/reducers/dataLoaders.ts index ca166fabcb..2dc1b53161 100644 --- a/ui/src/onboarding/reducers/dataLoaders.ts +++ b/ui/src/onboarding/reducers/dataLoaders.ts @@ -2,7 +2,10 @@ import _ from 'lodash' // Utils -import {createNewPlugin} from 'src/onboarding/utils/pluginConfigs' +import { + createNewPlugin, + updateConfigFields, +} from 'src/onboarding/utils/pluginConfigs' // Types import {Action} from 'src/onboarding/actions/dataLoaders' @@ -67,10 +70,11 @@ export default (state = INITIAL_STATE, action: Action): DataLoadersState => { return { ...tp, - plugin: { - ...plugin, - [action.payload.field]: action.payload.value, - }, + plugin: updateConfigFields( + plugin, + action.payload.field, + action.payload.value + ), } } return tp @@ -90,10 +94,11 @@ export default (state = INITIAL_STATE, action: Action): DataLoadersState => { return { ...tp, - plugin: { - ...plugin, - [action.payload.fieldName]: updatedConfigFieldValue, - }, + plugin: updateConfigFields( + plugin, + action.payload.fieldName, + updatedConfigFieldValue + ), } } return tp @@ -117,10 +122,11 @@ export default (state = INITIAL_STATE, action: Action): DataLoadersState => { return { ...tp, - plugin: { - ...plugin, - [action.payload.fieldName]: filteredConfigFieldValue, - }, + plugin: updateConfigFields( + plugin, + action.payload.fieldName, + filteredConfigFieldValue + ), } } return tp diff --git a/ui/src/onboarding/utils/pluginConfigs.ts b/ui/src/onboarding/utils/pluginConfigs.ts index 98ffbcc2c8..be19a50dee 100644 --- a/ui/src/onboarding/utils/pluginConfigs.ts +++ b/ui/src/onboarding/utils/pluginConfigs.ts @@ -14,6 +14,16 @@ export const getConfigFields = ( return telegrafPluginsInfo[pluginName].fields } +export const updateConfigFields = ( + plugin: T, + fieldName: string, + value: string[] | string +): T => { + return Object.assign({}, plugin, { + config: Object.assign({}, plugin.config, {[fieldName]: value}), + }) +} + export const createNewPlugin = (name: TelegrafPluginName): Plugin => { return telegrafPluginsInfo[name].defaults } From 1bb276f6bc72a854b16751117b8f1ab9a764ab45 Mon Sep 17 00:00:00 2001 From: Jade McGough Date: Tue, 11 Dec 2018 11:42:13 -0800 Subject: [PATCH 07/22] feat(http): add labels for views (#1800) * feat(http): add labels for views * update swagger * make fmt --- http/api_handler.go | 2 +- http/swagger.yml | 450 ++++++++++++++++++++++++++----------------- http/view_service.go | 22 ++- http/view_test.go | 15 +- 4 files changed, 299 insertions(+), 190 deletions(-) diff --git a/http/api_handler.go b/http/api_handler.go index 3b1e8b0386..7eecd57569 100644 --- a/http/api_handler.go +++ b/http/api_handler.go @@ -90,7 +90,7 @@ func NewAPIHandler(b *APIBackend) *APIHandler { h.DashboardHandler.DashboardService = b.DashboardService h.DashboardHandler.DashboardOperationLogService = b.DashboardOperationLogService - h.ViewHandler = NewViewHandler(b.UserResourceMappingService) + h.ViewHandler = NewViewHandler(b.UserResourceMappingService, b.LabelService) h.ViewHandler.ViewService = b.ViewService h.MacroHandler = NewMacroHandler() diff --git a/http/swagger.yml b/http/swagger.yml index efdaf5b2ae..c661ecf001 100644 --- a/http/swagger.yml +++ b/http/swagger.yml @@ -1091,6 +1091,284 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + '/views/{viewID}/labels': + get: + tags: + - Views + summary: list all labels for a view + parameters: + - in: path + name: viewID + schema: + type: string + required: true + description: ID of the view + responses: + '200': + description: a list of all labels for a view + content: + application/json: + schema: + type: object + properties: + labels: + type: array + items: + type: string + links: + $ref: "#/components/schemas/Links" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + tags: + - Views + summary: add a label to a view + parameters: + - in: path + name: viewID + schema: + type: string + required: true + description: ID of the view + requestBody: + description: label to add + required: true + content: + application/json: + schema: + type: object + properties: + label: + type: string + responses: + '200': + description: a list of all labels for a view + content: + application/json: + schema: + type: object + properties: + labels: + type: array + items: + type: string + links: + $ref: "#/components/schemas/Links" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + '/views/{viewID}/labels/{label}': + delete: + tags: + - Views + summary: delete a label from a view + parameters: + - in: path + name: viewID + schema: + type: string + required: true + description: ID of the view + - in: path + name: label + schema: + type: string + required: true + description: the label name + responses: + '204': + description: delete has been accepted + '404': + description: view not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + '/views/{viewID}/members': + get: + tags: + - Users + - Views + summary: List all view members + parameters: + - in: path + name: viewID + schema: + type: string + required: true + description: ID of the view + responses: + '200': + description: a list of users who have member privileges for a view + content: + application/json: + schema: + $ref: "#/components/schemas/Users" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + tags: + - Users + - Views + summary: Add view member + parameters: + - in: path + name: viewID + schema: + type: string + required: true + description: ID of the view + requestBody: + description: user to add as member + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/User" + responses: + '201': + description: added to view members + content: + application/json: + schema: + $ref: "#/components/schemas/User" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + '/views/{viewID}/members/{userID}': + delete: + tags: + - Users + - Views + summary: removes a member from an view + parameters: + - in: path + name: userID + schema: + type: string + required: true + description: ID of member to remove + - in: path + name: viewID + schema: + type: string + required: true + description: ID of the view + responses: + '204': + description: member removed + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + '/views/{viewID}/owners': + get: + tags: + - Users + - Views + summary: List all view owners + parameters: + - in: path + name: viewID + schema: + type: string + required: true + description: ID of the view + responses: + '200': + description: a list of users who have owner privileges for a view + content: + application/json: + schema: + $ref: "#/components/schemas/Users" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + tags: + - Users + - Views + summary: Add view owner + parameters: + - in: path + name: viewID + schema: + type: string + required: true + description: ID of the view + requestBody: + description: user to add as owner + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/User" + responses: + '201': + description: added to view owners + content: + application/json: + schema: + $ref: "#/components/schemas/User" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + '/views/{viewID}/owners/{userID}': + delete: + tags: + - Users + - Views + summary: removes an owner from a view + parameters: + - in: path + name: userID + schema: + type: string + required: true + description: ID of owner to remove + - in: path + name: viewID + schema: + type: string + required: true + description: ID of the view + responses: + '204': + description: owner removed + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /dashboards: post: tags: @@ -3557,178 +3835,6 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" - '/views/{viewID}/members': - get: - tags: - - Users - - Views - summary: List all view members - parameters: - - in: path - name: viewID - schema: - type: string - required: true - description: ID of the view - responses: - '200': - description: a list of users who have member privileges for a view - content: - application/json: - schema: - $ref: "#/components/schemas/Users" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - post: - tags: - - Users - - Views - summary: Add view member - parameters: - - in: path - name: viewID - schema: - type: string - required: true - description: ID of the view - requestBody: - description: user to add as member - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/User" - responses: - '201': - description: added to view members - content: - application/json: - schema: - $ref: "#/components/schemas/User" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - '/views/{viewID}/members/{userID}': - delete: - tags: - - Users - - Views - summary: removes a member from an view - parameters: - - in: path - name: userID - schema: - type: string - required: true - description: ID of member to remove - - in: path - name: viewID - schema: - type: string - required: true - description: ID of the view - responses: - '204': - description: member removed - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - '/views/{viewID}/owners': - get: - tags: - - Users - - Views - summary: List all view owners - parameters: - - in: path - name: viewID - schema: - type: string - required: true - description: ID of the view - responses: - '200': - description: a list of users who have owner privileges for a view - content: - application/json: - schema: - $ref: "#/components/schemas/Users" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - post: - tags: - - Users - - Views - summary: Add view owner - parameters: - - in: path - name: viewID - schema: - type: string - required: true - description: ID of the view - requestBody: - description: user to add as owner - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/User" - responses: - '201': - description: added to view owners - content: - application/json: - schema: - $ref: "#/components/schemas/User" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - '/views/{viewID}/owners/{userID}': - delete: - tags: - - Users - - Views - summary: removes an owner from a view - parameters: - - in: path - name: userID - schema: - type: string - required: true - description: ID of owner to remove - - in: path - name: viewID - schema: - type: string - required: true - description: ID of the view - responses: - '204': - description: owner removed - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" components: schemas: LanguageRequest: diff --git a/http/view_service.go b/http/view_service.go index 706d0790b5..086191071c 100644 --- a/http/view_service.go +++ b/http/view_service.go @@ -17,22 +17,26 @@ type ViewHandler struct { ViewService platform.ViewService UserResourceMappingService platform.UserResourceMappingService + LabelService platform.LabelService } const ( - viewsPath = "/api/v2/views" - viewsIDPath = "/api/v2/views/:id" - viewsIDMembersPath = "/api/v2/views/:id/members" - viewsIDMembersIDPath = "/api/v2/views/:id/members/:userID" - viewsIDOwnersPath = "/api/v2/views/:id/owners" - viewsIDOwnersIDPath = "/api/v2/views/:id/owners:userID" + viewsPath = "/api/v2/views" + viewsIDPath = "/api/v2/views/:id" + viewsIDMembersPath = "/api/v2/views/:id/members" + viewsIDMembersIDPath = "/api/v2/views/:id/members/:userID" + viewsIDOwnersPath = "/api/v2/views/:id/owners" + viewsIDOwnersIDPath = "/api/v2/views/:id/owners/:userID" + viewsIDLabelsPath = "/api/v2/views/:id/labels" + viewsIDLabelsNamePath = "/api/v2/views/:id/labels/:name" ) // NewViewHandler returns a new instance of ViewHandler. -func NewViewHandler(mappingService platform.UserResourceMappingService) *ViewHandler { +func NewViewHandler(mappingService platform.UserResourceMappingService, labelService platform.LabelService) *ViewHandler { h := &ViewHandler{ Router: httprouter.New(), UserResourceMappingService: mappingService, + LabelService: labelService, } h.HandlerFunc("POST", viewsPath, h.handlePostViews) @@ -50,6 +54,10 @@ func NewViewHandler(mappingService platform.UserResourceMappingService) *ViewHan h.HandlerFunc("GET", viewsIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, platform.Owner)) h.HandlerFunc("DELETE", viewsIDOwnersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Owner)) + h.HandlerFunc("GET", viewsIDLabelsPath, newGetLabelsHandler(h.LabelService)) + h.HandlerFunc("POST", viewsIDLabelsPath, newPostLabelHandler(h.LabelService)) + h.HandlerFunc("DELETE", viewsIDLabelsNamePath, newDeleteLabelHandler(h.LabelService)) + return h } diff --git a/http/view_test.go b/http/view_test.go index 7c3071f31d..8a3e1c82b5 100644 --- a/http/view_test.go +++ b/http/view_test.go @@ -129,8 +129,7 @@ func TestService_handleGetViews(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mappingService := mock.NewUserResourceMappingService() - h := NewViewHandler(mappingService) + h := NewViewHandler(mock.NewUserResourceMappingService(), mock.NewLabelService()) h.ViewService = tt.fields.ViewService r := httptest.NewRequest("GET", "http://any.url", nil) @@ -238,8 +237,7 @@ func TestService_handleGetView(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mappingService := mock.NewUserResourceMappingService() - h := NewViewHandler(mappingService) + h := NewViewHandler(mock.NewUserResourceMappingService(), mock.NewLabelService()) h.ViewService = tt.fields.ViewService r := httptest.NewRequest("GET", "http://any.url", nil) @@ -344,8 +342,7 @@ func TestService_handlePostViews(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mappingService := mock.NewUserResourceMappingService() - h := NewViewHandler(mappingService) + h := NewViewHandler(mock.NewUserResourceMappingService(), mock.NewLabelService()) h.ViewService = tt.fields.ViewService b, err := json.Marshal(tt.args.view) @@ -434,8 +431,7 @@ func TestService_handleDeleteView(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mappingService := mock.NewUserResourceMappingService() - h := NewViewHandler(mappingService) + h := NewViewHandler(mock.NewUserResourceMappingService(), mock.NewLabelService()) h.ViewService = tt.fields.ViewService r := httptest.NewRequest("GET", "http://any.url", nil) @@ -591,8 +587,7 @@ func TestService_handlePatchView(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mappingService := mock.NewUserResourceMappingService() - h := NewViewHandler(mappingService) + h := NewViewHandler(mock.NewUserResourceMappingService(), mock.NewLabelService()) h.ViewService = tt.fields.ViewService upd := platform.ViewUpdate{} From eadce6a4eeb47b50b0c5fb9b2c758bd2fd677abe Mon Sep 17 00:00:00 2001 From: Palak Bhojani Date: Tue, 11 Dec 2018 12:30:00 -0800 Subject: [PATCH 08/22] Add functionality to add new source button to go back to streaming sources selection --- ui/src/onboarding/components/OnboardingSideBar.tsx | 3 +++ ui/src/onboarding/containers/OnboardingWizard.tsx | 2 ++ 2 files changed, 5 insertions(+) diff --git a/ui/src/onboarding/components/OnboardingSideBar.tsx b/ui/src/onboarding/components/OnboardingSideBar.tsx index 88d4a6b6b6..d2cfbb9d48 100644 --- a/ui/src/onboarding/components/OnboardingSideBar.tsx +++ b/ui/src/onboarding/components/OnboardingSideBar.tsx @@ -27,6 +27,7 @@ interface Props { notify: NotificationAction onTabClick: (tabID: string) => void currentStepIndex: number + handleNewSourceClick: () => void } const configStateToTabStatus = (cs: ConfigurationState): TabStatus => { @@ -70,6 +71,7 @@ class OnboardingSideBar extends Component { } private get buttons(): JSX.Element[] { + const {handleNewSourceClick} = this.props return [ { titleText="Add New Source" color={ComponentColor.Default} icon={IconFont.Plus} + onClick={handleNewSourceClick} />, ] } diff --git a/ui/src/onboarding/containers/OnboardingWizard.tsx b/ui/src/onboarding/containers/OnboardingWizard.tsx index 5f33859a2d..fc0df6d919 100644 --- a/ui/src/onboarding/containers/OnboardingWizard.tsx +++ b/ui/src/onboarding/containers/OnboardingWizard.tsx @@ -121,6 +121,7 @@ class OnboardingWizard extends PureComponent { onSaveTelegrafConfig, setupParams, notify, + onDecrementCurrentStepIndex, } = this.props return ( @@ -135,6 +136,7 @@ class OnboardingWizard extends PureComponent { title="Selected Sources" visible={this.sideBarVisible} currentStepIndex={currentStepIndex} + handleNewSourceClick={onDecrementCurrentStepIndex} />
Date: Tue, 11 Dec 2018 12:34:53 -0800 Subject: [PATCH 09/22] chore(http): add org delete endpoint to swagger (#1844) --- http/swagger.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/http/swagger.yml b/http/swagger.yml index c661ecf001..d062a408da 100644 --- a/http/swagger.yml +++ b/http/swagger.yml @@ -2790,6 +2790,32 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + delete: + tags: + - Organizations + summary: Delete an organization + parameters: + - in: path + name: orgID + schema: + type: string + required: true + description: ID of organization to delete + responses: + '204': + description: delete has been accepted + '404': + description: organization not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" '/orgs/{orgID}/labels': get: tags: From c2b1b2d5bb8786e17e1b198dc8227b66761042db Mon Sep 17 00:00:00 2001 From: Deniz Kusefoglu Date: Tue, 11 Dec 2018 14:20:09 -0800 Subject: [PATCH 10/22] Add logos for telegraf plugins as components --- ui/src/onboarding/graphics/LogoApache.tsx | 120 +++++++++++++++++ ui/src/onboarding/graphics/LogoConsul.tsx | 67 +++++++++ ui/src/onboarding/graphics/LogoCpu.tsx | 50 +++++++ ui/src/onboarding/graphics/LogoDocker.tsx | 92 +++++++++++++ ui/src/onboarding/graphics/LogoElastic.tsx | 80 +++++++++++ ui/src/onboarding/graphics/LogoEtcd.tsx | 38 ++++++ ui/src/onboarding/graphics/LogoIis.tsx | 93 +++++++++++++ ui/src/onboarding/graphics/LogoKubernetes.tsx | 41 ++++++ ui/src/onboarding/graphics/LogoMesos.tsx | 127 ++++++++++++++++++ ui/src/onboarding/graphics/LogoMongodb.tsx | 76 +++++++++++ ui/src/onboarding/graphics/LogoMysql.tsx | 43 ++++++ ui/src/onboarding/graphics/LogoNginx.tsx | 39 ++++++ ui/src/onboarding/graphics/index.ts | 27 ++++ 13 files changed, 893 insertions(+) create mode 100644 ui/src/onboarding/graphics/LogoApache.tsx create mode 100644 ui/src/onboarding/graphics/LogoConsul.tsx create mode 100644 ui/src/onboarding/graphics/LogoCpu.tsx create mode 100644 ui/src/onboarding/graphics/LogoDocker.tsx create mode 100644 ui/src/onboarding/graphics/LogoElastic.tsx create mode 100644 ui/src/onboarding/graphics/LogoEtcd.tsx create mode 100644 ui/src/onboarding/graphics/LogoIis.tsx create mode 100644 ui/src/onboarding/graphics/LogoKubernetes.tsx create mode 100644 ui/src/onboarding/graphics/LogoMesos.tsx create mode 100644 ui/src/onboarding/graphics/LogoMongodb.tsx create mode 100644 ui/src/onboarding/graphics/LogoMysql.tsx create mode 100644 ui/src/onboarding/graphics/LogoNginx.tsx create mode 100644 ui/src/onboarding/graphics/index.ts diff --git a/ui/src/onboarding/graphics/LogoApache.tsx b/ui/src/onboarding/graphics/LogoApache.tsx new file mode 100644 index 0000000000..45be1ec977 --- /dev/null +++ b/ui/src/onboarding/graphics/LogoApache.tsx @@ -0,0 +1,120 @@ +// Libraries +import React, {SFC} from 'react' + +interface Props { + width?: number + height?: number +} + +const LogoApache: SFC = ({height, width}) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + {'logo_apache'} + + + + + + + + + ) +} + +export default LogoApache diff --git a/ui/src/onboarding/graphics/LogoConsul.tsx b/ui/src/onboarding/graphics/LogoConsul.tsx new file mode 100644 index 0000000000..1724cefbef --- /dev/null +++ b/ui/src/onboarding/graphics/LogoConsul.tsx @@ -0,0 +1,67 @@ +// Libraries +import React, {SFC} from 'react' + +interface Props { + width?: number + height?: number +} + +const LogoConsul: SFC = ({height, width}) => { + return ( + + + + + + + + + + + + + + ) +} + +export default LogoConsul diff --git a/ui/src/onboarding/graphics/LogoCpu.tsx b/ui/src/onboarding/graphics/LogoCpu.tsx new file mode 100644 index 0000000000..53fc3dce9e --- /dev/null +++ b/ui/src/onboarding/graphics/LogoCpu.tsx @@ -0,0 +1,50 @@ +// Libraries +import React, {SFC} from 'react' + +interface Props { + width?: number + height?: number +} + +const LogoCpu: SFC = ({height, width}) => { + return ( + + + + + + + + + + + + + + + + + + + ) +} + +export default LogoCpu diff --git a/ui/src/onboarding/graphics/LogoDocker.tsx b/ui/src/onboarding/graphics/LogoDocker.tsx new file mode 100644 index 0000000000..8ea158bc22 --- /dev/null +++ b/ui/src/onboarding/graphics/LogoDocker.tsx @@ -0,0 +1,92 @@ +// Libraries +import React, {SFC} from 'react' + +interface Props { + width?: number + height?: number +} + +const LogoDocker: SFC = ({height, width}) => { + return ( + + + + + + + + + + + + + + + ) +} + +export default LogoDocker diff --git a/ui/src/onboarding/graphics/LogoElastic.tsx b/ui/src/onboarding/graphics/LogoElastic.tsx new file mode 100644 index 0000000000..54efe379d9 --- /dev/null +++ b/ui/src/onboarding/graphics/LogoElastic.tsx @@ -0,0 +1,80 @@ +// Libraries +import React, {SFC} from 'react' + +interface Props { + width?: number + height?: number +} + +const LogoElastic: SFC = ({height, width}) => { + return ( + + + + + + + + + + + + ) +} + +export default LogoElastic diff --git a/ui/src/onboarding/graphics/LogoEtcd.tsx b/ui/src/onboarding/graphics/LogoEtcd.tsx new file mode 100644 index 0000000000..7afad905ae --- /dev/null +++ b/ui/src/onboarding/graphics/LogoEtcd.tsx @@ -0,0 +1,38 @@ +// Libraries +import React, {SFC} from 'react' + +interface Props { + width?: number + height?: number +} + +const LogoEtcd: SFC = ({height, width}) => { + return ( + + + + + + + + ) +} + +export default LogoEtcd diff --git a/ui/src/onboarding/graphics/LogoIis.tsx b/ui/src/onboarding/graphics/LogoIis.tsx new file mode 100644 index 0000000000..b64f4eee50 --- /dev/null +++ b/ui/src/onboarding/graphics/LogoIis.tsx @@ -0,0 +1,93 @@ +// Libraries +import React, {SFC} from 'react' + +interface Props { + width?: number + height?: number +} + +const LogoIis: SFC = ({height, width}) => { + return ( + + + + + + + + + + + + + + + + + {'logo_iis'} + + + + + + + + ) +} + +export default LogoIis diff --git a/ui/src/onboarding/graphics/LogoKubernetes.tsx b/ui/src/onboarding/graphics/LogoKubernetes.tsx new file mode 100644 index 0000000000..886101e5d0 --- /dev/null +++ b/ui/src/onboarding/graphics/LogoKubernetes.tsx @@ -0,0 +1,41 @@ +// Libraries +import React, {SFC} from 'react' + +interface Props { + width?: number + height?: number +} + +const LogoKubernetes: SFC = ({height, width}) => { + return ( + + + + + + + ) +} + +export default LogoKubernetes diff --git a/ui/src/onboarding/graphics/LogoMesos.tsx b/ui/src/onboarding/graphics/LogoMesos.tsx new file mode 100644 index 0000000000..0a44bd8334 --- /dev/null +++ b/ui/src/onboarding/graphics/LogoMesos.tsx @@ -0,0 +1,127 @@ +// Libraries +import React, {SFC} from 'react' + +interface Props { + width?: number + height?: number +} + +const LogoMesos: SFC = ({height, width}) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export default LogoMesos diff --git a/ui/src/onboarding/graphics/LogoMongodb.tsx b/ui/src/onboarding/graphics/LogoMongodb.tsx new file mode 100644 index 0000000000..a4982dff4c --- /dev/null +++ b/ui/src/onboarding/graphics/LogoMongodb.tsx @@ -0,0 +1,76 @@ +// Libraries +import React, {SFC} from 'react' + +interface Props { + width?: number + height?: number +} + +const LogoMongodb: SFC = ({height, width}) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + {'logo_mongodb'} + + + + + ) +} + +export default LogoMongodb diff --git a/ui/src/onboarding/graphics/LogoMysql.tsx b/ui/src/onboarding/graphics/LogoMysql.tsx new file mode 100644 index 0000000000..03a3fb5524 --- /dev/null +++ b/ui/src/onboarding/graphics/LogoMysql.tsx @@ -0,0 +1,43 @@ +// Libraries +import React, {SFC} from 'react' + +interface Props { + width?: number + height?: number +} + +const LogoMysql: SFC = ({height, width}) => { + return ( + + + + + + + ) +} + +export default LogoMysql diff --git a/ui/src/onboarding/graphics/LogoNginx.tsx b/ui/src/onboarding/graphics/LogoNginx.tsx new file mode 100644 index 0000000000..5c5d07dcae --- /dev/null +++ b/ui/src/onboarding/graphics/LogoNginx.tsx @@ -0,0 +1,39 @@ +// Libraries +import React, {SFC} from 'react' + +interface Props { + width?: number + height?: number +} + +const LogoNginx: SFC = ({height, width}) => { + return ( + + + + + + + ) +} + +export default LogoNginx diff --git a/ui/src/onboarding/graphics/index.ts b/ui/src/onboarding/graphics/index.ts new file mode 100644 index 0000000000..aec5faa8c3 --- /dev/null +++ b/ui/src/onboarding/graphics/index.ts @@ -0,0 +1,27 @@ +import LogoApache from 'src/onboarding/graphics/LogoApache' +import LogoConsul from 'src/onboarding/graphics/LogoConsul' +import LogoCpu from 'src/onboarding/graphics/LogoCpu' +import LogoDocker from 'src/onboarding/graphics/LogoDocker' +import LogoElastic from 'src/onboarding/graphics/LogoElastic' +import LogoEtcd from 'src/onboarding/graphics/LogoEtcd' +import LogoIis from 'src/onboarding/graphics/LogoIis' +import LogoKubernetes from 'src/onboarding/graphics/LogoKubernetes' +import LogoMesos from 'src/onboarding/graphics/LogoMesos' +import LogoMongodb from 'src/onboarding/graphics/LogoMongodb' +import LogoMysql from 'src/onboarding/graphics/LogoMysql' +import LogoNginx from 'src/onboarding/graphics/LogoNginx' + +export { + LogoApache, + LogoConsul, + LogoCpu, + LogoDocker, + LogoElastic, + LogoEtcd, + LogoIis, + LogoKubernetes, + LogoMongodb, + LogoMesos, + LogoMysql, + LogoNginx, +} From 079d461d345a4fea60607d63254294eeff3f6bbb Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Tue, 11 Dec 2018 15:17:44 -0800 Subject: [PATCH 11/22] Document endpoints used by the front end --- http/cur_swagger.yml | 181 ++++++++++++++++++++++++------------------- 1 file changed, 103 insertions(+), 78 deletions(-) diff --git a/http/cur_swagger.yml b/http/cur_swagger.yml index f1a6bad409..1bc4c0c2af 100644 --- a/http/cur_swagger.yml +++ b/http/cur_swagger.yml @@ -630,6 +630,7 @@ paths: $ref: "#/components/schemas/Error" /sources: post: + x-generated: true tags: - Sources summary: Creates a Source @@ -661,6 +662,7 @@ paths: schema: $ref: "#/components/schemas/Error" get: + x-generated: true tags: - Sources summary: Get all sources @@ -686,6 +688,7 @@ paths: $ref: "#/components/schemas/Error" /sources/{sourceID}: delete: + x-generated: true tags: - Sources summary: Delete a source @@ -712,6 +715,7 @@ paths: schema: $ref: "#/components/schemas/Error" patch: + x-generated: true tags: - Sources summary: Updates a Source @@ -749,6 +753,7 @@ paths: schema: $ref: "#/components/schemas/Error" get: + x-generated: true tags: - Sources summary: Get a source @@ -780,6 +785,7 @@ paths: $ref: "#/components/schemas/Error" /sources/{sourceID}/health: get: + x-generated: true tags: - Sources summary: Get a sources health @@ -849,6 +855,7 @@ paths: $ref: "#/components/schemas/Error" /views: post: + x-generated: true tags: - Views summary: A view contains information about the visual representation of data @@ -880,6 +887,7 @@ paths: schema: $ref: "#/components/schemas/Error" get: + x-generated: true tags: - Views summary: Get all views @@ -904,37 +912,39 @@ paths: schema: $ref: "#/components/schemas/Error" '/views/{viewID}': - get: - tags: - - Views - summary: Get a single View - parameters: - - in: path - name: viewID - schema: - type: string - required: true - description: ID of view to update - responses: - '200': - description: get a single view - content: - application/json: - schema: - $ref: "#/components/schemas/View" - '404': - description: view not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - patch: + get: + x-generated: true + tags: + - Views + summary: Get a single View + parameters: + - in: path + name: viewID + schema: + type: string + required: true + description: ID of view to update + responses: + '200': + description: get a single view + content: + application/json: + schema: + $ref: "#/components/schemas/View" + '404': + description: view not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + patch: + x-generated: true tags: - Views summary: Update a single view @@ -971,7 +981,8 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" - delete: + delete: + x-generated: true tags: - Views summary: Delete a view @@ -999,6 +1010,7 @@ paths: $ref: "#/components/schemas/Error" /dashboards: post: + x-generated: true tags: - Dashboards summary: Create a dashboard @@ -1030,6 +1042,7 @@ paths: schema: $ref: "#/components/schemas/Error" get: + x-generated: true tags: - Dashboards summary: Get all dashboards @@ -1069,47 +1082,49 @@ paths: schema: $ref: "#/components/schemas/Error" '/dashboards/{dashboardID}': - get: - tags: - - Dashboards - summary: Get a single Dashboard - parameters: - - in: path - name: dashboardID - schema: - type: string - required: true - description: ID of dashboard to update - responses: - '200': - description: get a single dashboard - content: - application/json: - schema: - $ref: "#/components/schemas/Dashboard" - '404': - description: dashboard not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - patch: + get: + x-generated: true + tags: + - Dashboards + summary: Get a single Dashboard + parameters: + - in: path + name: dashboardID + schema: + type: string + required: true + description: ID of dashboard to update + responses: + '200': + description: get a single dashboard + content: + application/json: + schema: + $ref: "#/components/schemas/Dashboard" + '404': + description: dashboard not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + patch: + x-generated: true tags: - Dashboards summary: Update a single dashboard requestBody: - description: patching of a dashboard - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Dashboard" + description: patching of a dashboard + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Dashboard" parameters: - in: path name: dashboardID @@ -1136,7 +1151,8 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" - delete: + delete: + x-generated: true tags: - Dashboards summary: Delete a dashboard @@ -1163,18 +1179,19 @@ paths: schema: $ref: "#/components/schemas/Error" '/dashboards/{dashboardID}/cells': - put: + put: + x-generated: true tags: - Cells - Dashboards summary: Replace a dashboards cells requestBody: - description: batch replaces all of a dashboards cells (this is used primarily to update the positional information of all of the cells) - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Cells" + description: batch replaces all of a dashboards cells (this is used primarily to update the positional information of all of the cells) + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Cells" parameters: - in: path name: dashboardID @@ -1201,7 +1218,8 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" - post: + post: + x-generated: true tags: - Cells - Dashboards @@ -1241,6 +1259,7 @@ paths: $ref: "#/components/schemas/Error" '/dashboards/{dashboardID}/cells/{cellID}': patch: + x-generated: true tags: - Cells - Dashboards @@ -1285,6 +1304,7 @@ paths: schema: $ref: "#/components/schemas/Error" delete: + x-generated: true tags: - Cells - Dashboards @@ -2487,6 +2507,7 @@ paths: $ref: "#/components/schemas/Error" /tasks: get: + x-generated: true tags: - Tasks summary: List tasks. @@ -2528,6 +2549,7 @@ paths: schema: $ref: "#/components/schemas/Error" post: + x-generated: true tags: - Tasks summary: Create a new task @@ -2553,6 +2575,7 @@ paths: $ref: "#/components/schemas/Error" '/tasks/{taskID}': get: + x-generated: true tags: - Tasks summary: Retrieve an task @@ -2577,6 +2600,7 @@ paths: schema: $ref: "#/components/schemas/Error" patch: + x-generated: true tags: - Tasks summary: Update a task @@ -2609,6 +2633,7 @@ paths: schema: $ref: "#/components/schemas/Error" delete: + x-generated: true tags: - Tasks summary: Delete a task From 8f8311a3ae2bb135384f0eb375f2e72c8b701995 Mon Sep 17 00:00:00 2001 From: Stuart Carnie Date: Mon, 10 Dec 2018 13:53:16 -0700 Subject: [PATCH 12/22] chore(gen): Add ingen generator data structures to platform for reuse --- pkg/data/gen/arrays.gen.go | 97 +++++++ pkg/data/gen/arrays.gen.go.tmpl | 26 ++ pkg/data/gen/gen.go | 3 + pkg/{testing => data}/gen/sequence.go | 10 +- pkg/data/gen/series_generator.go | 37 +++ pkg/{testing => data}/gen/tags_sequence.go | 8 +- pkg/data/gen/types.tmpldata | 27 ++ pkg/data/gen/util.go | 8 + pkg/data/gen/values_constant.gen.go | 308 +++++++++++++++++++++ pkg/data/gen/values_constant.gen.go.tmpl | 68 +++++ pkg/data/gen/values_sequence.go | 60 ++++ storage/reads/group_resultset_test.go | 4 +- 12 files changed, 647 insertions(+), 9 deletions(-) create mode 100644 pkg/data/gen/arrays.gen.go create mode 100644 pkg/data/gen/arrays.gen.go.tmpl create mode 100644 pkg/data/gen/gen.go rename pkg/{testing => data}/gen/sequence.go (86%) create mode 100644 pkg/data/gen/series_generator.go rename pkg/{testing => data}/gen/tags_sequence.go (86%) create mode 100644 pkg/data/gen/types.tmpldata create mode 100644 pkg/data/gen/util.go create mode 100644 pkg/data/gen/values_constant.gen.go create mode 100644 pkg/data/gen/values_constant.gen.go.tmpl create mode 100644 pkg/data/gen/values_sequence.go diff --git a/pkg/data/gen/arrays.gen.go b/pkg/data/gen/arrays.gen.go new file mode 100644 index 0000000000..302911d6b1 --- /dev/null +++ b/pkg/data/gen/arrays.gen.go @@ -0,0 +1,97 @@ +// Generated by tmpl +// https://github.com/benbjohnson/tmpl +// +// DO NOT EDIT! +// Source: arrays.gen.go.tmpl + +package gen + +import ( + "github.com/influxdata/platform/tsdb/cursors" + "github.com/influxdata/platform/tsdb/tsm1" +) + +type FloatArray struct { + cursors.FloatArray +} + +func NewFloatArrayLen(sz int) *FloatArray { + return &FloatArray{ + FloatArray: cursors.FloatArray{ + Timestamps: make([]int64, sz), + Values: make([]float64, sz), + }, + } +} + +func (a *FloatArray) Encode(b []byte) ([]byte, error) { + return tsm1.EncodeFloatArrayBlock(&a.FloatArray, b) +} + +type IntegerArray struct { + cursors.IntegerArray +} + +func NewIntegerArrayLen(sz int) *IntegerArray { + return &IntegerArray{ + IntegerArray: cursors.IntegerArray{ + Timestamps: make([]int64, sz), + Values: make([]int64, sz), + }, + } +} + +func (a *IntegerArray) Encode(b []byte) ([]byte, error) { + return tsm1.EncodeIntegerArrayBlock(&a.IntegerArray, b) +} + +type UnsignedArray struct { + cursors.UnsignedArray +} + +func NewUnsignedArrayLen(sz int) *UnsignedArray { + return &UnsignedArray{ + UnsignedArray: cursors.UnsignedArray{ + Timestamps: make([]int64, sz), + Values: make([]uint64, sz), + }, + } +} + +func (a *UnsignedArray) Encode(b []byte) ([]byte, error) { + return tsm1.EncodeUnsignedArrayBlock(&a.UnsignedArray, b) +} + +type StringArray struct { + cursors.StringArray +} + +func NewStringArrayLen(sz int) *StringArray { + return &StringArray{ + StringArray: cursors.StringArray{ + Timestamps: make([]int64, sz), + Values: make([]string, sz), + }, + } +} + +func (a *StringArray) Encode(b []byte) ([]byte, error) { + return tsm1.EncodeStringArrayBlock(&a.StringArray, b) +} + +type BooleanArray struct { + cursors.BooleanArray +} + +func NewBooleanArrayLen(sz int) *BooleanArray { + return &BooleanArray{ + BooleanArray: cursors.BooleanArray{ + Timestamps: make([]int64, sz), + Values: make([]bool, sz), + }, + } +} + +func (a *BooleanArray) Encode(b []byte) ([]byte, error) { + return tsm1.EncodeBooleanArrayBlock(&a.BooleanArray, b) +} diff --git a/pkg/data/gen/arrays.gen.go.tmpl b/pkg/data/gen/arrays.gen.go.tmpl new file mode 100644 index 0000000000..c7d99df68a --- /dev/null +++ b/pkg/data/gen/arrays.gen.go.tmpl @@ -0,0 +1,26 @@ +package gen + +import ( + "github.com/influxdata/platform/tsdb/tsm1" + "github.com/influxdata/platform/tsdb/cursors" +) + +{{range .}} +{{ $typename := print .Name "Array" }} +type {{$typename}} struct { + cursors.{{$typename}} +} + +func New{{$typename}}Len(sz int) *{{$typename}} { + return &{{$typename}}{ + {{$typename}}: cursors.{{$typename}}{ + Timestamps: make([]int64, sz), + Values: make([]{{.Type}}, sz), + }, + } +} + +func (a *{{$typename}}) Encode(b []byte) ([]byte, error) { + return tsm1.Encode{{$typename}}Block(&a.{{$typename}}, b) +} +{{end}} \ No newline at end of file diff --git a/pkg/data/gen/gen.go b/pkg/data/gen/gen.go new file mode 100644 index 0000000000..1ced3ec890 --- /dev/null +++ b/pkg/data/gen/gen.go @@ -0,0 +1,3 @@ +package gen + +//go:generate env GO111MODULE=on go run github.com/benbjohnson/tmpl -data=@types.tmpldata arrays.gen.go.tmpl values_constant.gen.go.tmpl diff --git a/pkg/testing/gen/sequence.go b/pkg/data/gen/sequence.go similarity index 86% rename from pkg/testing/gen/sequence.go rename to pkg/data/gen/sequence.go index 3942998821..1b843a462f 100644 --- a/pkg/testing/gen/sequence.go +++ b/pkg/data/gen/sequence.go @@ -8,6 +8,10 @@ import ( type Sequence interface { Next() bool Value() string +} + +type CountableSequence interface { + Sequence Count() int } @@ -49,11 +53,11 @@ func (s *CounterByteSequence) update() { s.val = fmt.Sprintf(s.format, fmt.Sprintf(s.nfmt, s.v)) } -func (s *CounterByteSequence) Count() int { return s.end - s.s } func (s *CounterByteSequence) Value() string { return s.val } +func (s *CounterByteSequence) Count() int { return s.end - s.s } type ConstantStringSequence string -func (ConstantStringSequence) Next() bool { return true } +func (s ConstantStringSequence) Next() bool { return true } func (s ConstantStringSequence) Value() string { return string(s) } -func (ConstantStringSequence) Count() int { return 1 } +func (s ConstantStringSequence) Count() int { return 1 } diff --git a/pkg/data/gen/series_generator.go b/pkg/data/gen/series_generator.go new file mode 100644 index 0000000000..2d46f6cb81 --- /dev/null +++ b/pkg/data/gen/series_generator.go @@ -0,0 +1,37 @@ +package gen + +import ( + "github.com/influxdata/platform/models" +) + +type SeriesGenerator interface { + // Next advances the series generator to the next series key. + Next() bool + + // Name returns the name of the measurement. + // The returned value may be modified by a subsequent call to Next. + Name() []byte + + // Tags returns the tag set. + // The returned value may be modified by a subsequent call to Next. + Tags() models.Tags + + // Field returns the name of the field. + // The returned value may be modified by a subsequent call to Next. + Field() []byte + + // ValuesGenerator returns a values sequence for the current series. + ValuesGenerator() ValuesSequence +} + +type ValuesSequence interface { + Reset() + Next() bool + Values() Values +} + +type Values interface { + MinTime() int64 + MaxTime() int64 + Encode([]byte) ([]byte, error) +} diff --git a/pkg/testing/gen/tags_sequence.go b/pkg/data/gen/tags_sequence.go similarity index 86% rename from pkg/testing/gen/tags_sequence.go rename to pkg/data/gen/tags_sequence.go index 5cd5e6d47a..d121d54a69 100644 --- a/pkg/testing/gen/tags_sequence.go +++ b/pkg/data/gen/tags_sequence.go @@ -16,12 +16,12 @@ type TagsSequence interface { type TagsValuesSequence struct { tags models.Tags - vals []Sequence + vals []CountableSequence n int max int } -func NewTagsValuesSequenceKeysValues(keys []string, vals []Sequence) *TagsValuesSequence { +func NewTagsValuesSequenceKeysValues(keys []string, vals []CountableSequence) *TagsValuesSequence { tm := make(map[string]string, len(keys)) for _, k := range keys { tm[k] = "" @@ -42,7 +42,7 @@ func NewTagsValuesSequenceKeysValues(keys []string, vals []Sequence) *TagsValues } } -func NewTagsValuesSequenceValues(prefix string, vals []Sequence) *TagsValuesSequence { +func NewTagsValuesSequenceValues(prefix string, vals []CountableSequence) *TagsValuesSequence { keys := make([]string, len(vals)) // max tag width tw := int(math.Ceil(math.Log10(float64(len(vals))))) @@ -82,7 +82,7 @@ func (s *TagsValuesSequence) Count() int { return s.max } type keyValues struct { keys []string - vals []Sequence + vals []CountableSequence } func (k keyValues) Len() int { return len(k.keys) } diff --git a/pkg/data/gen/types.tmpldata b/pkg/data/gen/types.tmpldata new file mode 100644 index 0000000000..b15cb586d5 --- /dev/null +++ b/pkg/data/gen/types.tmpldata @@ -0,0 +1,27 @@ +[ + { + "Name":"Float", + "name":"float", + "Type":"float64" + }, + { + "Name":"Integer", + "name":"integer", + "Type":"int64" + }, + { + "Name":"Unsigned", + "name":"unsigned", + "Type":"uint64" + }, + { + "Name":"String", + "name":"string", + "Type":"string" + }, + { + "Name":"Boolean", + "name":"boolean", + "Type":"bool" + } +] diff --git a/pkg/data/gen/util.go b/pkg/data/gen/util.go new file mode 100644 index 0000000000..ba74cfd53f --- /dev/null +++ b/pkg/data/gen/util.go @@ -0,0 +1,8 @@ +package gen + +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/pkg/data/gen/values_constant.gen.go b/pkg/data/gen/values_constant.gen.go new file mode 100644 index 0000000000..888f48379e --- /dev/null +++ b/pkg/data/gen/values_constant.gen.go @@ -0,0 +1,308 @@ +// Generated by tmpl +// https://github.com/benbjohnson/tmpl +// +// DO NOT EDIT! +// Source: values_constant.gen.go.tmpl + +package gen + +import ( + "time" + + "github.com/influxdata/platform/tsdb/cursors" +) + +type FloatConstantValuesSequence struct { + vals FloatArray + n int + t int64 + state struct { + n int + t int64 + d int64 + v float64 + } +} + +func NewFloatConstantValuesSequence(n int, start time.Time, delta time.Duration, v float64) *FloatConstantValuesSequence { + g := &FloatConstantValuesSequence{ + vals: *NewFloatArrayLen(cursors.DefaultMaxPointsPerBlock), + } + g.state.n = n + g.state.t = start.UnixNano() + g.state.d = int64(delta) + g.state.v = v + g.Reset() + return g +} + +func (g *FloatConstantValuesSequence) Reset() { + g.n = g.state.n + g.t = g.state.t +} + +func (g *FloatConstantValuesSequence) Next() bool { + if g.n == 0 { + return false + } + + c := min(g.n, cursors.DefaultMaxPointsPerBlock) + g.n -= c + g.vals.Timestamps = g.vals.Timestamps[:c] + g.vals.Values = g.vals.Values[:c] + + var ( + t = g.t + ts = g.vals.Timestamps + vs = g.vals.Values + d = g.state.d + ) + for i := 0; i < len(ts) && i < len(vs); i++ { + ts[i] = g.t + vs[i] = g.state.v + t += d + } + g.t = t + + return true +} + +func (g *FloatConstantValuesSequence) Values() Values { + return &g.vals +} + +type IntegerConstantValuesSequence struct { + vals IntegerArray + n int + t int64 + state struct { + n int + t int64 + d int64 + v int64 + } +} + +func NewIntegerConstantValuesSequence(n int, start time.Time, delta time.Duration, v int64) *IntegerConstantValuesSequence { + g := &IntegerConstantValuesSequence{ + vals: *NewIntegerArrayLen(cursors.DefaultMaxPointsPerBlock), + } + g.state.n = n + g.state.t = start.UnixNano() + g.state.d = int64(delta) + g.state.v = v + g.Reset() + return g +} + +func (g *IntegerConstantValuesSequence) Reset() { + g.n = g.state.n + g.t = g.state.t +} + +func (g *IntegerConstantValuesSequence) Next() bool { + if g.n == 0 { + return false + } + + c := min(g.n, cursors.DefaultMaxPointsPerBlock) + g.n -= c + g.vals.Timestamps = g.vals.Timestamps[:c] + g.vals.Values = g.vals.Values[:c] + + var ( + t = g.t + ts = g.vals.Timestamps + vs = g.vals.Values + d = g.state.d + ) + for i := 0; i < len(ts) && i < len(vs); i++ { + ts[i] = g.t + vs[i] = g.state.v + t += d + } + g.t = t + + return true +} + +func (g *IntegerConstantValuesSequence) Values() Values { + return &g.vals +} + +type UnsignedConstantValuesSequence struct { + vals UnsignedArray + n int + t int64 + state struct { + n int + t int64 + d int64 + v uint64 + } +} + +func NewUnsignedConstantValuesSequence(n int, start time.Time, delta time.Duration, v uint64) *UnsignedConstantValuesSequence { + g := &UnsignedConstantValuesSequence{ + vals: *NewUnsignedArrayLen(cursors.DefaultMaxPointsPerBlock), + } + g.state.n = n + g.state.t = start.UnixNano() + g.state.d = int64(delta) + g.state.v = v + g.Reset() + return g +} + +func (g *UnsignedConstantValuesSequence) Reset() { + g.n = g.state.n + g.t = g.state.t +} + +func (g *UnsignedConstantValuesSequence) Next() bool { + if g.n == 0 { + return false + } + + c := min(g.n, cursors.DefaultMaxPointsPerBlock) + g.n -= c + g.vals.Timestamps = g.vals.Timestamps[:c] + g.vals.Values = g.vals.Values[:c] + + var ( + t = g.t + ts = g.vals.Timestamps + vs = g.vals.Values + d = g.state.d + ) + for i := 0; i < len(ts) && i < len(vs); i++ { + ts[i] = g.t + vs[i] = g.state.v + t += d + } + g.t = t + + return true +} + +func (g *UnsignedConstantValuesSequence) Values() Values { + return &g.vals +} + +type StringConstantValuesSequence struct { + vals StringArray + n int + t int64 + state struct { + n int + t int64 + d int64 + v string + } +} + +func NewStringConstantValuesSequence(n int, start time.Time, delta time.Duration, v string) *StringConstantValuesSequence { + g := &StringConstantValuesSequence{ + vals: *NewStringArrayLen(cursors.DefaultMaxPointsPerBlock), + } + g.state.n = n + g.state.t = start.UnixNano() + g.state.d = int64(delta) + g.state.v = v + g.Reset() + return g +} + +func (g *StringConstantValuesSequence) Reset() { + g.n = g.state.n + g.t = g.state.t +} + +func (g *StringConstantValuesSequence) Next() bool { + if g.n == 0 { + return false + } + + c := min(g.n, cursors.DefaultMaxPointsPerBlock) + g.n -= c + g.vals.Timestamps = g.vals.Timestamps[:c] + g.vals.Values = g.vals.Values[:c] + + var ( + t = g.t + ts = g.vals.Timestamps + vs = g.vals.Values + d = g.state.d + ) + for i := 0; i < len(ts) && i < len(vs); i++ { + ts[i] = g.t + vs[i] = g.state.v + t += d + } + g.t = t + + return true +} + +func (g *StringConstantValuesSequence) Values() Values { + return &g.vals +} + +type BooleanConstantValuesSequence struct { + vals BooleanArray + n int + t int64 + state struct { + n int + t int64 + d int64 + v bool + } +} + +func NewBooleanConstantValuesSequence(n int, start time.Time, delta time.Duration, v bool) *BooleanConstantValuesSequence { + g := &BooleanConstantValuesSequence{ + vals: *NewBooleanArrayLen(cursors.DefaultMaxPointsPerBlock), + } + g.state.n = n + g.state.t = start.UnixNano() + g.state.d = int64(delta) + g.state.v = v + g.Reset() + return g +} + +func (g *BooleanConstantValuesSequence) Reset() { + g.n = g.state.n + g.t = g.state.t +} + +func (g *BooleanConstantValuesSequence) Next() bool { + if g.n == 0 { + return false + } + + c := min(g.n, cursors.DefaultMaxPointsPerBlock) + g.n -= c + g.vals.Timestamps = g.vals.Timestamps[:c] + g.vals.Values = g.vals.Values[:c] + + var ( + t = g.t + ts = g.vals.Timestamps + vs = g.vals.Values + d = g.state.d + ) + for i := 0; i < len(ts) && i < len(vs); i++ { + ts[i] = g.t + vs[i] = g.state.v + t += d + } + g.t = t + + return true +} + +func (g *BooleanConstantValuesSequence) Values() Values { + return &g.vals +} diff --git a/pkg/data/gen/values_constant.gen.go.tmpl b/pkg/data/gen/values_constant.gen.go.tmpl new file mode 100644 index 0000000000..d86bcfb836 --- /dev/null +++ b/pkg/data/gen/values_constant.gen.go.tmpl @@ -0,0 +1,68 @@ +package gen + +import ( + "time" + + "github.com/influxdata/platform/tsdb/cursors" +) + +{{range .}} +type {{.Name}}ConstantValuesSequence struct { + vals {{.Name}}Array + n int + t int64 + state struct { + n int + t int64 + d int64 + v {{.Type}} + } +} + +func New{{.Name}}ConstantValuesSequence(n int, start time.Time, delta time.Duration, v {{.Type}}) *{{.Name}}ConstantValuesSequence { + g := &{{.Name}}ConstantValuesSequence{ + vals: *New{{.Name}}ArrayLen(cursors.DefaultMaxPointsPerBlock), + } + g.state.n = n + g.state.t = start.UnixNano() + g.state.d = int64(delta) + g.state.v = v + g.Reset() + return g +} + +func (g *{{.Name}}ConstantValuesSequence) Reset() { + g.n = g.state.n + g.t = g.state.t +} + +func (g *{{.Name}}ConstantValuesSequence) Next() bool { + if g.n == 0 { + return false + } + + c := min(g.n, cursors.DefaultMaxPointsPerBlock) + g.n -= c + g.vals.Timestamps = g.vals.Timestamps[:c] + g.vals.Values = g.vals.Values[:c] + + var ( + t = g.t + ts = g.vals.Timestamps + vs = g.vals.Values + d = g.state.d + ) + for i := 0; i < len(ts) && i < len(vs); i++ { + ts[i] = g.t + vs[i] = g.state.v + t += d + } + g.t = t + + return true +} + +func (g *{{.Name}}ConstantValuesSequence) Values() Values { + return &g.vals +} +{{end}} \ No newline at end of file diff --git a/pkg/data/gen/values_sequence.go b/pkg/data/gen/values_sequence.go new file mode 100644 index 0000000000..8f45a1b989 --- /dev/null +++ b/pkg/data/gen/values_sequence.go @@ -0,0 +1,60 @@ +package gen + +import ( + "math/rand" + "time" + + "github.com/influxdata/platform/tsdb/cursors" +) + +type FloatRandomValuesSequence struct { + buf FloatArray + vals FloatArray + n int + t int64 + state struct { + n int + t int64 + d int64 + scale float64 + } +} + +func NewFloatRandomValuesSequence(n int, start time.Time, delta time.Duration, scale float64) *FloatRandomValuesSequence { + g := &FloatRandomValuesSequence{ + buf: *NewFloatArrayLen(cursors.DefaultMaxPointsPerBlock), + } + g.state.n = n + g.state.t = start.UnixNano() + g.state.d = int64(delta) + g.state.scale = scale + g.Reset() + return g +} + +func (g *FloatRandomValuesSequence) Reset() { + g.n = g.state.n + g.t = g.state.t +} + +func (g *FloatRandomValuesSequence) Next() bool { + if g.n == 0 { + return false + } + + c := min(g.n, cursors.DefaultMaxPointsPerBlock) + g.n -= c + g.vals.Timestamps = g.buf.Timestamps[:0] + g.vals.Values = g.buf.Values[:0] + + for i := 0; i < c; i++ { + g.vals.Timestamps = append(g.vals.Timestamps, g.t) + g.vals.Values = append(g.vals.Values, rand.Float64()*g.state.scale) + g.t += g.state.d + } + return true +} + +func (g *FloatRandomValuesSequence) Values() Values { + return &g.vals +} diff --git a/storage/reads/group_resultset_test.go b/storage/reads/group_resultset_test.go index d8a6c59103..04acf22cd1 100644 --- a/storage/reads/group_resultset_test.go +++ b/storage/reads/group_resultset_test.go @@ -7,7 +7,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/influxdata/platform/models" - "github.com/influxdata/platform/pkg/testing/gen" + "github.com/influxdata/platform/pkg/data/gen" "github.com/influxdata/platform/storage/reads" "github.com/influxdata/platform/storage/reads/datatypes" ) @@ -310,7 +310,7 @@ func (s *sliceSeriesCursor) Next() *reads.SeriesRow { func BenchmarkNewGroupResultSet_GroupBy(b *testing.B) { card := []int{10, 10, 10} - vals := make([]gen.Sequence, len(card)) + vals := make([]gen.CountableSequence, len(card)) for i := range card { vals[i] = gen.NewCounterByteSequenceCount(card[i]) } From 831fc8697b6a4671a9b65687db28c9ea8213a01d Mon Sep 17 00:00:00 2001 From: Kelvin Wang Date: Fri, 7 Dec 2018 13:12:24 -0500 Subject: [PATCH 13/22] fix(http): convert user errors --- bolt/basic_auth_service.go | 6 +- bolt/session.go | 6 +- bolt/user.go | 174 ++++++++++++++++++++++--------- bolt/user_test.go | 29 +----- http/user_service.go | 21 ++-- http/user_test.go | 7 +- inmem/user_service.go | 49 +++++++-- inmem/user_test.go | 33 +----- testing/user_service.go | 208 ++++++++++++++++++++++++------------- user.go | 10 ++ 10 files changed, 346 insertions(+), 197 deletions(-) diff --git a/bolt/basic_auth_service.go b/bolt/basic_auth_service.go index ec280b16c6..39592b260c 100644 --- a/bolt/basic_auth_service.go +++ b/bolt/basic_auth_service.go @@ -23,9 +23,9 @@ func (c *Client) setPassword(ctx context.Context, tx *bolt.Tx, name string, pass return err } - u, err := c.findUserByName(ctx, tx, name) - if err != nil { - return err + u, pe := c.findUserByName(ctx, tx, name) + if pe != nil { + return pe } encodedID, err := u.ID.Encode() diff --git a/bolt/session.go b/bolt/session.go index fb77b88e87..9342675318 100644 --- a/bolt/session.go +++ b/bolt/session.go @@ -124,9 +124,9 @@ func (c *Client) CreateSession(ctx context.Context, user string) (*platform.Sess } func (c *Client) createSession(ctx context.Context, tx *bolt.Tx, user string) (*platform.Session, error) { - u, err := c.findUserByName(ctx, tx, user) - if err != nil { - return nil, err + u, pe := c.findUserByName(ctx, tx, user) + if pe != nil { + return nil, pe } s := &platform.Session{} diff --git a/bolt/user.go b/bolt/user.go index 51e6f4aaa3..3c077c3576 100644 --- a/bolt/user.go +++ b/bolt/user.go @@ -6,7 +6,7 @@ import ( "fmt" "time" - "github.com/coreos/bbolt" + bolt "github.com/coreos/bbolt" "github.com/influxdata/platform" platformcontext "github.com/influxdata/platform/context" ) @@ -39,25 +39,30 @@ func (c *Client) FindUserByID(ctx context.Context, id platform.ID) (*platform.Us var u *platform.User err := c.db.View(func(tx *bolt.Tx) error { - usr, err := c.findUserByID(ctx, tx, id) - if err != nil { - return err + usr, pe := c.findUserByID(ctx, tx, id) + if pe != nil { + return pe } u = usr return nil }) if err != nil { - return nil, err + return nil, &platform.Error{ + Op: getOp(platform.OpFindUserByID), + Err: err, + } } return u, nil } -func (c *Client) findUserByID(ctx context.Context, tx *bolt.Tx, id platform.ID) (*platform.User, error) { +func (c *Client) findUserByID(ctx context.Context, tx *bolt.Tx, id platform.ID) (*platform.User, *platform.Error) { encodedID, err := id.Encode() if err != nil { - return nil, err + return nil, &platform.Error{ + Err: err, + } } var u platform.User @@ -71,7 +76,9 @@ func (c *Client) findUserByID(ctx context.Context, tx *bolt.Tx, id platform.ID) } if err := json.Unmarshal(v, &u); err != nil { - return nil, err + return nil, &platform.Error{ + Err: err, + } } return &u, nil @@ -82,9 +89,9 @@ func (c *Client) FindUserByName(ctx context.Context, n string) (*platform.User, var u *platform.User err := c.db.View(func(tx *bolt.Tx) error { - usr, err := c.findUserByName(ctx, tx, n) - if err != nil { - return err + usr, pe := c.findUserByName(ctx, tx, n) + if pe != nil { + return pe } u = usr return nil @@ -93,10 +100,9 @@ func (c *Client) FindUserByName(ctx context.Context, n string) (*platform.User, return u, err } -func (c *Client) findUserByName(ctx context.Context, tx *bolt.Tx, n string) (*platform.User, error) { +func (c *Client) findUserByName(ctx context.Context, tx *bolt.Tx, n string) (*platform.User, *platform.Error) { u := tx.Bucket(userIndex).Get(userIndexKey(n)) if u == nil { - // TODO: Make standard error return nil, &platform.Error{ Code: platform.ENotFound, Msg: "user not found", @@ -105,7 +111,9 @@ func (c *Client) findUserByName(ctx context.Context, tx *bolt.Tx, n string) (*pl var id platform.ID if err := id.Decode(u); err != nil { - return nil, err + return nil, &platform.Error{ + Err: err, + } } return c.findUserByID(ctx, tx, id) } @@ -114,18 +122,34 @@ func (c *Client) findUserByName(ctx context.Context, tx *bolt.Tx, n string) (*pl // Filters using ID, or Name should be efficient. // Other filters will do a linear scan across users until it finds a match. func (c *Client) FindUser(ctx context.Context, filter platform.UserFilter) (*platform.User, error) { + var u *platform.User + var err error + op := getOp(platform.OpFindUser) if filter.ID != nil { - return c.FindUserByID(ctx, *filter.ID) + u, err = c.FindUserByID(ctx, *filter.ID) + if err != nil { + return nil, &platform.Error{ + Op: op, + Err: err, + } + } + return u, nil } if filter.Name != nil { - return c.FindUserByName(ctx, *filter.Name) + u, err = c.FindUserByName(ctx, *filter.Name) + if err != nil { + return nil, &platform.Error{ + Op: op, + Err: err, + } + } + return u, nil } filterFn := filterUsersFn(filter) - var u *platform.User - err := c.db.View(func(tx *bolt.Tx) error { + err = c.db.View(func(tx *bolt.Tx) error { return forEachUser(ctx, tx, func(usr *platform.User) bool { if filterFn(usr) { u = usr @@ -136,11 +160,17 @@ func (c *Client) FindUser(ctx context.Context, filter platform.UserFilter) (*pla }) if err != nil { - return nil, err + return nil, &platform.Error{ + Op: op, + Err: err, + } } if u == nil { - return nil, fmt.Errorf("user not found") + return nil, &platform.Error{ + Code: platform.ENotFound, + Msg: "user not found", + } } return u, nil @@ -166,10 +196,14 @@ func filterUsersFn(filter platform.UserFilter) func(u *platform.User) bool { // Filters using ID, or Name should be efficient. // Other filters will do a linear scan across all users searching for a match. func (c *Client) FindUsers(ctx context.Context, filter platform.UserFilter, opt ...platform.FindOptions) ([]*platform.User, int, error) { + op := getOp(platform.OpFindUsers) if filter.ID != nil { u, err := c.FindUserByID(ctx, *filter.ID) if err != nil { - return nil, 0, err + return nil, 0, &platform.Error{ + Err: err, + Op: op, + } } return []*platform.User{u}, 1, nil @@ -178,7 +212,10 @@ func (c *Client) FindUsers(ctx context.Context, filter platform.UserFilter, opt if filter.Name != nil { u, err := c.FindUserByName(ctx, *filter.Name) if err != nil { - return nil, 0, err + return nil, 0, &platform.Error{ + Err: err, + Op: op, + } } return []*platform.User{u}, 1, nil @@ -204,12 +241,14 @@ func (c *Client) FindUsers(ctx context.Context, filter platform.UserFilter, opt // CreateUser creates a platform user and sets b.ID. func (c *Client) CreateUser(ctx context.Context, u *platform.User) error { - return c.db.Update(func(tx *bolt.Tx) error { + err := c.db.Update(func(tx *bolt.Tx) error { unique := c.uniqueUserName(ctx, tx, u) if !unique { - // TODO: make standard error - return fmt.Errorf("user with name %s already exists", u.Name) + return &platform.Error{ + Code: platform.EConflict, + Msg: fmt.Sprintf("user with name %s already exists", u.Name), + } } u.ID = c.IDGenerator.ID() @@ -220,6 +259,13 @@ func (c *Client) CreateUser(ctx context.Context, u *platform.User) error { return c.putUser(ctx, tx, u) }) + if err != nil { + return &platform.Error{ + Err: err, + Op: getOp(platform.OpCreateUser), + } + } + return nil } // PutUser will put a user without setting an ID. @@ -273,9 +319,12 @@ func (c *Client) uniqueUserName(ctx context.Context, tx *bolt.Tx, u *platform.Us func (c *Client) UpdateUser(ctx context.Context, id platform.ID, upd platform.UserUpdate) (*platform.User, error) { var u *platform.User err := c.db.Update(func(tx *bolt.Tx) error { - usr, err := c.updateUser(ctx, tx, id, upd) - if err != nil { - return err + usr, pe := c.updateUser(ctx, tx, id, upd) + if pe != nil { + return &platform.Error{ + Err: pe, + Op: getOp(platform.OpUpdateUser), + } } u = usr return nil @@ -284,7 +333,7 @@ func (c *Client) UpdateUser(ctx context.Context, id platform.ID, upd platform.Us return u, err } -func (c *Client) updateUser(ctx context.Context, tx *bolt.Tx, id platform.ID, upd platform.UserUpdate) (*platform.User, error) { +func (c *Client) updateUser(ctx context.Context, tx *bolt.Tx, id platform.ID, upd platform.UserUpdate) (*platform.User, *platform.Error) { u, err := c.findUserByID(ctx, tx, id) if err != nil { return nil, err @@ -294,17 +343,23 @@ func (c *Client) updateUser(ctx context.Context, tx *bolt.Tx, id platform.ID, up // Users are indexed by name and so the user index must be pruned // when name is modified. if err := tx.Bucket(userIndex).Delete(userIndexKey(u.Name)); err != nil { - return nil, err + return nil, &platform.Error{ + Err: err, + } } u.Name = *upd.Name } if err := c.appendUserEventToLog(ctx, tx, u.ID, userUpdatedEvent); err != nil { - return nil, err + return nil, &platform.Error{ + Err: err, + } } if err := c.putUser(ctx, tx, u); err != nil { - return nil, err + return nil, &platform.Error{ + Err: err, + } } return u, nil @@ -312,41 +367,64 @@ func (c *Client) updateUser(ctx context.Context, tx *bolt.Tx, id platform.ID, up // DeleteUser deletes a user and prunes it from the index. func (c *Client) DeleteUser(ctx context.Context, id platform.ID) error { - return c.db.Update(func(tx *bolt.Tx) error { - if err := c.deleteUsersAuthorizations(ctx, tx, id); err != nil { - return err + err := c.db.Update(func(tx *bolt.Tx) error { + if pe := c.deleteUsersAuthorizations(ctx, tx, id); pe != nil { + return pe } - return c.deleteUser(ctx, tx, id) + if pe := c.deleteUser(ctx, tx, id); pe != nil { + return pe + } + return nil }) + if err != nil { + return &platform.Error{ + Op: getOp(platform.OpDeleteUser), + Err: err, + } + } + return nil } -func (c *Client) deleteUser(ctx context.Context, tx *bolt.Tx, id platform.ID) error { - u, err := c.findUserByID(ctx, tx, id) - if err != nil { - return err +func (c *Client) deleteUser(ctx context.Context, tx *bolt.Tx, id platform.ID) *platform.Error { + u, pe := c.findUserByID(ctx, tx, id) + if pe != nil { + return pe } encodedID, err := id.Encode() if err != nil { - return err + return &platform.Error{ + Err: err, + } } if err := tx.Bucket(userIndex).Delete(userIndexKey(u.Name)); err != nil { - return err + return &platform.Error{ + Err: err, + } } if err := tx.Bucket(userUser).Delete(encodedID); err != nil { - return err + return &platform.Error{ + Err: err, + } } - return c.deleteUserResourceMappings(ctx, tx, platform.UserResourceMappingFilter{ + if err := c.deleteUserResourceMappings(ctx, tx, platform.UserResourceMappingFilter{ UserID: id, - }) + }); err != nil { + return &platform.Error{ + Err: err, + } + } + return nil } -func (c *Client) deleteUsersAuthorizations(ctx context.Context, tx *bolt.Tx, id platform.ID) error { +func (c *Client) deleteUsersAuthorizations(ctx context.Context, tx *bolt.Tx, id platform.ID) *platform.Error { authFilter := platform.AuthorizationFilter{ UserID: &id, } as, err := c.findAuthorizations(ctx, tx, authFilter) if err != nil { - return err + return &platform.Error{ + Err: err, + } } for _, a := range as { if err := c.deleteAuthorization(ctx, tx, a.ID); err != nil { @@ -356,7 +434,7 @@ func (c *Client) deleteUsersAuthorizations(ctx context.Context, tx *bolt.Tx, id return nil } -// GeUserOperationLog retrieves a user operation log. +// GetUserOperationLog retrieves a user operation log. func (c *Client) GetUserOperationLog(ctx context.Context, id platform.ID, opts platform.FindOptions) ([]*platform.OperationLogEntry, int, error) { // TODO(desa): might be worthwhile to allocate a slice of size opts.Limit log := []*platform.OperationLogEntry{} diff --git a/bolt/user_test.go b/bolt/user_test.go index 0dd3947a4f..6bc7840307 100644 --- a/bolt/user_test.go +++ b/bolt/user_test.go @@ -5,10 +5,11 @@ import ( "testing" "github.com/influxdata/platform" + bolt "github.com/influxdata/platform/bolt" platformtesting "github.com/influxdata/platform/testing" ) -func initUserService(f platformtesting.UserFields, t *testing.T) (platform.UserService, func()) { +func initUserService(f platformtesting.UserFields, t *testing.T) (platform.UserService, string, func()) { c, closeFn, err := NewTestClient() if err != nil { t.Fatalf("failed to create new bolt client: %v", err) @@ -20,7 +21,7 @@ func initUserService(f platformtesting.UserFields, t *testing.T) (platform.UserS t.Fatalf("failed to populate users") } } - return c, func() { + return c, bolt.OpPrefix, func() { defer closeFn() for _, u := range f.Users { if err := c.DeleteUser(ctx, u.ID); err != nil { @@ -30,26 +31,6 @@ func initUserService(f platformtesting.UserFields, t *testing.T) (platform.UserS } } -func TestUserService_CreateUser(t *testing.T) { - platformtesting.CreateUser(initUserService, t) -} - -func TestUserService_FindUserByID(t *testing.T) { - platformtesting.FindUserByID(initUserService, t) -} - -func TestUserService_FindUsers(t *testing.T) { - platformtesting.FindUsers(initUserService, t) -} - -func TestUserService_DeleteUser(t *testing.T) { - platformtesting.DeleteUser(initUserService, t) -} - -func TestUserService_FindUser(t *testing.T) { - platformtesting.FindUser(initUserService, t) -} - -func TestUserService_UpdateUser(t *testing.T) { - platformtesting.UpdateUser(initUserService, t) +func TestUserService(t *testing.T) { + platformtesting.UserService(initUserService, t) } diff --git a/http/user_service.go b/http/user_service.go index 7d71de6a5c..8e0f718e0a 100644 --- a/http/user_service.go +++ b/http/user_service.go @@ -413,6 +413,8 @@ type UserService struct { Addr string Token string InsecureSkipVerify bool + // OpPrefix is the ops of not found error. + OpPrefix string } // FindMe returns user information about the owner of the token @@ -436,7 +438,7 @@ func (s *UserService) FindMe(ctx context.Context, id platform.ID) (*platform.Use defer resp.Body.Close() - if err := CheckError(resp); err != nil { + if err := CheckError(resp, true); err != nil { return nil, err } @@ -466,7 +468,7 @@ func (s *UserService) FindUserByID(ctx context.Context, id platform.ID) (*platfo return nil, err } - if err := CheckError(resp); err != nil { + if err := CheckError(resp, true); err != nil { return nil, err } @@ -483,11 +485,18 @@ func (s *UserService) FindUserByID(ctx context.Context, id platform.ID) (*platfo func (s *UserService) FindUser(ctx context.Context, filter platform.UserFilter) (*platform.User, error) { users, n, err := s.FindUsers(ctx, filter) if err != nil { - return nil, err + return nil, &platform.Error{ + Op: s.OpPrefix + platform.OpFindUser, + Err: err, + } } if n == 0 { - return nil, ErrNotFound + return nil, &platform.Error{ + Code: platform.ENotFound, + Op: s.OpPrefix + platform.OpFindUser, + Msg: "no results found", + } } return users[0], nil @@ -564,7 +573,7 @@ func (s *UserService) CreateUser(ctx context.Context, u *platform.User) error { } // TODO(jsternberg): Should this check for a 201 explicitly? - if err := CheckError(resp); err != nil { + if err := CheckError(resp, true); err != nil { return err } @@ -603,7 +612,7 @@ func (s *UserService) UpdateUser(ctx context.Context, id platform.ID, upd platfo return nil, err } - if err := CheckError(resp); err != nil { + if err := CheckError(resp, true); err != nil { return nil, err } diff --git a/http/user_test.go b/http/user_test.go index e374845b4a..7ff20b09f4 100644 --- a/http/user_test.go +++ b/http/user_test.go @@ -10,7 +10,7 @@ import ( platformtesting "github.com/influxdata/platform/testing" ) -func initUserService(f platformtesting.UserFields, t *testing.T) (platform.UserService, func()) { +func initUserService(f platformtesting.UserFields, t *testing.T) (platform.UserService, string, func()) { t.Helper() svc := inmem.NewService() svc.IDGenerator = f.IDGenerator @@ -26,12 +26,13 @@ func initUserService(f platformtesting.UserFields, t *testing.T) (platform.UserS handler.UserService = svc server := httptest.NewServer(handler) client := UserService{ - Addr: server.URL, + Addr: server.URL, + OpPrefix: inmem.OpPrefix, } done := server.Close - return &client, done + return &client, inmem.OpPrefix, done } func TestUserService(t *testing.T) { diff --git a/inmem/user_service.go b/inmem/user_service.go index a8302a18a8..d1d63b5ff6 100644 --- a/inmem/user_service.go +++ b/inmem/user_service.go @@ -46,17 +46,21 @@ func (s *Service) FindUserByID(ctx context.Context, id platform.ID) (u *platform var pe *platform.Error u, pe = s.loadUser(id) if pe != nil { - err = pe + err = &platform.Error{ + Op: OpPrefix + platform.OpFindUserByID, + Err: pe, + } } return u, err } -func (c *Service) findUserByName(ctx context.Context, n string) (*platform.User, error) { - return c.FindUser(ctx, platform.UserFilter{Name: &n}) +func (s *Service) findUserByName(ctx context.Context, n string) (*platform.User, error) { + return s.FindUser(ctx, platform.UserFilter{Name: &n}) } // FindUser returns the first user that matches a filter. func (s *Service) FindUser(ctx context.Context, filter platform.UserFilter) (*platform.User, error) { + op := OpPrefix + platform.OpFindUser if filter.Name != nil { var o *platform.User @@ -75,6 +79,7 @@ func (s *Service) FindUser(ctx context.Context, filter platform.UserFilter) (*pl if o == nil { return nil, &platform.Error{ Code: platform.ENotFound, + Op: op, Msg: "user not found", } } @@ -82,14 +87,23 @@ func (s *Service) FindUser(ctx context.Context, filter platform.UserFilter) (*pl return o, nil } - return nil, fmt.Errorf("expected filter to contain name") + return nil, &platform.Error{ + Code: platform.EInvalid, + Op: op, + Msg: "expected filter to contain name", + } } +// FindUsers will retrieve a list of users from storage. func (s *Service) FindUsers(ctx context.Context, filter platform.UserFilter, opt ...platform.FindOptions) ([]*platform.User, int, error) { + op := OpPrefix + platform.OpFindUsers if filter.ID != nil { o, err := s.FindUserByID(ctx, *filter.ID) if err != nil { - return nil, 0, err + return nil, 0, &platform.Error{ + Err: err, + Op: op, + } } return []*platform.User{o}, 1, nil @@ -97,7 +111,10 @@ func (s *Service) FindUsers(ctx context.Context, filter platform.UserFilter, opt if filter.Name != nil { o, err := s.FindUser(ctx, filter) if err != nil { - return nil, 0, err + return nil, 0, &platform.Error{ + Err: err, + Op: op, + } } return []*platform.User{o}, 1, nil @@ -117,24 +134,34 @@ func (s *Service) FindUsers(ctx context.Context, filter platform.UserFilter, opt return orgs, len(orgs), nil } +// CreateUser will create an user into storage. func (s *Service) CreateUser(ctx context.Context, u *platform.User) error { if _, err := s.FindUser(ctx, platform.UserFilter{Name: &u.Name}); err == nil { - return fmt.Errorf("user with name %s already exists", u.Name) + return &platform.Error{ + Code: platform.EConflict, + Op: OpPrefix + platform.OpCreateUser, + Msg: fmt.Sprintf("user with name %s already exists", u.Name), + } } u.ID = s.IDGenerator.ID() s.PutUser(ctx, u) return nil } +// PutUser put a user into storage. func (s *Service) PutUser(ctx context.Context, o *platform.User) error { s.userKV.Store(o.ID.String(), o) return nil } +// UpdateUser update a user in storage. func (s *Service) UpdateUser(ctx context.Context, id platform.ID, upd platform.UserUpdate) (*platform.User, error) { o, err := s.FindUserByID(ctx, id) if err != nil { - return nil, err + return nil, &platform.Error{ + Err: err, + Op: OpPrefix + platform.OpUpdateUser, + } } if upd.Name != nil { @@ -146,9 +173,13 @@ func (s *Service) UpdateUser(ctx context.Context, id platform.ID, upd platform.U return o, nil } +// DeleteUser remove a user from storage. func (s *Service) DeleteUser(ctx context.Context, id platform.ID) error { if _, err := s.FindUserByID(ctx, id); err != nil { - return err + return &platform.Error{ + Err: err, + Op: OpPrefix + platform.OpDeleteUser, + } } s.userKV.Delete(id.String()) return nil diff --git a/inmem/user_test.go b/inmem/user_test.go index 5a7cb70ebb..a50bf0017e 100644 --- a/inmem/user_test.go +++ b/inmem/user_test.go @@ -8,7 +8,7 @@ import ( platformtesting "github.com/influxdata/platform/testing" ) -func initUserService(f platformtesting.UserFields, t *testing.T) (platform.UserService, func()) { +func initUserService(f platformtesting.UserFields, t *testing.T) (platform.UserService, string, func()) { s := NewService() s.IDGenerator = f.IDGenerator ctx := context.Background() @@ -17,35 +17,10 @@ func initUserService(f platformtesting.UserFields, t *testing.T) (platform.UserS t.Fatalf("failed to populate users") } } - return s, func() {} + return s, OpPrefix, func() {} } -func TestUserService_CreateUser(t *testing.T) { +func TestUserService(t *testing.T) { t.Parallel() - platformtesting.CreateUser(initUserService, t) -} - -func TestUserService_FindUserByID(t *testing.T) { - t.Parallel() - platformtesting.FindUserByID(initUserService, t) -} - -func TestUserService_FindUsers(t *testing.T) { - t.Parallel() - platformtesting.FindUsers(initUserService, t) -} - -func TestUserService_DeleteUser(t *testing.T) { - t.Parallel() - platformtesting.DeleteUser(initUserService, t) -} - -func TestUserService_FindUser(t *testing.T) { - t.Parallel() - platformtesting.FindUser(initUserService, t) -} - -func TestUserService_UpdateUser(t *testing.T) { - t.Parallel() - platformtesting.UpdateUser(initUserService, t) + platformtesting.UserService(initUserService, t) } diff --git a/testing/user_service.go b/testing/user_service.go index a9bbc22424..307d468a89 100644 --- a/testing/user_service.go +++ b/testing/user_service.go @@ -3,7 +3,6 @@ package testing import ( "bytes" "context" - "fmt" "sort" "testing" @@ -39,11 +38,11 @@ type UserFields struct { // UserService tests all the service functions. func UserService( - init func(UserFields, *testing.T) (platform.UserService, func()), t *testing.T, + init func(UserFields, *testing.T) (platform.UserService, string, func()), t *testing.T, ) { tests := []struct { name string - fn func(init func(UserFields, *testing.T) (platform.UserService, func()), + fn func(init func(UserFields, *testing.T) (platform.UserService, string, func()), t *testing.T) }{ { @@ -80,7 +79,7 @@ func UserService( // CreateUser testing func CreateUser( - init func(UserFields, *testing.T) (platform.UserService, func()), + init func(UserFields, *testing.T) (platform.UserService, string, func()), t *testing.T, ) { type args struct { @@ -173,26 +172,22 @@ func CreateUser( Name: "user1", }, }, - err: fmt.Errorf("user with name user1 already exists"), + err: &platform.Error{ + Code: platform.EConflict, + Op: platform.OpCreateUser, + Msg: "user with name user1 already exists", + }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, done := init(tt.fields, t) + s, opPrefix, done := init(tt.fields, t) defer done() ctx := context.TODO() err := s.CreateUser(ctx, tt.args.user) - if (err != nil) != (tt.wants.err != nil) { - t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err) - } - - if err != nil && tt.wants.err != nil { - if err.Error() != tt.wants.err.Error() { - t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error()) - } - } + diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t) // Delete only created users - ie., having a not nil ID if tt.args.user.ID.Valid() { @@ -212,7 +207,7 @@ func CreateUser( // FindUserByID testing func FindUserByID( - init func(UserFields, *testing.T) (platform.UserService, func()), + init func(UserFields, *testing.T) (platform.UserService, string, func()), t *testing.T, ) { type args struct { @@ -253,24 +248,41 @@ func FindUserByID( }, }, }, + { + name: "find user by id not exists", + fields: UserFields{ + Users: []*platform.User{ + { + ID: MustIDBase16(userOneID), + Name: "user1", + }, + { + ID: MustIDBase16(userTwoID), + Name: "user2", + }, + }, + }, + args: args{ + id: MustIDBase16(threeID), + }, + wants: wants{ + err: &platform.Error{ + Code: platform.ENotFound, + Op: platform.OpFindUserByID, + Msg: "user not found", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, done := init(tt.fields, t) + s, opPrefix, done := init(tt.fields, t) defer done() ctx := context.TODO() user, err := s.FindUserByID(ctx, tt.args.id) - if (err != nil) != (tt.wants.err != nil) { - t.Fatalf("expected errors to be equal '%v' got '%v'", tt.wants.err, err) - } - - if err != nil && tt.wants.err != nil { - if err.Error() != tt.wants.err.Error() { - t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err) - } - } + diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t) if diff := cmp.Diff(user, tt.wants.user, userCmpOptions...); diff != "" { t.Errorf("user is different -got/+want\ndiff %s", diff) @@ -281,7 +293,7 @@ func FindUserByID( // FindUsers testing func FindUsers( - init func(UserFields, *testing.T) (platform.UserService, func()), + init func(UserFields, *testing.T) (platform.UserService, string, func()), t *testing.T, ) { type args struct { @@ -379,11 +391,61 @@ func FindUsers( }, }, }, + { + name: "find user by id not exists", + fields: UserFields{ + Users: []*platform.User{ + { + ID: MustIDBase16(userOneID), + Name: "abc", + }, + { + ID: MustIDBase16(userTwoID), + Name: "xyz", + }, + }, + }, + args: args{ + ID: MustIDBase16(threeID), + }, + wants: wants{ + err: &platform.Error{ + Code: platform.ENotFound, + Op: platform.OpFindUsers, + Msg: "user not found", + }, + }, + }, + { + name: "find user by name not exists", + fields: UserFields{ + Users: []*platform.User{ + { + ID: MustIDBase16(userOneID), + Name: "abc", + }, + { + ID: MustIDBase16(userTwoID), + Name: "xyz", + }, + }, + }, + args: args{ + name: "no_exist", + }, + wants: wants{ + err: &platform.Error{ + Code: platform.ENotFound, + Op: platform.OpFindUsers, + Msg: "user not found", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, done := init(tt.fields, t) + s, opPrefix, done := init(tt.fields, t) defer done() ctx := context.TODO() @@ -396,15 +458,7 @@ func FindUsers( } users, _, err := s.FindUsers(ctx, filter) - if (err != nil) != (tt.wants.err != nil) { - t.Fatalf("expected errors to be equal '%v' got '%v'", tt.wants.err, err) - } - - if err != nil && tt.wants.err != nil { - if err.Error() != tt.wants.err.Error() { - t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err) - } - } + diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t) if diff := cmp.Diff(users, tt.wants.users, userCmpOptions...); diff != "" { t.Errorf("users are different -got/+want\ndiff %s", diff) @@ -415,7 +469,7 @@ func FindUsers( // DeleteUser testing func DeleteUser( - init func(UserFields, *testing.T) (platform.UserService, func()), + init func(UserFields, *testing.T) (platform.UserService, string, func()), t *testing.T, ) { type args struct { @@ -476,7 +530,11 @@ func DeleteUser( ID: MustIDBase16(userThreeID), }, wants: wants{ - err: fmt.Errorf(" user not found"), + err: &platform.Error{ + Code: platform.ENotFound, + Op: platform.OpDeleteUser, + Msg: "user not found", + }, users: []*platform.User{ { Name: "orgA", @@ -493,19 +551,11 @@ func DeleteUser( for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, done := init(tt.fields, t) + s, opPrefix, done := init(tt.fields, t) defer done() ctx := context.TODO() err := s.DeleteUser(ctx, tt.args.ID) - if (err != nil) != (tt.wants.err != nil) { - t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err) - } - - if err != nil && tt.wants.err != nil { - if err.Error() != tt.wants.err.Error() { - t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error()) - } - } + diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t) filter := platform.UserFilter{} users, _, err := s.FindUsers(ctx, filter) @@ -521,7 +571,7 @@ func DeleteUser( // FindUser testing func FindUser( - init func(UserFields, *testing.T) (platform.UserService, func()), + init func(UserFields, *testing.T) (platform.UserService, string, func()), t *testing.T, ) { type args struct { @@ -572,14 +622,18 @@ func FindUser( name: "abc", }, wants: wants{ - err: fmt.Errorf(" user not found"), + err: &platform.Error{ + Code: platform.ENotFound, + Msg: "user not found", + Op: platform.OpFindUser, + }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, done := init(tt.fields, t) + s, opPrefix, done := init(tt.fields, t) defer done() ctx := context.TODO() filter := platform.UserFilter{} @@ -588,15 +642,7 @@ func FindUser( } user, err := s.FindUser(ctx, filter) - if (err != nil) != (tt.wants.err != nil) { - t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err) - } - - if err != nil && tt.wants.err != nil { - if err.Error() != tt.wants.err.Error() { - t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error()) - } - } + diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t) if diff := cmp.Diff(user, tt.wants.user, userCmpOptions...); diff != "" { t.Errorf("users are different -got/+want\ndiff %s", diff) @@ -607,7 +653,7 @@ func FindUser( // UpdateUser testing func UpdateUser( - init func(UserFields, *testing.T) (platform.UserService, func()), + init func(UserFields, *testing.T) (platform.UserService, string, func()), t *testing.T, ) { type args struct { @@ -650,11 +696,37 @@ func UpdateUser( }, }, }, + { + name: "update name with id not exists", + fields: UserFields{ + Users: []*platform.User{ + { + ID: MustIDBase16(userOneID), + Name: "user1", + }, + { + ID: MustIDBase16(userTwoID), + Name: "user2", + }, + }, + }, + args: args{ + id: MustIDBase16(threeID), + name: "changed", + }, + wants: wants{ + err: &platform.Error{ + Code: platform.ENotFound, + Op: platform.OpUpdateUser, + Msg: "user not found", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s, done := init(tt.fields, t) + s, opPrefix, done := init(tt.fields, t) defer done() ctx := context.TODO() @@ -664,15 +736,7 @@ func UpdateUser( } user, err := s.UpdateUser(ctx, tt.args.id, upd) - if (err != nil) != (tt.wants.err != nil) { - t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err) - } - - if err != nil && tt.wants.err != nil { - if err.Error() != tt.wants.err.Error() { - t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error()) - } - } + diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t) if diff := cmp.Diff(user, tt.wants.user, userCmpOptions...); diff != "" { t.Errorf("user is different -got/+want\ndiff %s", diff) diff --git a/user.go b/user.go index a9b1282966..a2acbef52c 100644 --- a/user.go +++ b/user.go @@ -8,6 +8,16 @@ type User struct { Name string `json:"name"` } +// Ops for user errors and op log. +const ( + OpFindUserByID = "FindUserByID" + OpFindUser = "FindUser" + OpFindUsers = "FindUsers" + OpCreateUser = "CreateUser" + OpUpdateUser = "UpdateUser" + OpDeleteUser = "DeleteUser" +) + // UserService represents a service for managing user data. type UserService interface { From 46aed2774f146f85181cb90fd925b8e5013376f1 Mon Sep 17 00:00:00 2001 From: Edd Robinson Date: Wed, 12 Dec 2018 11:41:50 +0000 Subject: [PATCH 14/22] Add uptime to ready endpoint --- http/ready.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/http/ready.go b/http/ready.go index 2dabd05d38..5406896d03 100644 --- a/http/ready.go +++ b/http/ready.go @@ -1,12 +1,34 @@ package http import ( + "encoding/json" "fmt" "net/http" + "time" + + "github.com/influxdata/platform/toml" ) -// ReadyHandler is a default readiness handler. The default behavior is always ready. +var up = time.Now() + +// ReadyHandler is a default readiness handler. The default behaviour is always ready. func ReadyHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - fmt.Fprintln(w, `{"status": "ready"}`) + + var status = struct { + Status string `json:"status"` + Start time.Time `json:"started"` + Up toml.Duration `json:"up"` + }{ + Status: "ready", + Start: up, + Up: toml.Duration(time.Since(up)), + } + + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + err := enc.Encode(status) + if err != nil { + fmt.Fprintf(w, "Error encoding status data: %v\n", err) + } } From f383e8337ab36ce778f2fcc225c0143987a0c73d Mon Sep 17 00:00:00 2001 From: Mark Rushakoff Date: Tue, 11 Dec 2018 15:31:50 -0800 Subject: [PATCH 15/22] test(tsdb/tsi1): test series id cache delete concurrently Report the total number of gets, puts, and deletes at the end of the test. I've found this kind of output to be a useful sanity check in similar tests that exercise concurrency involving tasks. Use a local random source in each goroutine. I unscientifically eyeballed that to increase total operations by 5-10%. Also call t.Parallel in a few more tests that involve disk access. This shaves 1-2 seconds off the full tsi1 test suite on my machine. --- tsdb/tsi1/cache_test.go | 37 ++++++++++++++++++++++++++++++------- tsdb/tsi1/log_file_test.go | 8 ++++++++ tsdb/tsi1/partition_test.go | 2 ++ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/tsdb/tsi1/cache_test.go b/tsdb/tsi1/cache_test.go index 26473afb73..c4a7ecefe2 100644 --- a/tsdb/tsi1/cache_test.go +++ b/tsdb/tsi1/cache_test.go @@ -3,6 +3,7 @@ package tsi1 import ( "math/rand" "sync" + "sync/atomic" "testing" "time" @@ -152,49 +153,70 @@ func TestTagValueSeriesIDCache_addToSet(t *testing.T) { if !newSeriesIDSet(20).Equals(ss) { t.Fatalf("series id set was %v", ss) } - } -func TestTagValueSeriesIDCache_ConcurrentGetPut(t *testing.T) { +func TestTagValueSeriesIDCache_ConcurrentGetPutDelete(t *testing.T) { + // Exercise concurrent operations against a series ID cache. + // This will catch any likely data races, when run with the race detector. + if testing.Short() { t.Skip("Skipping long test") } - a := []string{"a", "b", "c", "d", "e"} - rnd := func() []byte { - return []byte(a[rand.Intn(len(a)-1)]) + t.Parallel() + + const letters = "abcde" + rnd := func(rng *rand.Rand) []byte { + return []byte{letters[rng.Intn(len(letters)-1)]} } cache := TestCache{NewTagValueSeriesIDCache(100)} done := make(chan struct{}) var wg sync.WaitGroup + var seriesIDCounter int32 // Atomic counter to ensure unique series IDs. for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() + + // Local rng to avoid lock contention. + rng := rand.New(rand.NewSource(rand.Int63())) for { select { case <-done: return default: } - cache.Put(rnd(), rnd(), rnd(), newSeriesIDSet()) + nextID := int(atomic.AddInt32(&seriesIDCounter, 1)) + cache.Put(rnd(rng), rnd(rng), rnd(rng), newSeriesIDSet(nextID)) } }() } + var gets, deletes int32 for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() + + // Local rng to avoid lock contention. + rng := rand.New(rand.NewSource(rand.Int63())) for { select { case <-done: return default: } - _ = cache.Get(rnd(), rnd(), rnd()) + name, key, value := rnd(rng), rnd(rng), rnd(rng) + if set := cache.Get(name, key, value); set != nil { + ids := set.Slice() + for _, id := range ids { + cache.Delete(name, key, value, tsdb.NewSeriesID(id)) + atomic.AddInt32(&deletes, 1) + } + } + atomic.AddInt32(&gets, 1) } }() } @@ -202,6 +224,7 @@ func TestTagValueSeriesIDCache_ConcurrentGetPut(t *testing.T) { time.Sleep(10 * time.Second) close(done) wg.Wait() + t.Logf("Concurrently executed against series ID cache: gets=%d puts=%d deletes=%d", gets, seriesIDCounter, deletes) } type TestCache struct { diff --git a/tsdb/tsi1/log_file_test.go b/tsdb/tsi1/log_file_test.go index dfc5a97451..17210b2d2b 100644 --- a/tsdb/tsi1/log_file_test.go +++ b/tsdb/tsi1/log_file_test.go @@ -23,6 +23,8 @@ import ( // Ensure log file can append series. func TestLogFile_AddSeriesList(t *testing.T) { + t.Parallel() + sfile := MustOpenSeriesFile() defer sfile.Close() @@ -127,6 +129,8 @@ func TestLogFile_AddSeriesList(t *testing.T) { } func TestLogFile_SeriesStoredInOrder(t *testing.T) { + t.Parallel() + sfile := MustOpenSeriesFile() defer sfile.Close() @@ -189,6 +193,8 @@ func TestLogFile_SeriesStoredInOrder(t *testing.T) { // Ensure log file can delete an existing measurement. func TestLogFile_DeleteMeasurement(t *testing.T) { + t.Parallel() + sfile := MustOpenSeriesFile() defer sfile.Close() @@ -232,6 +238,8 @@ func TestLogFile_DeleteMeasurement(t *testing.T) { // Ensure log file can recover correctly. func TestLogFile_Open(t *testing.T) { + t.Parallel() + t.Run("Truncate", func(t *testing.T) { sfile := MustOpenSeriesFile() defer sfile.Close() diff --git a/tsdb/tsi1/partition_test.go b/tsdb/tsi1/partition_test.go index c67730fe8a..2fc7fa93fd 100644 --- a/tsdb/tsi1/partition_test.go +++ b/tsdb/tsi1/partition_test.go @@ -12,6 +12,8 @@ import ( ) func TestPartition_Open(t *testing.T) { + t.Parallel() // There's a bit of IO in this test. + sfile := MustOpenSeriesFile() defer sfile.Close() From 58ff2c4eec9bd8f973308b77dde123c623528926 Mon Sep 17 00:00:00 2001 From: Jade McGough Date: Wed, 12 Dec 2018 09:18:56 -0800 Subject: [PATCH 16/22] fix(http): don't panic if an invalid macro id is requested (#1848) --- http/macro_service.go | 9 ++++++++- http/macro_test.go | 18 ++++++++++++++++++ http/swagger.yml | 6 ++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/http/macro_service.go b/http/macro_service.go index 2263963e67..376919fbe7 100644 --- a/http/macro_service.go +++ b/http/macro_service.go @@ -96,7 +96,14 @@ func requestMacroID(ctx context.Context) (platform.ID, error) { } id, err := platform.IDFromString(urlID) - return *id, err + if err != nil { + return platform.InvalidID(), &platform.Error{ + Code: platform.EInvalid, + Err: err, + } + } + + return *id, nil } func (h *MacroHandler) handleGetMacro(w http.ResponseWriter, r *http.Request) { diff --git a/http/macro_test.go b/http/macro_test.go index 06d35704b2..0f8e626920 100644 --- a/http/macro_test.go +++ b/http/macro_test.go @@ -159,6 +159,24 @@ func TestMacroService_handleGetMacro(t *testing.T) { body: ``, }, }, + { + name: "request an invalid macro ID", + args: args{ + id: "baz", + }, + fields: fields{ + &mock.MacroService{ + FindMacroByIDF: func(ctx context.Context, id platform.ID) (*platform.Macro, error) { + return nil, nil + }, + }, + }, + wants: wants{ + statusCode: 400, + contentType: "application/json", + body: `{"code":"invalid","msg":"An internal error has occurred.","err":"id must have a length of 16 bytes"}`, + }, + }, } for _, tt := range tests { diff --git a/http/swagger.yml b/http/swagger.yml index d062a408da..737dd517e3 100644 --- a/http/swagger.yml +++ b/http/swagger.yml @@ -494,6 +494,12 @@ paths: application/json: schema: $ref: "#/components/schemas/Macros" + '400': + description: invalid request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" default: description: internal server error content: From 78d0fc2b17b89b15d3377fc879ab9ac2b3151ee2 Mon Sep 17 00:00:00 2001 From: Jade McGough Date: Wed, 12 Dec 2018 10:24:33 -0800 Subject: [PATCH 17/22] fix(testing): compare expected error messages against actual (#1857) * fix(testing): compare expected error messages against actual * remove nonsense * remove nonsense * add expected error message for bucket not found * oops --- http/bucket_service.go | 1 + inmem/organization_service.go | 2 +- testing/bucket_service.go | 2 +- testing/organization_service.go | 2 +- testing/util.go | 12 ++++++++++-- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/http/bucket_service.go b/http/bucket_service.go index 9ea4551d0e..6cb0b59db5 100644 --- a/http/bucket_service.go +++ b/http/bucket_service.go @@ -516,6 +516,7 @@ func (s *BucketService) FindBucket(ctx context.Context, filter platform.BucketFi return nil, &platform.Error{ Code: platform.ENotFound, Op: s.OpPrefix + platform.OpFindBucket, + Msg: "bucket not found", Err: ErrNotFound, } } diff --git a/inmem/organization_service.go b/inmem/organization_service.go index d35c0fb3bf..687f8a0df3 100644 --- a/inmem/organization_service.go +++ b/inmem/organization_service.go @@ -149,7 +149,7 @@ func (s *Service) FindOrganizations(ctx context.Context, filter platform.Organiz return orgs, 0, &platform.Error{ Code: platform.ENotFound, Op: op, - Msg: OpPrefix + platform.OpFindOrganizations, + Msg: errOrganizationNotFound, } } diff --git a/testing/bucket_service.go b/testing/bucket_service.go index 85d7a2bf1b..8e79dfe62c 100644 --- a/testing/bucket_service.go +++ b/testing/bucket_service.go @@ -945,7 +945,7 @@ func FindBucket( err: &platform.Error{ Code: platform.ENotFound, Op: platform.OpFindBucket, - Msg: "no results found", + Msg: "bucket not found", }, }, }, diff --git a/testing/organization_service.go b/testing/organization_service.go index cb84dd2843..a81b874214 100644 --- a/testing/organization_service.go +++ b/testing/organization_service.go @@ -295,7 +295,7 @@ func FindOrganizationByID( err: &platform.Error{ Code: platform.ENotFound, Op: platform.OpFindOrganizationByID, - Msg: "", + Msg: "organization not found", }, }, }, diff --git a/testing/util.go b/testing/util.go index e794bcfa4a..01a1dfce9e 100644 --- a/testing/util.go +++ b/testing/util.go @@ -21,6 +21,10 @@ func diffErrors(actual, expected error, t *testing.T) { } func diffPlatformErrors(name string, actual, expected error, opPrefix string, t *testing.T) { + if expected == nil && actual == nil { + return + } + if expected == nil && actual != nil { t.Fatalf("%s failed, unexpected error %s", name, actual.Error()) } @@ -29,13 +33,17 @@ func diffPlatformErrors(name string, actual, expected error, opPrefix string, t t.Fatalf("%s failed, expected error %s but received nil", name, expected.Error()) } - if expected != nil && actual != nil && platform.ErrorCode(expected) != platform.ErrorCode(actual) { + if platform.ErrorCode(expected) != platform.ErrorCode(actual) { t.Fatalf("%s failed, expected error %q but received error code %q", name, platform.ErrorCode(expected), platform.ErrorCode(actual)) } - if expected != nil && actual != nil && opPrefix+platform.ErrorOp(expected) != platform.ErrorOp(actual) { + if opPrefix+platform.ErrorOp(expected) != platform.ErrorOp(actual) { t.Fatalf("%s failed, expected error %q but received error op %q", name, opPrefix+platform.ErrorOp(expected), platform.ErrorOp(actual)) } + + if platform.ErrorMessage(expected) != platform.ErrorMessage(actual) { + t.Fatalf("%s failed, expected error %q but received error message %q", name, platform.ErrorMessage(expected), platform.ErrorMessage(actual)) + } } // MustIDBase16 is an helper to ensure a correct ID is built during testing. From e57d082b1cec9273182b163816f78253c89239ef Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Tue, 11 Dec 2018 10:57:33 -0800 Subject: [PATCH 18/22] Enable setting prefix/suffix for gauge views --- ui/package-lock.json | 136 +++++++++++----------- ui/src/shared/reducers/v2/timeMachines.ts | 22 +++- 2 files changed, 88 insertions(+), 70 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 0a8ab6d0b6..83a9b9d112 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1487,7 +1487,7 @@ }, "array-flatten": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, @@ -5046,27 +5046,27 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "aproba": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "dev": true, "optional": true, @@ -5077,13 +5077,13 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "brace-expansion": { "version": "1.1.11", - "resolved": false, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { @@ -5093,39 +5093,39 @@ }, "chownr": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "concat-map": { "version": "0.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "console-control-strings": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true }, "core-util-is": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "2.6.9", - "resolved": false, + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, @@ -5135,28 +5135,28 @@ }, "deep-extend": { "version": "0.5.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, @@ -5166,14 +5166,14 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, @@ -5190,7 +5190,7 @@ }, "glob": { "version": "7.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "optional": true, @@ -5205,14 +5205,14 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.21", - "resolved": false, + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz", "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", "dev": true, "optional": true, @@ -5222,7 +5222,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, @@ -5232,7 +5232,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, @@ -5243,20 +5243,20 @@ }, "inherits": { "version": "2.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "ini": { "version": "1.3.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { @@ -5265,14 +5265,14 @@ }, "isarray": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { @@ -5281,13 +5281,13 @@ }, "minimist": { "version": "0.0.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "minipass": { "version": "2.2.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", "dev": true, "requires": { @@ -5297,7 +5297,7 @@ }, "minizlib": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", "dev": true, "optional": true, @@ -5307,7 +5307,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -5316,14 +5316,14 @@ }, "ms": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true, "optional": true }, "needle": { "version": "2.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.0.tgz", "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", "dev": true, "optional": true, @@ -5335,7 +5335,7 @@ }, "node-pre-gyp": { "version": "0.10.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz", "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", "dev": true, "optional": true, @@ -5354,7 +5354,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, @@ -5365,14 +5365,14 @@ }, "npm-bundled": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.1.10", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", "dev": true, "optional": true, @@ -5383,7 +5383,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, @@ -5396,20 +5396,20 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "object-assign": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { @@ -5418,21 +5418,21 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, @@ -5443,21 +5443,21 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true }, "rc": { "version": "1.2.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.7.tgz", "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", "dev": true, "optional": true, @@ -5470,7 +5470,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true @@ -5479,7 +5479,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -5495,7 +5495,7 @@ }, "rimraf": { "version": "2.6.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "optional": true, @@ -5505,48 +5505,48 @@ }, "safe-buffer": { "version": "5.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, "safer-buffer": { "version": "2.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true }, "semver": { "version": "5.5.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { @@ -5557,7 +5557,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, @@ -5567,7 +5567,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -5576,14 +5576,14 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "4.4.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.1.tgz", "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", "dev": true, "optional": true, @@ -5599,14 +5599,14 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "dev": true, "optional": true, @@ -5616,13 +5616,13 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "yallist": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", "dev": true } @@ -9042,7 +9042,7 @@ }, "pako": { "version": "0.2.9", - "resolved": "http://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", "dev": true }, diff --git a/ui/src/shared/reducers/v2/timeMachines.ts b/ui/src/shared/reducers/v2/timeMachines.ts index 612c8fa584..8401e9faac 100644 --- a/ui/src/shared/reducers/v2/timeMachines.ts +++ b/ui/src/shared/reducers/v2/timeMachines.ts @@ -260,13 +260,31 @@ export const timeMachineReducer = ( case 'SET_PREFIX': { const {prefix} = action.payload - return setYAxis(state, {prefix}) + switch (state.view.properties.type) { + case ViewType.Gauge: + case ViewType.SingleStat: + return setViewProperties(state, {prefix}) + case ViewType.XY: + case ViewType.LinePlusSingleStat: + return setYAxis(state, {prefix}) + default: + return state + } } case 'SET_SUFFIX': { const {suffix} = action.payload - return setYAxis(state, {suffix}) + switch (state.view.properties.type) { + case ViewType.Gauge: + case ViewType.SingleStat: + return setViewProperties(state, {suffix}) + case ViewType.XY: + case ViewType.LinePlusSingleStat: + return setYAxis(state, {suffix}) + default: + return state + } } case 'SET_COLORS': { From 0e1d89aa19f411f9a0d3bf78f45cb30dd0890eef Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Tue, 11 Dec 2018 13:45:32 -0800 Subject: [PATCH 19/22] Polish query tab styling --- ui/src/shared/components/TimeMachine.scss | 2 +- .../components/TimeMachineFluxEditor.tsx | 2 +- .../shared/components/TimeMachineQueries.scss | 2 ++ .../components/TimeMachineQueryTab.scss | 26 ++++++++++++++++--- .../components/view_options/ViewOptions.scss | 2 +- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/ui/src/shared/components/TimeMachine.scss b/ui/src/shared/components/TimeMachine.scss index 646dc2e142..44b3eca723 100644 --- a/ui/src/shared/components/TimeMachine.scss +++ b/ui/src/shared/components/TimeMachine.scss @@ -18,11 +18,11 @@ $time-machine--controls-height: 62px; width: 100%; height: 100%; position: absolute; + background-color: $g3-castle; } .time-machine--top { position: relative; - background-color: $g3-castle; } .time-machine--bottom { diff --git a/ui/src/shared/components/TimeMachineFluxEditor.tsx b/ui/src/shared/components/TimeMachineFluxEditor.tsx index f4938bdbdc..fc15a21406 100644 --- a/ui/src/shared/components/TimeMachineFluxEditor.tsx +++ b/ui/src/shared/components/TimeMachineFluxEditor.tsx @@ -55,7 +55,7 @@ class TimeMachineFluxEditor extends PureComponent { }, { render: () => , - handlePixels: 10, + handlePixels: 6, size: 0.25, }, ] diff --git a/ui/src/shared/components/TimeMachineQueries.scss b/ui/src/shared/components/TimeMachineQueries.scss index e0935046bd..be52c66c07 100644 --- a/ui/src/shared/components/TimeMachineQueries.scss +++ b/ui/src/shared/components/TimeMachineQueries.scss @@ -13,6 +13,8 @@ justify-content: space-between; align-items: flex-start; border-bottom: 4px solid $g3-castle; + background: $g2-kevlar; + padding: 8px 10px 0 10px; } .time-machine-queries--tabs { diff --git a/ui/src/shared/components/TimeMachineQueryTab.scss b/ui/src/shared/components/TimeMachineQueryTab.scss index 7b024c1843..f06656c1ef 100644 --- a/ui/src/shared/components/TimeMachineQueryTab.scss +++ b/ui/src/shared/components/TimeMachineQueryTab.scss @@ -1,22 +1,41 @@ @import "src/style/modules"; +$time-machine-query-tab--lr-padding: 12px; + .time-machine-query-tab { background: $g2-kevlar; border-radius: 5px 5px 0 0; - margin-right: 3px; display: flex; justify-content: space-between; align-items: center; - padding: 0 15px; + padding-right: $time-machine-query-tab--lr-padding; color: $g10-wolf; font-weight: 700; font-size: 13px; cursor: pointer !important; @include no-user-select(); + + &:not(.active):before { + content: ""; + height: 60%; + border-left: 1px solid $g3-castle; + padding-right: $time-machine-query-tab--lr-padding; + margin-left: -1px; + } + + &:first-child:before { + border-left: none; + } + + &:first-child.active { + margin-left: -1px; + } + &.active { background: $g3-castle; color: $g16-pearl; + padding-left: $time-machine-query-tab--lr-padding; } &:hover:not(.active) { @@ -27,8 +46,9 @@ .time-machine-query-tab--close { margin-left: 30px; margin-bottom: 2px; + color: $g7-graphite; &:hover { - color: $g19-ghost; + color: $g16-pearl; } } diff --git a/ui/src/shared/components/view_options/ViewOptions.scss b/ui/src/shared/components/view_options/ViewOptions.scss index 371bea747e..4b74d4539f 100644 --- a/ui/src/shared/components/view_options/ViewOptions.scss +++ b/ui/src/shared/components/view_options/ViewOptions.scss @@ -6,7 +6,7 @@ @import 'src/style/modules'; .view-options { - padding: $ix-marg-d; + padding: $ix-marg-c $ix-marg-d; } .view-options--header { From f3a655bea835e9e66a5d7ae5078fd2c9c36cb749 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Tue, 11 Dec 2018 14:35:12 -0800 Subject: [PATCH 20/22] Polish AutoInput - Allow deleting the input entirely - Only reset value if mode has changed, even if a mode button is clicked - Emit changes to the input value consistently --- .../components/auto_input/AutoInput.tsx | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/ui/src/clockface/components/auto_input/AutoInput.tsx b/ui/src/clockface/components/auto_input/AutoInput.tsx index f7939b39b4..e1d7ffb92b 100644 --- a/ui/src/clockface/components/auto_input/AutoInput.tsx +++ b/ui/src/clockface/components/auto_input/AutoInput.tsx @@ -2,7 +2,7 @@ import React, {Component, ChangeEvent, KeyboardEvent} from 'react' // Components -import {Input, InputType, Radio, ButtonShape} from 'src/clockface' +import {Input, Radio, ButtonShape} from 'src/clockface' // Styles import './AutoInput.scss' @@ -26,7 +26,7 @@ interface Props { interface State { inputMode: Mode - inputValue: number + inputValue: string } @ErrorHandling @@ -79,20 +79,17 @@ export default class AutoInput extends Component { private get input(): JSX.Element { const {inputMode, inputValue} = this.state - const {min, max, inputPlaceholder} = this.props + const {inputPlaceholder} = this.props if (inputMode === Mode.Custom) { return (
@@ -101,32 +98,41 @@ export default class AutoInput extends Component { } private handleInputChange = (e: ChangeEvent) => { - const inputValue = Number(e.target.value) + const {max, min} = this.props + + let inputValue = e.target.value + + if (Number(inputValue) < min) { + inputValue = String(min) + } else if (Number(inputValue) > max) { + inputValue = String(max) + } this.setState({inputValue}) } private handleRadioClick = (inputMode: Mode) => { - const {onChange} = this.props - - if (inputMode === Mode.Custom) { - this.setState({inputMode, inputValue: 0}) - onChange(null) - } else { - this.setState({inputMode, inputValue: null}) + if (inputMode === this.state.inputMode) { + return } - } - private handleInputBlur = (e: ChangeEvent) => { - const {onChange} = this.props - const inputValue = Number(e.target.value) - - onChange(inputValue) + this.setState({inputMode, inputValue: ''}, this.emitValue) } private handleInputKeyPress = (e: KeyboardEvent) => { if (e.key === 'Enter') { - this.props.onChange(this.state.inputValue) + this.emitValue() + } + } + + private emitValue = () => { + const {onChange} = this.props + const {inputValue} = this.state + + if (inputValue === '' || isNaN(Number(inputValue))) { + onChange(null) + } else { + onChange(Number(inputValue)) } } } From 1e167ae41ef424a487c2abcc8d32b58b419f2395 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Tue, 11 Dec 2018 16:13:01 -0800 Subject: [PATCH 21/22] Fix decimal place visualization option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes the gauge and single stat visualizations to use their decimal place option more literally. For example, if the decimal place option is set to 2 digits, then every number in the visualization will be formatted with 2 digits after the decimal place. Previously, this option was ignored altogether, and seemed to be programmed to mean “display at most 2 digits after the decimal place”. --- ui/src/dashboards/constants/index.ts | 5 +- ui/src/shared/components/Gauge.tsx | 46 ++++--------------- ui/src/shared/components/GaugeChart.test.tsx | 2 +- ui/src/shared/components/GaugeChart.tsx | 2 +- .../view_options/options/DecimalPlaces.tsx | 4 +- 5 files changed, 14 insertions(+), 45 deletions(-) diff --git a/ui/src/dashboards/constants/index.ts b/ui/src/dashboards/constants/index.ts index dbb1ab824f..de27d2b667 100644 --- a/ui/src/dashboards/constants/index.ts +++ b/ui/src/dashboards/constants/index.ts @@ -102,9 +102,8 @@ export enum CEOTabs { Vis = 'Visualization', } -export const MAX_TO_LOCALE_STRING_VAL = 20 // 20 is the max input to maximumFractionDigits in spec for "to locale string" -export const MIN_DECIMAL_PLACES = '0' -export const MAX_DECIMAL_PLACES = MAX_TO_LOCALE_STRING_VAL.toString() +export const MIN_DECIMAL_PLACES = 0 +export const MAX_DECIMAL_PLACES = 10 // used in importing dashboards and mapping sources export const DYNAMIC_SOURCE = 'dynamic' diff --git a/ui/src/shared/components/Gauge.tsx b/ui/src/shared/components/Gauge.tsx index 91e931a781..ecf1ceb06d 100644 --- a/ui/src/shared/components/Gauge.tsx +++ b/ui/src/shared/components/Gauge.tsx @@ -10,7 +10,7 @@ import { DEFAULT_VALUE_MIN, MIN_THRESHOLDS, } from 'src/shared/constants/thresholds' -import {MAX_TO_LOCALE_STRING_VAL} from 'src/dashboards/constants' +import {MAX_DECIMAL_PLACES} from 'src/dashboards/constants' import {ErrorHandling} from 'src/shared/decorators/errors' @@ -320,7 +320,7 @@ class Gauge extends Component { ctx.textBaseline = 'middle' ctx.textAlign = 'center' - const valueString = this.valueToString(gaugePosition) + const valueString = this.labelToString(gaugePosition) const textY = radius const textContent = `${prefix}${valueString}${suffix}` @@ -328,45 +328,15 @@ class Gauge extends Component { } private labelToString(value: number): string { - const {decimalPlaces} = this.props + const {isEnforced, digits} = this.props.decimalPlaces - let valueString - - if (decimalPlaces.isEnforced) { - const digits = Math.min(decimalPlaces.digits, MAX_TO_LOCALE_STRING_VAL) - valueString = value.toLocaleString(undefined, { - minimumFractionDigits: 0, - maximumFractionDigits: digits, - }) - } else { - valueString = value.toLocaleString(undefined, { - minimumFractionDigits: 0, - maximumFractionDigits: MAX_TO_LOCALE_STRING_VAL, - }) + if (isEnforced && digits) { + return value.toFixed(Math.min(digits, MAX_DECIMAL_PLACES)) } - return valueString - } - - private valueToString(value: number): string { - const {decimalPlaces} = this.props - - let valueString - - if (decimalPlaces.isEnforced) { - const digits = Math.min(decimalPlaces.digits, MAX_TO_LOCALE_STRING_VAL) - valueString = value.toLocaleString(undefined, { - minimumFractionDigits: digits, - maximumFractionDigits: digits, - }) - } else { - valueString = value.toLocaleString(undefined, { - minimumFractionDigits: 0, - maximumFractionDigits: MAX_TO_LOCALE_STRING_VAL, - }) - } - - return valueString + // Ensure the number has at most MAX_DECIMAL_PLACES digits after the + // decimal place + return String(Number(value.toFixed(MAX_DECIMAL_PLACES))) } private drawNeedle = (ctx, radius, minValue, maxValue) => { diff --git a/ui/src/shared/components/GaugeChart.test.tsx b/ui/src/shared/components/GaugeChart.test.tsx index cd3063f402..007861bc2c 100644 --- a/ui/src/shared/components/GaugeChart.test.tsx +++ b/ui/src/shared/components/GaugeChart.test.tsx @@ -71,7 +71,7 @@ describe('GaugeChart', () => { const wrapper = setup({tables}) expect(wrapper.find(Gauge).exists()).toBe(true) - expect(wrapper.find(Gauge).props().gaugePosition).toBe('2') + expect(wrapper.find(Gauge).props().gaugePosition).toBe(2) }) }) }) diff --git a/ui/src/shared/components/GaugeChart.tsx b/ui/src/shared/components/GaugeChart.tsx index 3f9623047e..0ab20ec44b 100644 --- a/ui/src/shared/components/GaugeChart.tsx +++ b/ui/src/shared/components/GaugeChart.tsx @@ -44,7 +44,7 @@ class GaugeChart extends PureComponent { const {values} = getLastValues(tables) const lastValue = _.get(values, 0, 0) - return lastValue + return Number(lastValue) } } diff --git a/ui/src/shared/components/view_options/options/DecimalPlaces.tsx b/ui/src/shared/components/view_options/options/DecimalPlaces.tsx index da3707b87d..8be25d1298 100644 --- a/ui/src/shared/components/view_options/options/DecimalPlaces.tsx +++ b/ui/src/shared/components/view_options/options/DecimalPlaces.tsx @@ -31,8 +31,8 @@ class DecimalPlacesOption extends PureComponent { inputPlaceholder="Enter a number" onChange={this.handleSetValue} value={this.value} - min={Number(MIN_DECIMAL_PLACES)} - max={Number(MAX_DECIMAL_PLACES)} + min={MIN_DECIMAL_PLACES} + max={MAX_DECIMAL_PLACES} /> ) From 66770afdc8f6242c083fa4d80dcb73aac717d2ce Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Tue, 11 Dec 2018 16:36:08 -0800 Subject: [PATCH 22/22] Use clockface dropdown in ColorDropdown component --- ui/src/shared/components/ColorDropdown.scss | 121 +----------------- ui/src/shared/components/ColorDropdown.tsx | 134 ++++---------------- 2 files changed, 32 insertions(+), 223 deletions(-) diff --git a/ui/src/shared/components/ColorDropdown.scss b/ui/src/shared/components/ColorDropdown.scss index 99f6901f7b..354f2f2ce5 100644 --- a/ui/src/shared/components/ColorDropdown.scss +++ b/ui/src/shared/components/ColorDropdown.scss @@ -1,119 +1,12 @@ -/* - Color Dropdown - ------------------------------------------------------------------------------ -*/ - -$color-dropdown--circle: 14px; -$color-dropdown--bar: 104px; -$color-dropdown--bar-height: 10px; -$color-dropdown--left-padding: 11px; -$color-dropdown--name-padding: 20px; - -.color-dropdown { - width: 140px; - height: 30px; - position: relative; -} - -.color-dropdown.color-dropdown--stretch { - width: 100%; -} - -.color-dropdown--toggle { - width: 100%; - position: relative; -} -.color-dropdown--toggle span.caret { - font-style: normal !important; - position: absolute; - top: 50%; - right: 11px; - transform: translateY(-50%); -} - -.color-dropdown--menu { - position: absolute; - top: 30px; - left: 0; - z-index: 5; - width: 100%; - border-radius: 4px; - box-shadow: 0 2px 5px 0.6px fade-out($g0-obsidian, 0.7); - @include gradient-h($g0-obsidian, $g2-kevlar); -} .color-dropdown--item { - @include no-user-select(); - width: 100%; - height: 28px; - position: relative; - color: $g11-sidewalk; - transition: color 0.25s ease, background-color 0.25s ease; + display: flex; + align-items: center; + justify-content: flex-start; +} - &:hover { - background-color: $g4-onyx; - color: $g18-cloud; - } - &:hover, - &:hover > * { - cursor: pointer !important; - } - &.active { - background-color: $g3-castle; - color: $g15-platinum; - } - &:first-child { - border-radius: 4px 4px 0 0; - } - &:last-child { - border-radius: 0 0 4px 4px; - } -} -.color-dropdown--swatch, -.color-dropdown--swatches, -.color-dropdown--name { - position: absolute; - top: 50%; - transform: translateY(-50%); -} .color-dropdown--swatch { - width: $color-dropdown--circle; - height: $color-dropdown--circle; + width: 14px; + height: 14px; border-radius: 50%; - left: $color-dropdown--left-padding; -} -.color-dropdown--swatches { - width: $color-dropdown--bar; - height: $color-dropdown--bar-height; - border-radius: $color-dropdown--bar-height / 2; - left: $color-dropdown--left-padding; -} -.color-dropdown--name { - text-align: left; - right: $color-dropdown--name-padding; - left: $color-dropdown--circle + $color-dropdown--name-padding; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 13px; - font-weight: 600; - text-transform: capitalize; - - .color-dropdown--swatches + & { - left: $color-dropdown--bar + $color-dropdown--name-padding; - } -} -.color-dropdown - .color-dropdown--menu - .fancy-scroll--container - .fancy-scroll--track-v - .fancy-scroll--thumb-v { - @include gradient-v($g9-mountain, $g7-graphite); -} -.color-dropdown--toggle.color-dropdown__disabled { - color: $g7-graphite; - font-style: italic; - cursor: not-allowed; -} -.color-dropdown--toggle.color-dropdown__disabled > .color-dropdown--swatch { - background-color: $g7-graphite !important; + margin-right: 5px; } diff --git a/ui/src/shared/components/ColorDropdown.tsx b/ui/src/shared/components/ColorDropdown.tsx index e8bb6a00be..09ef18a011 100644 --- a/ui/src/shared/components/ColorDropdown.tsx +++ b/ui/src/shared/components/ColorDropdown.tsx @@ -1,16 +1,8 @@ // Libraries -import React, {Component} from 'react' -import classnames from 'classnames' +import React, {SFC} from 'react' // Components -import {ClickOutside} from 'src/shared/components/ClickOutside' -import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar' - -// Utils -import {ErrorHandling} from 'src/shared/decorators/errors' - -// Constants -import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index' +import {Dropdown, ComponentStatus} from 'src/clockface' // Types import {ColorLabel} from 'src/types/colors' @@ -23,110 +15,34 @@ interface Props { onChoose: (colors: ColorLabel) => void } -interface State { - expanded: boolean -} +const titleCase = (name: string) => `${name[0].toUpperCase()}${name.slice(1)}` -@ErrorHandling -export default class ColorDropdown extends Component { - public static defaultProps: Partial = { - stretchToFit: false, - disabled: false, - } +const ColorDropdown: SFC = props => { + const {selected, colors, onChoose, disabled, stretchToFit} = props - public state: State = {expanded: false} + const status = disabled ? ComponentStatus.Disabled : ComponentStatus.Default + const widthPixels = stretchToFit ? null : 200 - public render() { - const {expanded} = this.state - const {selected} = this.props - - return ( - -
-
+ return ( + + {colors.map(color => ( + +
-
{selected.name}
- +
{titleCase(color.name)}
- {expanded && this.renderMenu} -
- - ) - } - - private get dropdownClassNames(): string { - const {stretchToFit} = this.props - const {expanded} = this.state - - return classnames('color-dropdown', { - open: expanded, - 'color-dropdown--stretch': stretchToFit, - }) - } - - private get buttonClassNames(): string { - const {disabled} = this.props - const {expanded} = this.state - - return classnames('btn btn-sm btn-default color-dropdown--toggle', { - active: expanded, - 'color-dropdown__disabled': disabled, - }) - } - - private get renderMenu(): JSX.Element { - const {colors, selected} = this.props - - return ( -
- - {colors.map((color, i) => ( -
- - {color.name} -
- ))} -
-
- ) - } - - private handleToggleMenu = (): void => { - const {disabled} = this.props - - if (disabled) { - return - } - this.setState({expanded: !this.state.expanded}) - } - - private handleClickOutside = (): void => { - this.setState({expanded: false}) - } - - private handleColorClick = color => (): void => { - this.props.onChoose(color) - this.setState({expanded: false}) - } +
+ ))} +
+ ) } + +export default ColorDropdown