mirror of https://github.com/apache/druid.git
Web console: misc fixes and SQL query re-formatting (#14906)
* better dialog formatting * use CSS to render triangle * can flatten in kafka also * better formatting * better format * fill in empty values in line chart * more fp * add show others
This commit is contained in:
parent
9142f4b8d7
commit
30c49c4cfc
|
@ -4874,8 +4874,8 @@ name: "@babel/code-frame"
|
|||
license_category: binary
|
||||
module: web-console
|
||||
license_name: MIT License
|
||||
copyright: Sebastian McKenzie
|
||||
version: 7.12.11
|
||||
copyright: The Babel Team
|
||||
version: 7.22.10
|
||||
license_file_path: licenses/bin/@babel-code-frame.MIT
|
||||
|
||||
---
|
||||
|
@ -4884,18 +4884,28 @@ name: "@babel/helper-module-imports"
|
|||
license_category: binary
|
||||
module: web-console
|
||||
license_name: MIT License
|
||||
copyright: Logan Smyth
|
||||
version: 7.13.12
|
||||
copyright: The Babel Team
|
||||
version: 7.22.5
|
||||
license_file_path: licenses/bin/@babel-helper-module-imports.MIT
|
||||
|
||||
---
|
||||
|
||||
name: "@babel/helper-string-parser"
|
||||
license_category: binary
|
||||
module: web-console
|
||||
license_name: MIT License
|
||||
copyright: The Babel Team
|
||||
version: 7.22.5
|
||||
license_file_path: licenses/bin/@babel-helper-string-parser.MIT
|
||||
|
||||
---
|
||||
|
||||
name: "@babel/helper-validator-identifier"
|
||||
license_category: binary
|
||||
module: web-console
|
||||
license_name: MIT License
|
||||
copyright: The Babel Team
|
||||
version: 7.19.1
|
||||
version: 7.22.5
|
||||
license_file_path: licenses/bin/@babel-helper-validator-identifier.MIT
|
||||
|
||||
---
|
||||
|
@ -4905,7 +4915,7 @@ license_category: binary
|
|||
module: web-console
|
||||
license_name: MIT License
|
||||
copyright: The Babel Team
|
||||
version: 7.18.6
|
||||
version: 7.22.10
|
||||
license_file_path: licenses/bin/@babel-highlight.MIT
|
||||
|
||||
---
|
||||
|
@ -4924,8 +4934,8 @@ name: "@babel/types"
|
|||
license_category: binary
|
||||
module: web-console
|
||||
license_name: MIT License
|
||||
copyright: Sebastian McKenzie
|
||||
version: 7.14.4
|
||||
copyright: The Babel Team
|
||||
version: 7.22.11
|
||||
license_file_path: licenses/bin/@babel-types.MIT
|
||||
|
||||
---
|
||||
|
@ -4998,7 +5008,7 @@ license_category: binary
|
|||
module: web-console
|
||||
license_name: Apache License version 2.0
|
||||
copyright: Imply Data
|
||||
version: 0.20.5
|
||||
version: 0.21.1
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,22 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,27 @@
|
|||
Copyright 2013-2016 Mike Bostock
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the author nor the names of contributors may be used to
|
||||
endorse or promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,21 @@
|
|||
Copyright (c) 2011 Alexander Shtuchkin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
Copyright (c) 2014-2016, Michael Bostock
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* The name Michael Bostock may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018 Nikita Skovoroda <chalkerx@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
File diff suppressed because it is too large
Load Diff
|
@ -68,7 +68,7 @@
|
|||
"@blueprintjs/datetime2": "^0.9.35",
|
||||
"@blueprintjs/icons": "^4.16.0",
|
||||
"@blueprintjs/popover2": "^1.14.9",
|
||||
"@druid-toolkit/query": "^0.20.5",
|
||||
"@druid-toolkit/query": "^0.21.1",
|
||||
"@druid-toolkit/visuals-core": "^0.3.3",
|
||||
"@druid-toolkit/visuals-react": "^0.3.3",
|
||||
"ace-builds": "~1.4.14",
|
||||
|
@ -109,7 +109,7 @@
|
|||
"@awesome-code-style/eslint-config": "^4.1.0",
|
||||
"@awesome-code-style/prettier-config": "^4.0.0",
|
||||
"@awesome-code-style/stylelint-config": "^4.0.0",
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/core": "^7.18.6",
|
||||
"@babel/preset-env": "^7.14.4",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/classnames": "^2.2.9",
|
||||
|
@ -172,7 +172,7 @@
|
|||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.5",
|
||||
"uuid": "^7.0.2",
|
||||
"webpack": "^5.33.2",
|
||||
"webpack": "^5.76.0",
|
||||
"webpack-bundle-analyzer": "^4.4.1",
|
||||
"webpack-cli": "^4.6.0",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
|
|
|
@ -73,6 +73,7 @@ export interface FancyNumericInputProps {
|
|||
value: number | undefined;
|
||||
defaultValue?: number;
|
||||
onValueChange(value: number): void;
|
||||
onValueEmpty?: () => void;
|
||||
|
||||
min?: number;
|
||||
max?: number;
|
||||
|
@ -98,6 +99,7 @@ export const FancyNumericInput = React.memo(function FancyNumericInput(
|
|||
value,
|
||||
defaultValue,
|
||||
onValueChange,
|
||||
onValueEmpty,
|
||||
|
||||
min,
|
||||
max,
|
||||
|
@ -139,8 +141,8 @@ export const FancyNumericInput = React.memo(function FancyNumericInput(
|
|||
}
|
||||
|
||||
function increment(delta: number): void {
|
||||
if (typeof shownNumberRaw !== 'number') return;
|
||||
changeValue(shownNumberRaw + delta);
|
||||
if (typeof shownNumberRaw !== 'number' && shownValue !== '') return;
|
||||
changeValue((shownNumberRaw ?? 0) + delta);
|
||||
}
|
||||
|
||||
function getIncrementSize(isShiftKeyPressed: boolean, isAltKeyPressed: boolean): number {
|
||||
|
@ -171,6 +173,9 @@ export const FancyNumericInput = React.memo(function FancyNumericInput(
|
|||
if (typeof shownNumber === 'number') {
|
||||
changeValue(shownNumber);
|
||||
}
|
||||
if (valueAsString === '' && onValueEmpty) {
|
||||
onValueEmpty();
|
||||
}
|
||||
}}
|
||||
onBlur={e => {
|
||||
setShownValue(numberToShown(effectiveValue));
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@import '../../variables';
|
||||
|
||||
.supervisor-reset-offsets-dialog {
|
||||
.#{$bp-ns}-dialog-body {
|
||||
position: relative;
|
||||
min-height: 50vh;
|
||||
overflow: auto;
|
||||
max-height: 80vh;
|
||||
}
|
||||
|
||||
.label-button {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
|
@ -19,10 +19,13 @@
|
|||
import { Button, Classes, Code, ControlGroup, Dialog, FormGroup, Intent } from '@blueprintjs/core';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Loader } from '../../components';
|
||||
import { FancyNumericInput } from '../../components/fancy-numeric-input/fancy-numeric-input';
|
||||
import { useQueryManager } from '../../hooks';
|
||||
import { Api, AppToaster } from '../../singletons';
|
||||
import { deepGet, getDruidErrorMessage } from '../../utils';
|
||||
import { deepDelete, deepGet, getDruidErrorMessage } from '../../utils';
|
||||
|
||||
import './supervisor-reset-offsets-dialog.scss';
|
||||
|
||||
type OffsetMap = Record<string, number>;
|
||||
|
||||
|
@ -50,6 +53,7 @@ export const SupervisorResetOffsetsDialog = React.memo(function SupervisorResetO
|
|||
|
||||
const stream = deepGet(statusResp.data || {}, 'payload.stream');
|
||||
const latestOffsets = deepGet(statusResp.data || {}, 'payload.latestOffsets');
|
||||
const latestOffsetsEntries = latestOffsets ? Object.entries(latestOffsets) : undefined;
|
||||
|
||||
async function onSave() {
|
||||
if (!stream) return;
|
||||
|
@ -89,29 +93,39 @@ export const SupervisorResetOffsetsDialog = React.memo(function SupervisorResetO
|
|||
onClose={onClose}
|
||||
title={`Set supervisor offsets: ${supervisorId}`}
|
||||
>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
{statusResp.loading && <Loader />}
|
||||
{latestOffsetsEntries && (
|
||||
<>
|
||||
<p>
|
||||
Set <Code>{supervisorId}</Code> to specific offsets
|
||||
</p>
|
||||
{latestOffsets &&
|
||||
Object.entries(latestOffsets).map(([key, latestOffset]) => (
|
||||
<FormGroup key={key}>
|
||||
{latestOffsetsEntries.map(([key, latestOffset]) => (
|
||||
<FormGroup key={key} label={key} helperText={`(currently: ${latestOffset})`}>
|
||||
<ControlGroup>
|
||||
<Button text={`${key} (currently: ${latestOffset})`} disabled />
|
||||
<Button className="label-button" text="New offset:" disabled />
|
||||
<FancyNumericInput
|
||||
value={offsetsToResetTo[key]}
|
||||
onValueChange={valueAsNumber => {
|
||||
setOffsetsToResetTo({ ...offsetsToResetTo, [key]: valueAsNumber });
|
||||
}}
|
||||
onValueEmpty={() => {
|
||||
setOffsetsToResetTo(deepDelete(offsetsToResetTo, key));
|
||||
}}
|
||||
min={0}
|
||||
fill
|
||||
placeholder={"Don't change"}
|
||||
placeholder="Don't change offset"
|
||||
/>
|
||||
</ControlGroup>
|
||||
</FormGroup>
|
||||
))}
|
||||
{latestOffsetsEntries.length === 0 && (
|
||||
<p>There are no partitions currently in this supervisor.</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button text="Close" onClick={onClose} />
|
||||
<Button text="Save" intent={Intent.PRIMARY} onClick={() => void onSave()} />
|
||||
|
|
|
@ -65,13 +65,15 @@ export const SUCCESS_ASYNC_STATUS: AsyncStatusResponse = {
|
|||
|
||||
/*
|
||||
REPLACE INTO "k" OVERWRITE ALL
|
||||
WITH "ext" AS (SELECT *
|
||||
WITH "ext" AS (
|
||||
SELECT *
|
||||
FROM TABLE(
|
||||
EXTERN(
|
||||
'{"type":"local","filter":"blah.json_","baseDir":"/"}',
|
||||
'{"type":"json"}'
|
||||
)
|
||||
) EXTEND ("timestamp" VARCHAR, "session" VARCHAR))
|
||||
) EXTEND ("timestamp" VARCHAR, "session" VARCHAR)
|
||||
)
|
||||
SELECT
|
||||
TIME_PARSE("timestamp") AS "__time",
|
||||
"session"
|
||||
|
|
|
@ -24,13 +24,15 @@ describe('ingest-query-pattern', () => {
|
|||
it('works', () => {
|
||||
const query = SqlQuery.parse(sane`
|
||||
INSERT INTO "kttm-2019"
|
||||
WITH "ext" AS (SELECT *
|
||||
WITH "ext" AS (
|
||||
SELECT *
|
||||
FROM TABLE(
|
||||
EXTERN(
|
||||
'{"type":"http","uris":["https://example.com/data.json.gz"]}',
|
||||
'{"type":"json"}'
|
||||
)
|
||||
) EXTEND ("timestamp" VARCHAR, "agent_category" VARCHAR, "agent_type" VARCHAR, "browser" VARCHAR, "browser_version" VARCHAR, "city" VARCHAR, "continent" VARCHAR, "country" VARCHAR, "version" VARCHAR, "event_type" VARCHAR, "event_subtype" VARCHAR, "loaded_image" VARCHAR, "adblock_list" VARCHAR, "forwarded_for" VARCHAR, "language" VARCHAR, "number" BIGINT, "os" VARCHAR, "path" VARCHAR, "platform" VARCHAR, "referrer" VARCHAR, "referrer_host" VARCHAR, "region" VARCHAR, "remote_address" VARCHAR, "screen" VARCHAR, "session" VARCHAR, "session_length" BIGINT, "timezone" VARCHAR, "timezone_offset" BIGINT, "window" VARCHAR))
|
||||
) EXTEND ("timestamp" VARCHAR, "agent_category" VARCHAR, "agent_type" VARCHAR, "browser" VARCHAR, "browser_version" VARCHAR, "city" VARCHAR, "continent" VARCHAR, "country" VARCHAR, "version" VARCHAR, "event_type" VARCHAR, "event_subtype" VARCHAR, "loaded_image" VARCHAR, "adblock_list" VARCHAR, "forwarded_for" VARCHAR, "language" VARCHAR, "number" BIGINT, "os" VARCHAR, "path" VARCHAR, "platform" VARCHAR, "referrer" VARCHAR, "referrer_host" VARCHAR, "region" VARCHAR, "remote_address" VARCHAR, "screen" VARCHAR, "session" VARCHAR, "session_length" BIGINT, "timezone" VARCHAR, "timezone_offset" BIGINT, "window" VARCHAR)
|
||||
)
|
||||
SELECT
|
||||
TIME_PARSE("timestamp") AS __time,
|
||||
agent_category,
|
||||
|
|
|
@ -22,7 +22,7 @@ import React from 'react';
|
|||
import type { Field } from '../../components';
|
||||
import { AutoForm, ExternalLink } from '../../components';
|
||||
import { getLink } from '../../links';
|
||||
import { compact, deepGet, deepSet, oneOf, typeIs, typeIsKnown } from '../../utils';
|
||||
import { compact, deepGet, deepSet, oneOf, typeIsKnown } from '../../utils';
|
||||
import type { FlattenSpec } from '../flatten-spec/flatten-spec';
|
||||
|
||||
export interface InputFormat {
|
||||
|
@ -523,10 +523,11 @@ export function issueWithInputFormat(inputFormat: InputFormat | undefined): stri
|
|||
return AutoForm.issueWithModel(inputFormat, BATCH_INPUT_FORMAT_FIELDS);
|
||||
}
|
||||
|
||||
export const inputFormatCanProduceNestedData: (inputFormat: InputFormat) => boolean = typeIs(
|
||||
'json',
|
||||
'parquet',
|
||||
'orc',
|
||||
'avro_ocf',
|
||||
'avro_stream',
|
||||
export function inputFormatCanProduceNestedData(inputFormat: InputFormat): boolean {
|
||||
if (inputFormat.type === 'kafka') {
|
||||
return Boolean(
|
||||
inputFormat.valueFormat && inputFormatCanProduceNestedData(inputFormat.valueFormat),
|
||||
);
|
||||
}
|
||||
return oneOf(inputFormat.type, 'json', 'parquet', 'orc', 'avro_ocf', 'avro_stream');
|
||||
}
|
||||
|
|
|
@ -379,18 +379,22 @@ export class WorkbenchQuery {
|
|||
}
|
||||
|
||||
public canPrettify(): boolean {
|
||||
return this.isJsonLike();
|
||||
return Boolean(this.isJsonLike() || this.parsedQuery);
|
||||
}
|
||||
|
||||
public prettify(): WorkbenchQuery {
|
||||
const queryString = this.getQueryString();
|
||||
let parsed;
|
||||
const { queryString, parsedQuery } = this;
|
||||
if (parsedQuery) {
|
||||
return this.changeQueryString(parsedQuery.prettify().toString());
|
||||
} else {
|
||||
let parsedJson;
|
||||
try {
|
||||
parsed = Hjson.parse(queryString);
|
||||
parsedJson = Hjson.parse(queryString);
|
||||
} catch {
|
||||
return this;
|
||||
}
|
||||
return this.changeQueryString(JSONBig.stringify(parsed, undefined, 2));
|
||||
return this.changeQueryString(JSONBig.stringify(parsedJson, undefined, 2));
|
||||
}
|
||||
}
|
||||
|
||||
public getIngestDatasource(): string | undefined {
|
||||
|
|
|
@ -52,9 +52,11 @@ describe('sample-query', () => {
|
|||
CAST("c1" AS VARCHAR) AS "host",
|
||||
CAST("c2" AS VARCHAR) AS "service",
|
||||
PARSE_JSON("c3") AS "msg"
|
||||
FROM (VALUES
|
||||
FROM (
|
||||
VALUES
|
||||
('2022-02-01T00:00:00.000Z', 'brokerA.internal', 'broker', '"{\\"type\\":\\"sys\\",\\"swap/free\\":1223334,\\"swap/max\\":3223334}"'),
|
||||
('2022-02-01T00:00:00.000Z', 'brokerA.internal', 'broker', '"{\\"type\\":\\"query\\",\\"time\\":1223,\\"bytes\\":2434234}"')) AS "t" ("c0", "c1", "c2", "c3")
|
||||
('2022-02-01T00:00:00.000Z', 'brokerA.internal', 'broker', '"{\\"type\\":\\"query\\",\\"time\\":1223,\\"bytes\\":2434234}"')
|
||||
) AS "t" ("c0", "c1", "c2", "c3")
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -237,13 +237,15 @@ describe('sql', () => {
|
|||
it('works with replace query', () => {
|
||||
const text = sane`
|
||||
REPLACE INTO "wikipedia" OVERWRITE ALL
|
||||
WITH "ext" AS (SELECT *
|
||||
WITH "ext" AS (
|
||||
SELECT *
|
||||
FROM TABLE(
|
||||
EXTERN(
|
||||
'{"type":"http","uris":["https://druid.apache.org/data/wikipedia.json.gz"]}',
|
||||
'{"type":"json"}'
|
||||
)
|
||||
) EXTEND ("isRobot" VARCHAR, "channel" VARCHAR, "timestamp" VARCHAR))
|
||||
) EXTEND ("isRobot" VARCHAR, "channel" VARCHAR, "timestamp" VARCHAR)
|
||||
)
|
||||
SELECT
|
||||
TIME_PARSE("timestamp") AS "__time",
|
||||
"isRobot",
|
||||
|
@ -257,19 +259,21 @@ describe('sql', () => {
|
|||
expect(found).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"endOffset": 363,
|
||||
"endOffset": 379,
|
||||
"endRowColumn": Object {
|
||||
"column": 18,
|
||||
"row": 13,
|
||||
"row": 15,
|
||||
},
|
||||
"sql": "REPLACE INTO \\"wikipedia\\" OVERWRITE ALL
|
||||
WITH \\"ext\\" AS (SELECT *
|
||||
WITH \\"ext\\" AS (
|
||||
SELECT *
|
||||
FROM TABLE(
|
||||
EXTERN(
|
||||
'{\\"type\\":\\"http\\",\\"uris\\":[\\"https://druid.apache.org/data/wikipedia.json.gz\\"]}',
|
||||
'{\\"type\\":\\"json\\"}'
|
||||
)
|
||||
) EXTEND (\\"isRobot\\" VARCHAR, \\"channel\\" VARCHAR, \\"timestamp\\" VARCHAR))
|
||||
) EXTEND (\\"isRobot\\" VARCHAR, \\"channel\\" VARCHAR, \\"timestamp\\" VARCHAR)
|
||||
)
|
||||
SELECT
|
||||
TIME_PARSE(\\"timestamp\\") AS \\"__time\\",
|
||||
\\"isRobot\\",
|
||||
|
@ -283,18 +287,20 @@ describe('sql', () => {
|
|||
},
|
||||
},
|
||||
Object {
|
||||
"endOffset": 344,
|
||||
"endOffset": 360,
|
||||
"endRowColumn": Object {
|
||||
"column": 10,
|
||||
"row": 12,
|
||||
"row": 14,
|
||||
},
|
||||
"sql": "WITH \\"ext\\" AS (SELECT *
|
||||
"sql": "WITH \\"ext\\" AS (
|
||||
SELECT *
|
||||
FROM TABLE(
|
||||
EXTERN(
|
||||
'{\\"type\\":\\"http\\",\\"uris\\":[\\"https://druid.apache.org/data/wikipedia.json.gz\\"]}',
|
||||
'{\\"type\\":\\"json\\"}'
|
||||
)
|
||||
) EXTEND (\\"isRobot\\" VARCHAR, \\"channel\\" VARCHAR, \\"timestamp\\" VARCHAR))
|
||||
) EXTEND (\\"isRobot\\" VARCHAR, \\"channel\\" VARCHAR, \\"timestamp\\" VARCHAR)
|
||||
)
|
||||
SELECT
|
||||
TIME_PARSE(\\"timestamp\\") AS \\"__time\\",
|
||||
\\"isRobot\\",
|
||||
|
@ -307,10 +313,10 @@ describe('sql', () => {
|
|||
},
|
||||
},
|
||||
Object {
|
||||
"endOffset": 261,
|
||||
"endOffset": 276,
|
||||
"endRowColumn": Object {
|
||||
"column": 68,
|
||||
"row": 7,
|
||||
"column": 70,
|
||||
"row": 8,
|
||||
},
|
||||
"sql": "SELECT *
|
||||
FROM TABLE(
|
||||
|
@ -319,27 +325,27 @@ describe('sql', () => {
|
|||
'{\\"type\\":\\"json\\"}'
|
||||
)
|
||||
) EXTEND (\\"isRobot\\" VARCHAR, \\"channel\\" VARCHAR, \\"timestamp\\" VARCHAR)",
|
||||
"startOffset": 54,
|
||||
"startOffset": 57,
|
||||
"startRowColumn": Object {
|
||||
"column": 15,
|
||||
"row": 1,
|
||||
"column": 2,
|
||||
"row": 2,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"endOffset": 344,
|
||||
"endOffset": 360,
|
||||
"endRowColumn": Object {
|
||||
"column": 10,
|
||||
"row": 12,
|
||||
"row": 14,
|
||||
},
|
||||
"sql": "SELECT
|
||||
TIME_PARSE(\\"timestamp\\") AS \\"__time\\",
|
||||
\\"isRobot\\",
|
||||
\\"channel\\"
|
||||
FROM \\"ext\\"",
|
||||
"startOffset": 263,
|
||||
"startOffset": 279,
|
||||
"startRowColumn": Object {
|
||||
"column": 0,
|
||||
"row": 8,
|
||||
"row": 10,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -16,24 +16,27 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { C, F, L, SqlExpression } from '@druid-toolkit/query';
|
||||
import { C, F, L, SqlCase, SqlExpression } from '@druid-toolkit/query';
|
||||
import { typedVisualModule } from '@druid-toolkit/visuals-core';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
import { getInitQuery } from '../utils';
|
||||
|
||||
function transformData(data: any[]): any[] {
|
||||
const OTHERS_VALUE = 'Others';
|
||||
|
||||
function transformData(data: any[], vs: string[]): Record<string, number>[] {
|
||||
const zeroDatum = Object.fromEntries(vs.map(v => [v, 0]));
|
||||
|
||||
let lastTime = -1;
|
||||
let lastDatum: any;
|
||||
let lastDatum: Record<string, number> | undefined;
|
||||
const ret = [];
|
||||
for (const d of data) {
|
||||
if (d.time.valueOf() === lastTime) {
|
||||
lastDatum[d.stack] = d.met;
|
||||
} else {
|
||||
if (d.time.valueOf() !== lastTime) {
|
||||
if (lastDatum) ret.push(lastDatum);
|
||||
lastTime = d.time.valueOf();
|
||||
lastDatum = { time: d.time, [d.stack]: d.met };
|
||||
lastDatum = { ...zeroDatum, time: d.time };
|
||||
}
|
||||
lastDatum![d.stack] = d.met;
|
||||
}
|
||||
if (lastDatum) ret.push(lastDatum);
|
||||
return ret;
|
||||
|
@ -73,6 +76,13 @@ export default typedVisualModule({
|
|||
visible: ({ params }) => Boolean(params.splitColumn),
|
||||
},
|
||||
},
|
||||
showOthers: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
control: {
|
||||
visible: ({ params }) => Boolean(params.splitColumn),
|
||||
},
|
||||
},
|
||||
metric: {
|
||||
type: 'aggregate',
|
||||
default: { expression: SqlExpression.parse('COUNT(*)'), name: 'Count', sqlType: 'BIGINT' },
|
||||
|
@ -136,7 +146,7 @@ export default typedVisualModule({
|
|||
|
||||
return {
|
||||
async update({ table, where, parameterValues }) {
|
||||
const { splitColumn, metric, numberToStack, timeGranularity } = parameterValues;
|
||||
const { splitColumn, metric, numberToStack, showOthers, timeGranularity } = parameterValues;
|
||||
|
||||
const vs = splitColumn
|
||||
? (
|
||||
|
@ -153,21 +163,30 @@ export default typedVisualModule({
|
|||
await host.sqlQuery(
|
||||
getInitQuery(
|
||||
table,
|
||||
splitColumn && vs ? where.and(splitColumn.expression.in(vs)) : where,
|
||||
splitColumn && vs && !showOthers ? where.and(splitColumn.expression.in(vs)) : where,
|
||||
)
|
||||
.addSelect(F.timeFloor(C('__time'), L(timeGranularity)).as('time'), {
|
||||
addToGroupBy: 'end',
|
||||
addToOrderBy: 'end',
|
||||
direction: 'ASC',
|
||||
})
|
||||
.applyIf(splitColumn, q =>
|
||||
q.addSelect(splitColumn!.expression.as('stack'), { addToGroupBy: 'end' }),
|
||||
)
|
||||
.applyIf(splitColumn, q => {
|
||||
const splitEx = splitColumn!.expression;
|
||||
return q.addSelect(
|
||||
(showOthers
|
||||
? SqlCase.ifThenElse(splitEx.in(vs!), splitEx, L(OTHERS_VALUE))
|
||||
: splitEx
|
||||
).as('stack'),
|
||||
{ addToGroupBy: 'end' },
|
||||
);
|
||||
})
|
||||
.addSelect(metric.expression.as('met')),
|
||||
)
|
||||
).toObjectArray();
|
||||
|
||||
const sourceData = vs ? transformData(dataset) : dataset;
|
||||
const effectiveVs = vs && showOthers ? vs.concat(OTHERS_VALUE) : vs;
|
||||
const sourceData = effectiveVs ? transformData(dataset, effectiveVs) : dataset;
|
||||
|
||||
const showSymbol = sourceData.length < 2;
|
||||
myChart.setOption(
|
||||
{
|
||||
|
@ -175,15 +194,15 @@ export default typedVisualModule({
|
|||
dimensions: ['time'].concat(vs || ['met']),
|
||||
source: sourceData,
|
||||
},
|
||||
legend: vs
|
||||
legend: effectiveVs
|
||||
? {
|
||||
data: vs,
|
||||
data: effectiveVs,
|
||||
}
|
||||
: undefined,
|
||||
series: (vs || ['met']).map(v => {
|
||||
series: (effectiveVs || ['met']).map(v => {
|
||||
return {
|
||||
id: v,
|
||||
name: v,
|
||||
name: effectiveVs ? v : metric.name,
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
showSymbol,
|
||||
|
|
|
@ -36,44 +36,36 @@
|
|||
cursor: pointer;
|
||||
|
||||
&:before {
|
||||
content: '⏵';
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: $blue3;
|
||||
color: white;
|
||||
line-height: 12px;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 5px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 3px solid transparent;
|
||||
border-bottom: 3px solid transparent;
|
||||
border-left: 6px solid $white;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:before {
|
||||
background: $blue2;
|
||||
}
|
||||
|
||||
&:hover:after {
|
||||
content: 'Run';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 16px;
|
||||
right: 0;
|
||||
background: #383d57;
|
||||
text-align: left;
|
||||
animation: sharpFadeIn 1s;
|
||||
&:after {
|
||||
border-left-color: $gray5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sharpFadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
90% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue