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
|
license_category: binary
|
||||||
module: web-console
|
module: web-console
|
||||||
license_name: MIT License
|
license_name: MIT License
|
||||||
copyright: Sebastian McKenzie
|
copyright: The Babel Team
|
||||||
version: 7.12.11
|
version: 7.22.10
|
||||||
license_file_path: licenses/bin/@babel-code-frame.MIT
|
license_file_path: licenses/bin/@babel-code-frame.MIT
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -4884,18 +4884,28 @@ name: "@babel/helper-module-imports"
|
||||||
license_category: binary
|
license_category: binary
|
||||||
module: web-console
|
module: web-console
|
||||||
license_name: MIT License
|
license_name: MIT License
|
||||||
copyright: Logan Smyth
|
copyright: The Babel Team
|
||||||
version: 7.13.12
|
version: 7.22.5
|
||||||
license_file_path: licenses/bin/@babel-helper-module-imports.MIT
|
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"
|
name: "@babel/helper-validator-identifier"
|
||||||
license_category: binary
|
license_category: binary
|
||||||
module: web-console
|
module: web-console
|
||||||
license_name: MIT License
|
license_name: MIT License
|
||||||
copyright: The Babel Team
|
copyright: The Babel Team
|
||||||
version: 7.19.1
|
version: 7.22.5
|
||||||
license_file_path: licenses/bin/@babel-helper-validator-identifier.MIT
|
license_file_path: licenses/bin/@babel-helper-validator-identifier.MIT
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -4905,7 +4915,7 @@ license_category: binary
|
||||||
module: web-console
|
module: web-console
|
||||||
license_name: MIT License
|
license_name: MIT License
|
||||||
copyright: The Babel Team
|
copyright: The Babel Team
|
||||||
version: 7.18.6
|
version: 7.22.10
|
||||||
license_file_path: licenses/bin/@babel-highlight.MIT
|
license_file_path: licenses/bin/@babel-highlight.MIT
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -4924,8 +4934,8 @@ name: "@babel/types"
|
||||||
license_category: binary
|
license_category: binary
|
||||||
module: web-console
|
module: web-console
|
||||||
license_name: MIT License
|
license_name: MIT License
|
||||||
copyright: Sebastian McKenzie
|
copyright: The Babel Team
|
||||||
version: 7.14.4
|
version: 7.22.11
|
||||||
license_file_path: licenses/bin/@babel-types.MIT
|
license_file_path: licenses/bin/@babel-types.MIT
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -4998,7 +5008,7 @@ license_category: binary
|
||||||
module: web-console
|
module: web-console
|
||||||
license_name: Apache License version 2.0
|
license_name: Apache License version 2.0
|
||||||
copyright: Imply Data
|
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/datetime2": "^0.9.35",
|
||||||
"@blueprintjs/icons": "^4.16.0",
|
"@blueprintjs/icons": "^4.16.0",
|
||||||
"@blueprintjs/popover2": "^1.14.9",
|
"@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-core": "^0.3.3",
|
||||||
"@druid-toolkit/visuals-react": "^0.3.3",
|
"@druid-toolkit/visuals-react": "^0.3.3",
|
||||||
"ace-builds": "~1.4.14",
|
"ace-builds": "~1.4.14",
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
"@awesome-code-style/eslint-config": "^4.1.0",
|
"@awesome-code-style/eslint-config": "^4.1.0",
|
||||||
"@awesome-code-style/prettier-config": "^4.0.0",
|
"@awesome-code-style/prettier-config": "^4.0.0",
|
||||||
"@awesome-code-style/stylelint-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",
|
"@babel/preset-env": "^7.14.4",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@types/classnames": "^2.2.9",
|
"@types/classnames": "^2.2.9",
|
||||||
|
@ -172,7 +172,7 @@
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"uuid": "^7.0.2",
|
"uuid": "^7.0.2",
|
||||||
"webpack": "^5.33.2",
|
"webpack": "^5.76.0",
|
||||||
"webpack-bundle-analyzer": "^4.4.1",
|
"webpack-bundle-analyzer": "^4.4.1",
|
||||||
"webpack-cli": "^4.6.0",
|
"webpack-cli": "^4.6.0",
|
||||||
"webpack-dev-server": "^3.11.2"
|
"webpack-dev-server": "^3.11.2"
|
||||||
|
|
|
@ -73,6 +73,7 @@ export interface FancyNumericInputProps {
|
||||||
value: number | undefined;
|
value: number | undefined;
|
||||||
defaultValue?: number;
|
defaultValue?: number;
|
||||||
onValueChange(value: number): void;
|
onValueChange(value: number): void;
|
||||||
|
onValueEmpty?: () => void;
|
||||||
|
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
|
@ -98,6 +99,7 @@ export const FancyNumericInput = React.memo(function FancyNumericInput(
|
||||||
value,
|
value,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
onValueChange,
|
onValueChange,
|
||||||
|
onValueEmpty,
|
||||||
|
|
||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
|
@ -139,8 +141,8 @@ export const FancyNumericInput = React.memo(function FancyNumericInput(
|
||||||
}
|
}
|
||||||
|
|
||||||
function increment(delta: number): void {
|
function increment(delta: number): void {
|
||||||
if (typeof shownNumberRaw !== 'number') return;
|
if (typeof shownNumberRaw !== 'number' && shownValue !== '') return;
|
||||||
changeValue(shownNumberRaw + delta);
|
changeValue((shownNumberRaw ?? 0) + delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIncrementSize(isShiftKeyPressed: boolean, isAltKeyPressed: boolean): number {
|
function getIncrementSize(isShiftKeyPressed: boolean, isAltKeyPressed: boolean): number {
|
||||||
|
@ -171,6 +173,9 @@ export const FancyNumericInput = React.memo(function FancyNumericInput(
|
||||||
if (typeof shownNumber === 'number') {
|
if (typeof shownNumber === 'number') {
|
||||||
changeValue(shownNumber);
|
changeValue(shownNumber);
|
||||||
}
|
}
|
||||||
|
if (valueAsString === '' && onValueEmpty) {
|
||||||
|
onValueEmpty();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onBlur={e => {
|
onBlur={e => {
|
||||||
setShownValue(numberToShown(effectiveValue));
|
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 { Button, Classes, Code, ControlGroup, Dialog, FormGroup, Intent } from '@blueprintjs/core';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { Loader } from '../../components';
|
||||||
import { FancyNumericInput } from '../../components/fancy-numeric-input/fancy-numeric-input';
|
import { FancyNumericInput } from '../../components/fancy-numeric-input/fancy-numeric-input';
|
||||||
import { useQueryManager } from '../../hooks';
|
import { useQueryManager } from '../../hooks';
|
||||||
import { Api, AppToaster } from '../../singletons';
|
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>;
|
type OffsetMap = Record<string, number>;
|
||||||
|
|
||||||
|
@ -50,6 +53,7 @@ export const SupervisorResetOffsetsDialog = React.memo(function SupervisorResetO
|
||||||
|
|
||||||
const stream = deepGet(statusResp.data || {}, 'payload.stream');
|
const stream = deepGet(statusResp.data || {}, 'payload.stream');
|
||||||
const latestOffsets = deepGet(statusResp.data || {}, 'payload.latestOffsets');
|
const latestOffsets = deepGet(statusResp.data || {}, 'payload.latestOffsets');
|
||||||
|
const latestOffsetsEntries = latestOffsets ? Object.entries(latestOffsets) : undefined;
|
||||||
|
|
||||||
async function onSave() {
|
async function onSave() {
|
||||||
if (!stream) return;
|
if (!stream) return;
|
||||||
|
@ -89,29 +93,39 @@ export const SupervisorResetOffsetsDialog = React.memo(function SupervisorResetO
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={`Set supervisor offsets: ${supervisorId}`}
|
title={`Set supervisor offsets: ${supervisorId}`}
|
||||||
>
|
>
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<div className={Classes.DIALOG_BODY}>
|
||||||
<div className={Classes.DIALOG_BODY}>
|
{statusResp.loading && <Loader />}
|
||||||
<p>
|
{latestOffsetsEntries && (
|
||||||
Set <Code>{supervisorId}</Code> to specific offsets
|
<>
|
||||||
</p>
|
<p>
|
||||||
{latestOffsets &&
|
Set <Code>{supervisorId}</Code> to specific offsets
|
||||||
Object.entries(latestOffsets).map(([key, latestOffset]) => (
|
</p>
|
||||||
<FormGroup key={key}>
|
{latestOffsetsEntries.map(([key, latestOffset]) => (
|
||||||
|
<FormGroup key={key} label={key} helperText={`(currently: ${latestOffset})`}>
|
||||||
<ControlGroup>
|
<ControlGroup>
|
||||||
<Button text={`${key} (currently: ${latestOffset})`} disabled />
|
<Button className="label-button" text="New offset:" disabled />
|
||||||
<FancyNumericInput
|
<FancyNumericInput
|
||||||
value={offsetsToResetTo[key]}
|
value={offsetsToResetTo[key]}
|
||||||
onValueChange={valueAsNumber => {
|
onValueChange={valueAsNumber => {
|
||||||
setOffsetsToResetTo({ ...offsetsToResetTo, [key]: valueAsNumber });
|
setOffsetsToResetTo({ ...offsetsToResetTo, [key]: valueAsNumber });
|
||||||
}}
|
}}
|
||||||
|
onValueEmpty={() => {
|
||||||
|
setOffsetsToResetTo(deepDelete(offsetsToResetTo, key));
|
||||||
|
}}
|
||||||
min={0}
|
min={0}
|
||||||
fill
|
fill
|
||||||
placeholder={"Don't change"}
|
placeholder="Don't change offset"
|
||||||
/>
|
/>
|
||||||
</ControlGroup>
|
</ControlGroup>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
))}
|
))}
|
||||||
</div>
|
{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}>
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
<Button text="Close" onClick={onClose} />
|
<Button text="Close" onClick={onClose} />
|
||||||
<Button text="Save" intent={Intent.PRIMARY} onClick={() => void onSave()} />
|
<Button text="Save" intent={Intent.PRIMARY} onClick={() => void onSave()} />
|
||||||
|
|
|
@ -65,13 +65,15 @@ export const SUCCESS_ASYNC_STATUS: AsyncStatusResponse = {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
REPLACE INTO "k" OVERWRITE ALL
|
REPLACE INTO "k" OVERWRITE ALL
|
||||||
WITH "ext" AS (SELECT *
|
WITH "ext" AS (
|
||||||
FROM TABLE(
|
SELECT *
|
||||||
EXTERN(
|
FROM TABLE(
|
||||||
'{"type":"local","filter":"blah.json_","baseDir":"/"}',
|
EXTERN(
|
||||||
'{"type":"json"}'
|
'{"type":"local","filter":"blah.json_","baseDir":"/"}',
|
||||||
)
|
'{"type":"json"}'
|
||||||
) EXTEND ("timestamp" VARCHAR, "session" VARCHAR))
|
)
|
||||||
|
) EXTEND ("timestamp" VARCHAR, "session" VARCHAR)
|
||||||
|
)
|
||||||
SELECT
|
SELECT
|
||||||
TIME_PARSE("timestamp") AS "__time",
|
TIME_PARSE("timestamp") AS "__time",
|
||||||
"session"
|
"session"
|
||||||
|
|
|
@ -24,13 +24,15 @@ describe('ingest-query-pattern', () => {
|
||||||
it('works', () => {
|
it('works', () => {
|
||||||
const query = SqlQuery.parse(sane`
|
const query = SqlQuery.parse(sane`
|
||||||
INSERT INTO "kttm-2019"
|
INSERT INTO "kttm-2019"
|
||||||
WITH "ext" AS (SELECT *
|
WITH "ext" AS (
|
||||||
FROM TABLE(
|
SELECT *
|
||||||
EXTERN(
|
FROM TABLE(
|
||||||
'{"type":"http","uris":["https://example.com/data.json.gz"]}',
|
EXTERN(
|
||||||
'{"type":"json"}'
|
'{"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
|
SELECT
|
||||||
TIME_PARSE("timestamp") AS __time,
|
TIME_PARSE("timestamp") AS __time,
|
||||||
agent_category,
|
agent_category,
|
||||||
|
|
|
@ -22,7 +22,7 @@ import React from 'react';
|
||||||
import type { Field } from '../../components';
|
import type { Field } from '../../components';
|
||||||
import { AutoForm, ExternalLink } from '../../components';
|
import { AutoForm, ExternalLink } from '../../components';
|
||||||
import { getLink } from '../../links';
|
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';
|
import type { FlattenSpec } from '../flatten-spec/flatten-spec';
|
||||||
|
|
||||||
export interface InputFormat {
|
export interface InputFormat {
|
||||||
|
@ -523,10 +523,11 @@ export function issueWithInputFormat(inputFormat: InputFormat | undefined): stri
|
||||||
return AutoForm.issueWithModel(inputFormat, BATCH_INPUT_FORMAT_FIELDS);
|
return AutoForm.issueWithModel(inputFormat, BATCH_INPUT_FORMAT_FIELDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const inputFormatCanProduceNestedData: (inputFormat: InputFormat) => boolean = typeIs(
|
export function inputFormatCanProduceNestedData(inputFormat: InputFormat): boolean {
|
||||||
'json',
|
if (inputFormat.type === 'kafka') {
|
||||||
'parquet',
|
return Boolean(
|
||||||
'orc',
|
inputFormat.valueFormat && inputFormatCanProduceNestedData(inputFormat.valueFormat),
|
||||||
'avro_ocf',
|
);
|
||||||
'avro_stream',
|
}
|
||||||
);
|
return oneOf(inputFormat.type, 'json', 'parquet', 'orc', 'avro_ocf', 'avro_stream');
|
||||||
|
}
|
||||||
|
|
|
@ -379,18 +379,22 @@ export class WorkbenchQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
public canPrettify(): boolean {
|
public canPrettify(): boolean {
|
||||||
return this.isJsonLike();
|
return Boolean(this.isJsonLike() || this.parsedQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
public prettify(): WorkbenchQuery {
|
public prettify(): WorkbenchQuery {
|
||||||
const queryString = this.getQueryString();
|
const { queryString, parsedQuery } = this;
|
||||||
let parsed;
|
if (parsedQuery) {
|
||||||
try {
|
return this.changeQueryString(parsedQuery.prettify().toString());
|
||||||
parsed = Hjson.parse(queryString);
|
} else {
|
||||||
} catch {
|
let parsedJson;
|
||||||
return this;
|
try {
|
||||||
|
parsedJson = Hjson.parse(queryString);
|
||||||
|
} catch {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return this.changeQueryString(JSONBig.stringify(parsedJson, undefined, 2));
|
||||||
}
|
}
|
||||||
return this.changeQueryString(JSONBig.stringify(parsed, undefined, 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getIngestDatasource(): string | undefined {
|
public getIngestDatasource(): string | undefined {
|
||||||
|
|
|
@ -52,9 +52,11 @@ describe('sample-query', () => {
|
||||||
CAST("c1" AS VARCHAR) AS "host",
|
CAST("c1" AS VARCHAR) AS "host",
|
||||||
CAST("c2" AS VARCHAR) AS "service",
|
CAST("c2" AS VARCHAR) AS "service",
|
||||||
PARSE_JSON("c3") AS "msg"
|
PARSE_JSON("c3") AS "msg"
|
||||||
FROM (VALUES
|
FROM (
|
||||||
('2022-02-01T00:00:00.000Z', 'brokerA.internal', 'broker', '"{\\"type\\":\\"sys\\",\\"swap/free\\":1223334,\\"swap/max\\":3223334}"'),
|
VALUES
|
||||||
('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\\":\\"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")
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -237,13 +237,15 @@ describe('sql', () => {
|
||||||
it('works with replace query', () => {
|
it('works with replace query', () => {
|
||||||
const text = sane`
|
const text = sane`
|
||||||
REPLACE INTO "wikipedia" OVERWRITE ALL
|
REPLACE INTO "wikipedia" OVERWRITE ALL
|
||||||
WITH "ext" AS (SELECT *
|
WITH "ext" AS (
|
||||||
FROM TABLE(
|
SELECT *
|
||||||
EXTERN(
|
FROM TABLE(
|
||||||
'{"type":"http","uris":["https://druid.apache.org/data/wikipedia.json.gz"]}',
|
EXTERN(
|
||||||
'{"type":"json"}'
|
'{"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
|
SELECT
|
||||||
TIME_PARSE("timestamp") AS "__time",
|
TIME_PARSE("timestamp") AS "__time",
|
||||||
"isRobot",
|
"isRobot",
|
||||||
|
@ -257,19 +259,21 @@ describe('sql', () => {
|
||||||
expect(found).toMatchInlineSnapshot(`
|
expect(found).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"endOffset": 363,
|
"endOffset": 379,
|
||||||
"endRowColumn": Object {
|
"endRowColumn": Object {
|
||||||
"column": 18,
|
"column": 18,
|
||||||
"row": 13,
|
"row": 15,
|
||||||
},
|
},
|
||||||
"sql": "REPLACE INTO \\"wikipedia\\" OVERWRITE ALL
|
"sql": "REPLACE INTO \\"wikipedia\\" OVERWRITE ALL
|
||||||
WITH \\"ext\\" AS (SELECT *
|
WITH \\"ext\\" AS (
|
||||||
FROM TABLE(
|
SELECT *
|
||||||
EXTERN(
|
FROM TABLE(
|
||||||
'{\\"type\\":\\"http\\",\\"uris\\":[\\"https://druid.apache.org/data/wikipedia.json.gz\\"]}',
|
EXTERN(
|
||||||
'{\\"type\\":\\"json\\"}'
|
'{\\"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
|
SELECT
|
||||||
TIME_PARSE(\\"timestamp\\") AS \\"__time\\",
|
TIME_PARSE(\\"timestamp\\") AS \\"__time\\",
|
||||||
\\"isRobot\\",
|
\\"isRobot\\",
|
||||||
|
@ -283,18 +287,20 @@ describe('sql', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"endOffset": 344,
|
"endOffset": 360,
|
||||||
"endRowColumn": Object {
|
"endRowColumn": Object {
|
||||||
"column": 10,
|
"column": 10,
|
||||||
"row": 12,
|
"row": 14,
|
||||||
},
|
},
|
||||||
"sql": "WITH \\"ext\\" AS (SELECT *
|
"sql": "WITH \\"ext\\" AS (
|
||||||
FROM TABLE(
|
SELECT *
|
||||||
EXTERN(
|
FROM TABLE(
|
||||||
'{\\"type\\":\\"http\\",\\"uris\\":[\\"https://druid.apache.org/data/wikipedia.json.gz\\"]}',
|
EXTERN(
|
||||||
'{\\"type\\":\\"json\\"}'
|
'{\\"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
|
SELECT
|
||||||
TIME_PARSE(\\"timestamp\\") AS \\"__time\\",
|
TIME_PARSE(\\"timestamp\\") AS \\"__time\\",
|
||||||
\\"isRobot\\",
|
\\"isRobot\\",
|
||||||
|
@ -307,39 +313,39 @@ describe('sql', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"endOffset": 261,
|
"endOffset": 276,
|
||||||
"endRowColumn": Object {
|
"endRowColumn": Object {
|
||||||
"column": 68,
|
"column": 70,
|
||||||
"row": 7,
|
"row": 8,
|
||||||
},
|
},
|
||||||
"sql": "SELECT *
|
"sql": "SELECT *
|
||||||
FROM TABLE(
|
FROM TABLE(
|
||||||
EXTERN(
|
EXTERN(
|
||||||
'{\\"type\\":\\"http\\",\\"uris\\":[\\"https://druid.apache.org/data/wikipedia.json.gz\\"]}',
|
'{\\"type\\":\\"http\\",\\"uris\\":[\\"https://druid.apache.org/data/wikipedia.json.gz\\"]}',
|
||||||
'{\\"type\\":\\"json\\"}'
|
'{\\"type\\":\\"json\\"}'
|
||||||
)
|
)
|
||||||
) EXTEND (\\"isRobot\\" VARCHAR, \\"channel\\" VARCHAR, \\"timestamp\\" VARCHAR)",
|
) EXTEND (\\"isRobot\\" VARCHAR, \\"channel\\" VARCHAR, \\"timestamp\\" VARCHAR)",
|
||||||
"startOffset": 54,
|
"startOffset": 57,
|
||||||
"startRowColumn": Object {
|
"startRowColumn": Object {
|
||||||
"column": 15,
|
"column": 2,
|
||||||
"row": 1,
|
"row": 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"endOffset": 344,
|
"endOffset": 360,
|
||||||
"endRowColumn": Object {
|
"endRowColumn": Object {
|
||||||
"column": 10,
|
"column": 10,
|
||||||
"row": 12,
|
"row": 14,
|
||||||
},
|
},
|
||||||
"sql": "SELECT
|
"sql": "SELECT
|
||||||
TIME_PARSE(\\"timestamp\\") AS \\"__time\\",
|
TIME_PARSE(\\"timestamp\\") AS \\"__time\\",
|
||||||
\\"isRobot\\",
|
\\"isRobot\\",
|
||||||
\\"channel\\"
|
\\"channel\\"
|
||||||
FROM \\"ext\\"",
|
FROM \\"ext\\"",
|
||||||
"startOffset": 263,
|
"startOffset": 279,
|
||||||
"startRowColumn": Object {
|
"startRowColumn": Object {
|
||||||
"column": 0,
|
"column": 0,
|
||||||
"row": 8,
|
"row": 10,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -16,24 +16,27 @@
|
||||||
* limitations under the License.
|
* 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 { typedVisualModule } from '@druid-toolkit/visuals-core';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
import { getInitQuery } from '../utils';
|
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 lastTime = -1;
|
||||||
let lastDatum: any;
|
let lastDatum: Record<string, number> | undefined;
|
||||||
const ret = [];
|
const ret = [];
|
||||||
for (const d of data) {
|
for (const d of data) {
|
||||||
if (d.time.valueOf() === lastTime) {
|
if (d.time.valueOf() !== lastTime) {
|
||||||
lastDatum[d.stack] = d.met;
|
|
||||||
} else {
|
|
||||||
if (lastDatum) ret.push(lastDatum);
|
if (lastDatum) ret.push(lastDatum);
|
||||||
lastTime = d.time.valueOf();
|
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);
|
if (lastDatum) ret.push(lastDatum);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -73,6 +76,13 @@ export default typedVisualModule({
|
||||||
visible: ({ params }) => Boolean(params.splitColumn),
|
visible: ({ params }) => Boolean(params.splitColumn),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
showOthers: {
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
control: {
|
||||||
|
visible: ({ params }) => Boolean(params.splitColumn),
|
||||||
|
},
|
||||||
|
},
|
||||||
metric: {
|
metric: {
|
||||||
type: 'aggregate',
|
type: 'aggregate',
|
||||||
default: { expression: SqlExpression.parse('COUNT(*)'), name: 'Count', sqlType: 'BIGINT' },
|
default: { expression: SqlExpression.parse('COUNT(*)'), name: 'Count', sqlType: 'BIGINT' },
|
||||||
|
@ -136,7 +146,7 @@ export default typedVisualModule({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async update({ table, where, parameterValues }) {
|
async update({ table, where, parameterValues }) {
|
||||||
const { splitColumn, metric, numberToStack, timeGranularity } = parameterValues;
|
const { splitColumn, metric, numberToStack, showOthers, timeGranularity } = parameterValues;
|
||||||
|
|
||||||
const vs = splitColumn
|
const vs = splitColumn
|
||||||
? (
|
? (
|
||||||
|
@ -153,21 +163,30 @@ export default typedVisualModule({
|
||||||
await host.sqlQuery(
|
await host.sqlQuery(
|
||||||
getInitQuery(
|
getInitQuery(
|
||||||
table,
|
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'), {
|
.addSelect(F.timeFloor(C('__time'), L(timeGranularity)).as('time'), {
|
||||||
addToGroupBy: 'end',
|
addToGroupBy: 'end',
|
||||||
addToOrderBy: 'end',
|
addToOrderBy: 'end',
|
||||||
direction: 'ASC',
|
direction: 'ASC',
|
||||||
})
|
})
|
||||||
.applyIf(splitColumn, q =>
|
.applyIf(splitColumn, q => {
|
||||||
q.addSelect(splitColumn!.expression.as('stack'), { addToGroupBy: 'end' }),
|
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')),
|
.addSelect(metric.expression.as('met')),
|
||||||
)
|
)
|
||||||
).toObjectArray();
|
).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;
|
const showSymbol = sourceData.length < 2;
|
||||||
myChart.setOption(
|
myChart.setOption(
|
||||||
{
|
{
|
||||||
|
@ -175,15 +194,15 @@ export default typedVisualModule({
|
||||||
dimensions: ['time'].concat(vs || ['met']),
|
dimensions: ['time'].concat(vs || ['met']),
|
||||||
source: sourceData,
|
source: sourceData,
|
||||||
},
|
},
|
||||||
legend: vs
|
legend: effectiveVs
|
||||||
? {
|
? {
|
||||||
data: vs,
|
data: effectiveVs,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
series: (vs || ['met']).map(v => {
|
series: (effectiveVs || ['met']).map(v => {
|
||||||
return {
|
return {
|
||||||
id: v,
|
id: v,
|
||||||
name: v,
|
name: effectiveVs ? v : metric.name,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
stack: 'Total',
|
stack: 'Total',
|
||||||
showSymbol,
|
showSymbol,
|
||||||
|
|
|
@ -36,44 +36,36 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: '⏵';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 3px;
|
top: 3px;
|
||||||
left: 2px;
|
left: 2px;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
background: $blue3;
|
background: $blue3;
|
||||||
color: white;
|
|
||||||
line-height: 12px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover:before {
|
&:after {
|
||||||
background: $blue2;
|
content: '';
|
||||||
}
|
|
||||||
|
|
||||||
&:hover:after {
|
|
||||||
content: 'Run';
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 6px;
|
||||||
left: 16px;
|
left: 5px;
|
||||||
right: 0;
|
width: 0;
|
||||||
background: #383d57;
|
height: 0;
|
||||||
text-align: left;
|
border-top: 3px solid transparent;
|
||||||
animation: sharpFadeIn 1s;
|
border-bottom: 3px solid transparent;
|
||||||
|
border-left: 6px solid $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&:before {
|
||||||
|
background: $blue2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
border-left-color: $gray5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes sharpFadeIn {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
90% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue