mirror of https://github.com/apache/druid.git
Web console: Multi-stage query support (#12919)
* MSQ web console * fix typo in comments * remove useless conditional * wrap SQL_DATA_TYPES * fixes sus regex * rewrite regex * remove problematic regex * fix UTs * convert PARTITIONED / CLUSTERED BY to ORDER BY for preview * fix log * updated to use shuffle * Web console: Use Ace.Completion directly (#1405) * Use Ace.Completion directly * Another Ace.Completion * better comment * fix column ordering in e2e test * add nested data example also Co-authored-by: John Gozde <john.gozde@imply.io>
This commit is contained in:
parent
02914c17b9
commit
04ee7abeff
|
@ -5317,7 +5317,7 @@ license_category: binary
|
|||
module: web-console
|
||||
license_name: MIT License
|
||||
copyright: Matt Zabriskie
|
||||
version: 0.21.4
|
||||
version: 0.26.1
|
||||
license_file_path: licenses/bin/axios.MIT
|
||||
|
||||
---
|
||||
|
@ -5666,7 +5666,7 @@ license_category: binary
|
|||
module: web-console
|
||||
license_name: Apache License version 2.0
|
||||
copyright: Imply Data
|
||||
version: 0.14.10
|
||||
version: 0.14.24
|
||||
|
||||
---
|
||||
|
||||
|
@ -6536,4 +6536,14 @@ license_name: ISC License
|
|||
copyright: Eemeli Aro
|
||||
version: 1.10.2
|
||||
license_file_path: licenses/bin/yaml.ISC
|
||||
|
||||
---
|
||||
|
||||
name: "zustand"
|
||||
license_category: binary
|
||||
module: web-console
|
||||
license_name: MIT License
|
||||
copyright: Paul Henschel
|
||||
version: 3.7.2
|
||||
license_file_path: licenses/bin/zustand.MIT
|
||||
# Web console modules end
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Paul Henschel
|
||||
|
||||
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.
|
|
@ -141,7 +141,7 @@ export class DatasourcesOverview {
|
|||
}
|
||||
|
||||
private async clickMoreButton(options: any): Promise<void> {
|
||||
await this.page.click('//button[span[@icon="more"]]', options);
|
||||
await this.page.click('.more-button button', options);
|
||||
await this.waitForPopupMenu();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,10 +30,10 @@ enum TaskColumn {
|
|||
GROUP_ID,
|
||||
TYPE,
|
||||
DATASOURCE,
|
||||
LOCATION,
|
||||
CREATED_TIME,
|
||||
STATUS,
|
||||
CREATED_TIME,
|
||||
DURATION,
|
||||
LOCATION,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,7 +34,7 @@ export class DataLoader {
|
|||
|
||||
constructor(props: DataLoaderProps) {
|
||||
Object.assign(this, props);
|
||||
this.baseUrl = props.unifiedConsoleUrl + '#load-data';
|
||||
this.baseUrl = props.unifiedConsoleUrl + '#data-loader';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import * as playwright from 'playwright-chromium';
|
||||
|
||||
import { clickButton, clickText, setInput } from '../../util/playwright';
|
||||
import { clickButton, clickText } from '../../util/playwright';
|
||||
import { extractTable } from '../../util/table';
|
||||
|
||||
/**
|
||||
|
@ -37,8 +37,9 @@ export class QueryOverview {
|
|||
await this.page.goto(this.baseUrl);
|
||||
await this.page.reload({ waitUntil: 'networkidle' });
|
||||
|
||||
const input = await this.page.$('div.query-input textarea');
|
||||
await setInput(input!, query);
|
||||
const input = await this.page.waitForSelector('div.query-input textarea');
|
||||
await input.fill(query);
|
||||
|
||||
await clickButton(this.page, 'Run');
|
||||
await this.page.waitForSelector('div.query-info');
|
||||
|
||||
|
@ -49,10 +50,8 @@ export class QueryOverview {
|
|||
await this.page.goto(this.baseUrl);
|
||||
await this.page.reload({ waitUntil: 'networkidle' });
|
||||
|
||||
await this.page.waitForSelector('div.query-input textarea');
|
||||
const input = await this.page.$('div.query-input textarea');
|
||||
|
||||
await setInput(input!, query);
|
||||
const input = await this.page.waitForSelector('div.query-input textarea');
|
||||
await input.fill(query);
|
||||
|
||||
await Promise.all([
|
||||
this.page.waitForRequest(
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 * as playwright from 'playwright-chromium';
|
||||
|
||||
import { clickButton } from '../../util/playwright';
|
||||
import { extractTable } from '../../util/table';
|
||||
|
||||
/**
|
||||
* Represents the workbench tab.
|
||||
*/
|
||||
export class WorkbenchOverview {
|
||||
private readonly page: playwright.Page;
|
||||
private readonly baseUrl: string;
|
||||
|
||||
constructor(page: playwright.Page, unifiedConsoleUrl: string) {
|
||||
this.page = page;
|
||||
this.baseUrl = unifiedConsoleUrl + '#workbench';
|
||||
}
|
||||
|
||||
async runQuery(query: string): Promise<string[][]> {
|
||||
await this.page.goto(this.baseUrl);
|
||||
await this.page.reload({ waitUntil: 'networkidle' });
|
||||
|
||||
const input = await this.page.waitForSelector('div.flexible-query-input textarea');
|
||||
await input.fill(query);
|
||||
await clickButton(this.page, 'Run');
|
||||
await this.page.waitForSelector('div.result-table-pane', { timeout: 120000 });
|
||||
|
||||
return await extractTable(this.page, 'div.result-table-pane div.rt-tr-group', 'div.rt-td');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 * as playwright from 'playwright-chromium';
|
||||
|
||||
import { WorkbenchOverview } from './component/workbench/overview';
|
||||
import { saveScreenshotIfError } from './util/debug';
|
||||
import { DRUID_EXAMPLES_QUICKSTART_TUTORIAL_DIR, UNIFIED_CONSOLE_URL } from './util/druid';
|
||||
import { createBrowser, createPage } from './util/playwright';
|
||||
import { waitTillWebConsoleReady } from './util/setup';
|
||||
|
||||
jest.setTimeout(5 * 60 * 1000);
|
||||
|
||||
describe('Multi-stage query', () => {
|
||||
let browser: playwright.Browser;
|
||||
let page: playwright.Page;
|
||||
|
||||
beforeAll(async () => {
|
||||
await waitTillWebConsoleReady();
|
||||
browser = await createBrowser();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
page = await createPage(browser);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it('runs a query that reads external data', async () => {
|
||||
await saveScreenshotIfError('multi-stage-query', page, async () => {
|
||||
const workbench = new WorkbenchOverview(page, UNIFIED_CONSOLE_URL);
|
||||
const results = await workbench.runQuery(`WITH ext AS (SELECT *
|
||||
FROM TABLE(
|
||||
EXTERN(
|
||||
'{"type":"local","filter":"wikiticker-2015-09-12-sampled.json.gz","baseDir":${JSON.stringify(
|
||||
DRUID_EXAMPLES_QUICKSTART_TUTORIAL_DIR,
|
||||
)}}',
|
||||
'{"type":"json"}',
|
||||
'[{"name":"channel","type":"string"}]'
|
||||
)
|
||||
))
|
||||
SELECT
|
||||
channel,
|
||||
CAST(COUNT(*) AS VARCHAR) AS "CountString"
|
||||
FROM ext
|
||||
GROUP BY 1
|
||||
ORDER BY COUNT(*) DESC
|
||||
LIMIT 10`);
|
||||
expect(results).toBeDefined();
|
||||
expect(results.length).toBe(10);
|
||||
expect(results[0]).toStrictEqual(['#en.wikipedia', '11549']);
|
||||
expect(results[1]).toStrictEqual(['#vi.wikipedia', '9747']);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -54,6 +54,12 @@ exports.SQL_KEYWORDS = [
|
|||
'ROWS',
|
||||
'ONLY',
|
||||
'VALUES',
|
||||
'PARTITIONED BY',
|
||||
'CLUSTERED BY',
|
||||
'TIME',
|
||||
'INSERT INTO',
|
||||
'REPLACE INTO',
|
||||
'OVERWRITE',
|
||||
];
|
||||
|
||||
exports.SQL_EXPRESSION_PARTS = [
|
||||
|
|
|
@ -16,5 +16,5 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const SQL_DATA_TYPES: [name: string, runtime: string, description: string][];
|
||||
export const SQL_DATA_TYPES: Record<string, [runtime: string, description: string][]>;
|
||||
export const SQL_FUNCTIONS: Record<string, [args: string, description: string][]>;
|
||||
|
|
|
@ -5262,16 +5262,6 @@
|
|||
"integrity": "sha512-wBlsw+8n21e6eTd4yVv8YD/E3xq0O6nNnJIquutAsFGE7EyMKz7W6RNT6BRu1SmdgmlCZ9tb0X+j+D6HGr8pZw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/yauzl": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
|
||||
"integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.11.0.tgz",
|
||||
|
@ -6465,11 +6455,18 @@
|
|||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
||||
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
"follow-redirects": "^1.14.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"follow-redirects": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
|
||||
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-jest": {
|
||||
|
@ -7003,12 +7000,6 @@
|
|||
"node-int64": "^0.4.0"
|
||||
}
|
||||
},
|
||||
"buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
|
||||
"dev": true
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
|
@ -8469,11 +8460,11 @@
|
|||
}
|
||||
},
|
||||
"druid-query-toolkit": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.14.10.tgz",
|
||||
"integrity": "sha512-Y720YxnT3EmqtE/x1QkrkEiomn5TdVArxI3+gdLRH8FYMRedpSPe2nkQVNYma9b7Lww/rzk4Q+a8mNWQ1YH9oQ==",
|
||||
"version": "0.14.24",
|
||||
"resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.14.24.tgz",
|
||||
"integrity": "sha512-NBV9prXllZiiYLCfD/k5UmJZg7EU7aqsQPIfTYiYgl9XY4QheY4IO8c5mD7lW8qPpS2qEr4mM9CXjGPPZTQrmw==",
|
||||
"requires": {
|
||||
"tslib": "^2.2.0"
|
||||
"tslib": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"duplexer": {
|
||||
|
@ -10121,44 +10112,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"extract-zip": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
||||
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/yauzl": "^2.9.1",
|
||||
"debug": "^4.1.1",
|
||||
"get-stream": "^5.1.0",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
|
||||
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pump": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"extsprintf": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||
|
@ -10290,15 +10243,6 @@
|
|||
"bser": "2.1.1"
|
||||
}
|
||||
},
|
||||
"fd-slicer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||
"integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"file-entry-cache": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||
|
@ -14986,12 +14930,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"jpeg-js": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.3.tgz",
|
||||
"integrity": "sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==",
|
||||
"dev": true
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz",
|
||||
|
@ -17004,12 +16942,6 @@
|
|||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
|
||||
},
|
||||
"pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
|
||||
"dev": true
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
|
@ -17095,67 +17027,19 @@
|
|||
}
|
||||
},
|
||||
"playwright-chromium": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-chromium/-/playwright-chromium-1.18.1.tgz",
|
||||
"integrity": "sha512-DHAOdzZhhu4pMe9yg2zL49JSGXLHTO+DL76duukoy807o+ccu1tEbqyUId46ogLYk7rOjblVK6o7YG/vyVCasQ==",
|
||||
"version": "1.25.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-chromium/-/playwright-chromium-1.25.0.tgz",
|
||||
"integrity": "sha512-FH9ho3noAWVStCJx4XW78+D8QW0A99WDp53DDkYeVdEpJqCmAIKHCSE6dl5XtaDKrZPYC1ZG5hGXQh1K5H/p+g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"playwright-core": "=1.18.1"
|
||||
"playwright-core": "1.25.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.18.1.tgz",
|
||||
"integrity": "sha512-NALGl8R1GHzGLlhUApmpmfh6M1rrrPcDTygWvhTbprxwGB9qd/j9DRwyn4HTQcUB6o0/VOpo46fH9ez3+D/Rog==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^8.2.0",
|
||||
"debug": "^4.1.1",
|
||||
"extract-zip": "^2.0.1",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"jpeg-js": "^0.4.2",
|
||||
"mime": "^2.4.6",
|
||||
"pngjs": "^5.0.0",
|
||||
"progress": "^2.0.3",
|
||||
"proper-lockfile": "^4.1.1",
|
||||
"proxy-from-env": "^1.1.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"socks-proxy-agent": "^6.1.0",
|
||||
"stack-utils": "^2.0.3",
|
||||
"ws": "^7.4.6",
|
||||
"yauzl": "^2.10.0",
|
||||
"yazl": "^2.5.1"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
"version": "1.25.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.25.0.tgz",
|
||||
"integrity": "sha512-kZ3Jwaf3wlu0GgU0nB8UMQ+mXFTqBIFz9h1svTlNduNKjnbPXFxw7mJanLVjqxHJRn62uBfmgBj93YHidk2N5Q==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -17165,12 +17049,6 @@
|
|||
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
|
||||
"dev": true
|
||||
},
|
||||
"pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"dev": true
|
||||
},
|
||||
"popper.js": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
||||
|
@ -18791,12 +18669,6 @@
|
|||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"dev": true
|
||||
},
|
||||
"progress": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||
"dev": true
|
||||
},
|
||||
"prompts": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||
|
@ -18828,25 +18700,6 @@
|
|||
"reflect.ownkeys": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"proper-lockfile": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
|
||||
"integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"retry": "^0.12.0",
|
||||
"signal-exit": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"graceful-fs": {
|
||||
"version": "4.2.9",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
|
||||
"integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
|
||||
|
@ -18857,12 +18710,6 @@
|
|||
"ipaddr.js": "1.9.1"
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"prr": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||
|
@ -19500,6 +19347,160 @@
|
|||
"is-finite": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"replace": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/replace/-/replace-1.2.1.tgz",
|
||||
"integrity": "sha512-KZCBe/tPanwBlbjSMQby4l+zjSiFi3CLEP/6VLClnRYgJ46DZ5u9tmA6ceWeFS8coaUnU4ZdGNb/puUGMHNSRg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "2.4.2",
|
||||
"minimatch": "3.0.4",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-locate": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-limit": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"version": "2.88.2",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||
|
@ -20086,12 +20087,6 @@
|
|||
"integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=",
|
||||
"dev": true
|
||||
},
|
||||
"smart-buffer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
||||
"dev": true
|
||||
},
|
||||
"snake-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
|
||||
|
@ -20279,44 +20274,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"socks": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz",
|
||||
"integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ip": "^1.1.5",
|
||||
"smart-buffer": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"socks-proxy-agent": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz",
|
||||
"integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"agent-base": "^6.0.2",
|
||||
"debug": "^4.3.1",
|
||||
"socks": "^2.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"source-list-map": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
||||
|
@ -23529,25 +23486,6 @@
|
|||
"decamelize": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"yauzl": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||
"integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-crc32": "~0.2.3",
|
||||
"fd-slicer": "~1.1.0"
|
||||
}
|
||||
},
|
||||
"yazl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz",
|
||||
"integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-crc32": "~0.2.3"
|
||||
}
|
||||
},
|
||||
"yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
|
@ -23560,6 +23498,11 @@
|
|||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"zustand": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz",
|
||||
"integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA=="
|
||||
},
|
||||
"zwitch": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz",
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
"@blueprintjs/icons": "^4.1.1",
|
||||
"@blueprintjs/popover2": "^1.0.3",
|
||||
"ace-builds": "^1.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^0.26.1",
|
||||
"classnames": "^2.2.6",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"core-js": "^3.10.1",
|
||||
|
@ -79,7 +79,7 @@
|
|||
"d3-axis": "^1.0.12",
|
||||
"d3-scale": "^3.2.0",
|
||||
"d3-selection": "^1.4.0",
|
||||
"druid-query-toolkit": "^0.14.10",
|
||||
"druid-query-toolkit": "^0.14.24",
|
||||
"file-saver": "^2.0.2",
|
||||
"follow-redirects": "^1.14.7",
|
||||
"fontsource-open-sans": "^3.0.9",
|
||||
|
@ -100,7 +100,8 @@
|
|||
"react-splitter-layout": "^4.0.0",
|
||||
"react-table": "~6.10.3",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"tslib": "^2.3.1"
|
||||
"tslib": "^2.3.1",
|
||||
"zustand": "^3.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@awesome-code-style/eslint-config": "^4.0.0",
|
||||
|
@ -154,11 +155,12 @@
|
|||
"jest": "^27.5.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"node-sass": "^5.0.0",
|
||||
"playwright-chromium": "^1.18.1",
|
||||
"playwright-chromium": "^1.24.1",
|
||||
"postcss": "^8.3.0",
|
||||
"postcss-loader": "^5.3.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"prettier": "^2.5.1",
|
||||
"replace": "^1.2.1",
|
||||
"sass-loader": "^11.0.1",
|
||||
"snarkdown": "^2.0.0",
|
||||
"style-loader": "^2.0.0",
|
||||
|
|
|
@ -70,7 +70,7 @@ const readDoc = async () => {
|
|||
const lines = data.split('\n');
|
||||
|
||||
const functionDocs = {};
|
||||
const dataTypeDocs = [];
|
||||
const dataTypeDocs = {};
|
||||
for (let line of lines) {
|
||||
const functionMatch = line.match(/^\|\s*`(\w+)\(([^|]*)\)`\s*\|([^|]+)\|(?:([^|]+)\|)?$/);
|
||||
if (functionMatch) {
|
||||
|
@ -84,11 +84,7 @@ const readDoc = async () => {
|
|||
|
||||
const dataTypeMatch = line.match(/^\|([A-Z]+)\|([A-Z]+)\|([^|]*)\|([^|]*)\|$/);
|
||||
if (dataTypeMatch) {
|
||||
dataTypeDocs.push([
|
||||
dataTypeMatch[1],
|
||||
dataTypeMatch[2],
|
||||
convertMarkdownToHtml(dataTypeMatch[4]),
|
||||
]);
|
||||
dataTypeDocs[dataTypeMatch[1]] = [dataTypeMatch[2], convertMarkdownToHtml(dataTypeMatch[4])];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ function _build_distribution() {
|
|||
&& tar xzf "apache-druid-$(_get_druid_version)-bin.tar.gz" \
|
||||
&& cd apache-druid-$(_get_druid_version) \
|
||||
&& bin/run-java -classpath "lib/*" org.apache.druid.cli.Main tools pull-deps -c org.apache.druid.extensions:druid-testing-tools \
|
||||
&& echo -e "\n\ndruid.extensions.loadList=[\"druid-hdfs-storage\", \"druid-kafka-indexing-service\", \"druid-datasketches\", \"druid-testing-tools\"]" >> conf/druid/single-server/micro-quickstart/_common/common.runtime.properties \
|
||||
&& echo -e "\n\ndruid.extensions.loadList=[\"druid-hdfs-storage\", \"druid-kafka-indexing-service\", \"druid-datasketches\", \"druid-multi-stage-query\", \"druid-testing-tools\"]" >> conf/druid/single-server/micro-quickstart/_common/common.runtime.properties \
|
||||
&& echo -e "\n\ndruid.server.http.allowedHttpMethods=[\"HEAD\"]" >> conf/druid/single-server/micro-quickstart/_common/common.runtime.properties \
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env node
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const replace = require('replace');
|
||||
|
||||
if (process.argv.length !== 5) {
|
||||
console.log('Usage: mv <src-location> <old-component-name> <new-component-name>');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
const location = process.argv[2];
|
||||
const oldName = process.argv[3];
|
||||
const newName = process.argv[4];
|
||||
|
||||
if (!/^([a-z0-9-])+$/.test(oldName)) {
|
||||
console.log('must be a hyphen case old name');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
if (!/^([a-z0-9-])+$/.test(newName)) {
|
||||
console.log('must be a hyphen case new name');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
const oldPath = './src/' + location + '/' + oldName + '/';
|
||||
const newPath = './src/' + location + '/' + newName + '/';
|
||||
|
||||
const camelOldName = oldName.replace(/(^|-)[a-z]/g, s => s.replace('-', '').toUpperCase());
|
||||
const camelNewName = newName.replace(/(^|-)[a-z]/g, s => s.replace('-', '').toUpperCase());
|
||||
|
||||
console.log('Making path:', newPath);
|
||||
|
||||
fs.moveSync(oldPath, newPath);
|
||||
fs.renameSync(newPath + oldName + '.tsx', newPath + newName + '.tsx');
|
||||
try {
|
||||
fs.renameSync(newPath + oldName + '.scss', newPath + newName + '.scss');
|
||||
} catch {}
|
||||
try {
|
||||
fs.renameSync(newPath + oldName + '.spec.tsx', newPath + newName + '.spec.tsx');
|
||||
} catch {}
|
||||
|
||||
const replacePath = './src/';
|
||||
|
||||
replace({
|
||||
regex: oldName,
|
||||
replacement: newName,
|
||||
paths: [replacePath],
|
||||
recursive: true,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
replace({
|
||||
regex: camelOldName,
|
||||
replacement: camelNewName,
|
||||
paths: [replacePath],
|
||||
recursive: true,
|
||||
silent: true,
|
||||
});
|
|
@ -48,9 +48,7 @@ ace.define(
|
|||
).join('|');
|
||||
|
||||
// Stuff like: 'int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp'
|
||||
var dataTypes = druidFunctions.SQL_DATA_TYPES.map(function (f) {
|
||||
return f[0];
|
||||
}).join('|');
|
||||
var dataTypes = Object.keys(druidFunctions.SQL_DATA_TYPES).join('|');
|
||||
|
||||
var keywordMapper = this.createKeywordMapper(
|
||||
{
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
} from '../react-table';
|
||||
import { countBy } from '../utils';
|
||||
|
||||
const NoData = React.memo(function NoData(props) {
|
||||
const NoData = React.memo(function NoData(props: { children?: React.ReactNode }) {
|
||||
const { children } = props;
|
||||
if (!children) return null;
|
||||
return <div className="rt-noData">{children}</div>;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
top: -50000px; // Send it into the stratosphere (get it out of the parent container to prevent the browser from adding '...')
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.real-text {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ClickToCopy matches snapshot 1`] = `
|
||||
<a
|
||||
class="click-to-copy"
|
||||
title="Click to copy:
|
||||
Hello world"
|
||||
>
|
||||
Hello world
|
||||
</a>
|
||||
`;
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { ClickToCopy } from './click-to-copy';
|
||||
|
||||
describe('ClickToCopy', () => {
|
||||
it('matches snapshot', () => {
|
||||
const arrayInput = <ClickToCopy text="Hello world" />;
|
||||
|
||||
const { container } = render(arrayInput);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 { Intent } from '@blueprintjs/core';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import React from 'react';
|
||||
|
||||
import { AppToaster } from '../../singletons';
|
||||
|
||||
export interface ClickToCopyProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export const ClickToCopy = React.memo(function ClickToCopy(props: ClickToCopyProps) {
|
||||
const { text } = props;
|
||||
|
||||
return (
|
||||
<a
|
||||
className="click-to-copy"
|
||||
title={`Click to copy:\n${text}`}
|
||||
onClick={() => {
|
||||
copy(text, { format: 'text/plain' });
|
||||
AppToaster.show({
|
||||
message: `'${text}' copied to clipboard`,
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
});
|
|
@ -1,668 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DatasourceColumnsTable matches snapshot on error 1`] = `
|
||||
<div
|
||||
className="datasource-columns-table"
|
||||
>
|
||||
<div
|
||||
className="main-area"
|
||||
>
|
||||
<ReactTable
|
||||
AggregatedComponent={[Function]}
|
||||
ExpanderComponent={[Function]}
|
||||
FilterComponent={[Function]}
|
||||
LoadingComponent={[Function]}
|
||||
NoDataComponent={[Function]}
|
||||
PadRowComponent={[Function]}
|
||||
PaginationComponent={[Function]}
|
||||
PivotValueComponent={[Function]}
|
||||
ResizerComponent={[Function]}
|
||||
TableComponent={[Function]}
|
||||
TbodyComponent={[Function]}
|
||||
TdComponent={[Function]}
|
||||
TfootComponent={[Function]}
|
||||
ThComponent={[Function]}
|
||||
TheadComponent={[Function]}
|
||||
TrComponent={[Function]}
|
||||
TrGroupComponent={[Function]}
|
||||
aggregatedKey="_aggregated"
|
||||
className=""
|
||||
collapseOnDataChange={true}
|
||||
collapseOnPageChange={true}
|
||||
collapseOnSortingChange={true}
|
||||
column={
|
||||
Object {
|
||||
"Aggregated": undefined,
|
||||
"Cell": undefined,
|
||||
"Expander": undefined,
|
||||
"Filter": undefined,
|
||||
"Footer": undefined,
|
||||
"Header": undefined,
|
||||
"Pivot": undefined,
|
||||
"PivotValue": undefined,
|
||||
"Placeholder": undefined,
|
||||
"aggregate": undefined,
|
||||
"className": "",
|
||||
"filterAll": false,
|
||||
"filterMethod": undefined,
|
||||
"filterable": undefined,
|
||||
"footerClassName": "",
|
||||
"footerStyle": Object {},
|
||||
"getFooterProps": [Function],
|
||||
"getHeaderProps": [Function],
|
||||
"getProps": [Function],
|
||||
"headerClassName": "",
|
||||
"headerStyle": Object {},
|
||||
"minResizeWidth": 11,
|
||||
"minWidth": 100,
|
||||
"resizable": undefined,
|
||||
"show": true,
|
||||
"sortMethod": undefined,
|
||||
"sortable": undefined,
|
||||
"style": Object {},
|
||||
}
|
||||
}
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"Header": "Column name",
|
||||
"accessor": "COLUMN_NAME",
|
||||
"className": "padded",
|
||||
"width": 300,
|
||||
},
|
||||
Object {
|
||||
"Header": "Data type",
|
||||
"accessor": "DATA_TYPE",
|
||||
"className": "padded",
|
||||
"width": 200,
|
||||
},
|
||||
]
|
||||
}
|
||||
data={Array []}
|
||||
defaultExpanded={Object {}}
|
||||
defaultFilterMethod={[Function]}
|
||||
defaultFiltered={Array []}
|
||||
defaultPage={0}
|
||||
defaultPageSize={25}
|
||||
defaultResized={Array []}
|
||||
defaultSortDesc={false}
|
||||
defaultSortMethod={[Function]}
|
||||
defaultSorted={Array []}
|
||||
expanderDefaults={
|
||||
Object {
|
||||
"filterable": false,
|
||||
"resizable": false,
|
||||
"sortable": false,
|
||||
"width": 35,
|
||||
}
|
||||
}
|
||||
filterable={true}
|
||||
freezeWhenExpanded={false}
|
||||
getLoadingProps={[Function]}
|
||||
getNoDataProps={[Function]}
|
||||
getPaginationProps={[Function]}
|
||||
getProps={[Function]}
|
||||
getResizerProps={[Function]}
|
||||
getTableProps={[Function]}
|
||||
getTbodyProps={[Function]}
|
||||
getTdProps={[Function]}
|
||||
getTfootProps={[Function]}
|
||||
getTfootTdProps={[Function]}
|
||||
getTfootTrProps={[Function]}
|
||||
getTheadFilterProps={[Function]}
|
||||
getTheadFilterThProps={[Function]}
|
||||
getTheadFilterTrProps={[Function]}
|
||||
getTheadGroupProps={[Function]}
|
||||
getTheadGroupThProps={[Function]}
|
||||
getTheadGroupTrProps={[Function]}
|
||||
getTheadProps={[Function]}
|
||||
getTheadThProps={[Function]}
|
||||
getTheadTrProps={[Function]}
|
||||
getTrGroupProps={[Function]}
|
||||
getTrProps={[Function]}
|
||||
groupedByPivotKey="_groupedByPivot"
|
||||
indexKey="_index"
|
||||
loading={false}
|
||||
loadingText="Loading..."
|
||||
multiSort={true}
|
||||
nestingLevelKey="_nestingLevel"
|
||||
nextText="Next"
|
||||
noDataText="test error"
|
||||
ofText="of"
|
||||
onFetchData={[Function]}
|
||||
originalKey="_original"
|
||||
pageJumpText="jump to page"
|
||||
pageSizeOptions={
|
||||
Array [
|
||||
25,
|
||||
50,
|
||||
100,
|
||||
]
|
||||
}
|
||||
pageText="Page"
|
||||
pivotDefaults={Object {}}
|
||||
pivotIDKey="_pivotID"
|
||||
pivotValKey="_pivotVal"
|
||||
previousText="Previous"
|
||||
resizable={true}
|
||||
resolveData={[Function]}
|
||||
rowsSelectorText="rows per page"
|
||||
rowsText="rows"
|
||||
showPageJump={true}
|
||||
showPageSizeOptions={true}
|
||||
showPagination={false}
|
||||
showPaginationBottom={true}
|
||||
showPaginationTop={false}
|
||||
sortable={true}
|
||||
style={Object {}}
|
||||
subRowsKey="_subRows"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DatasourceColumnsTable matches snapshot on init 1`] = `
|
||||
<div
|
||||
className="datasource-columns-table"
|
||||
>
|
||||
<div
|
||||
className="main-area"
|
||||
>
|
||||
<ReactTable
|
||||
AggregatedComponent={[Function]}
|
||||
ExpanderComponent={[Function]}
|
||||
FilterComponent={[Function]}
|
||||
LoadingComponent={[Function]}
|
||||
NoDataComponent={[Function]}
|
||||
PadRowComponent={[Function]}
|
||||
PaginationComponent={[Function]}
|
||||
PivotValueComponent={[Function]}
|
||||
ResizerComponent={[Function]}
|
||||
TableComponent={[Function]}
|
||||
TbodyComponent={[Function]}
|
||||
TdComponent={[Function]}
|
||||
TfootComponent={[Function]}
|
||||
ThComponent={[Function]}
|
||||
TheadComponent={[Function]}
|
||||
TrComponent={[Function]}
|
||||
TrGroupComponent={[Function]}
|
||||
aggregatedKey="_aggregated"
|
||||
className=""
|
||||
collapseOnDataChange={true}
|
||||
collapseOnPageChange={true}
|
||||
collapseOnSortingChange={true}
|
||||
column={
|
||||
Object {
|
||||
"Aggregated": undefined,
|
||||
"Cell": undefined,
|
||||
"Expander": undefined,
|
||||
"Filter": undefined,
|
||||
"Footer": undefined,
|
||||
"Header": undefined,
|
||||
"Pivot": undefined,
|
||||
"PivotValue": undefined,
|
||||
"Placeholder": undefined,
|
||||
"aggregate": undefined,
|
||||
"className": "",
|
||||
"filterAll": false,
|
||||
"filterMethod": undefined,
|
||||
"filterable": undefined,
|
||||
"footerClassName": "",
|
||||
"footerStyle": Object {},
|
||||
"getFooterProps": [Function],
|
||||
"getHeaderProps": [Function],
|
||||
"getProps": [Function],
|
||||
"headerClassName": "",
|
||||
"headerStyle": Object {},
|
||||
"minResizeWidth": 11,
|
||||
"minWidth": 100,
|
||||
"resizable": undefined,
|
||||
"show": true,
|
||||
"sortMethod": undefined,
|
||||
"sortable": undefined,
|
||||
"style": Object {},
|
||||
}
|
||||
}
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"Header": "Column name",
|
||||
"accessor": "COLUMN_NAME",
|
||||
"className": "padded",
|
||||
"width": 300,
|
||||
},
|
||||
Object {
|
||||
"Header": "Data type",
|
||||
"accessor": "DATA_TYPE",
|
||||
"className": "padded",
|
||||
"width": 200,
|
||||
},
|
||||
]
|
||||
}
|
||||
data={Array []}
|
||||
defaultExpanded={Object {}}
|
||||
defaultFilterMethod={[Function]}
|
||||
defaultFiltered={Array []}
|
||||
defaultPage={0}
|
||||
defaultPageSize={25}
|
||||
defaultResized={Array []}
|
||||
defaultSortDesc={false}
|
||||
defaultSortMethod={[Function]}
|
||||
defaultSorted={Array []}
|
||||
expanderDefaults={
|
||||
Object {
|
||||
"filterable": false,
|
||||
"resizable": false,
|
||||
"sortable": false,
|
||||
"width": 35,
|
||||
}
|
||||
}
|
||||
filterable={true}
|
||||
freezeWhenExpanded={false}
|
||||
getLoadingProps={[Function]}
|
||||
getNoDataProps={[Function]}
|
||||
getPaginationProps={[Function]}
|
||||
getProps={[Function]}
|
||||
getResizerProps={[Function]}
|
||||
getTableProps={[Function]}
|
||||
getTbodyProps={[Function]}
|
||||
getTdProps={[Function]}
|
||||
getTfootProps={[Function]}
|
||||
getTfootTdProps={[Function]}
|
||||
getTfootTrProps={[Function]}
|
||||
getTheadFilterProps={[Function]}
|
||||
getTheadFilterThProps={[Function]}
|
||||
getTheadFilterTrProps={[Function]}
|
||||
getTheadGroupProps={[Function]}
|
||||
getTheadGroupThProps={[Function]}
|
||||
getTheadGroupTrProps={[Function]}
|
||||
getTheadProps={[Function]}
|
||||
getTheadThProps={[Function]}
|
||||
getTheadTrProps={[Function]}
|
||||
getTrGroupProps={[Function]}
|
||||
getTrProps={[Function]}
|
||||
groupedByPivotKey="_groupedByPivot"
|
||||
indexKey="_index"
|
||||
loading={false}
|
||||
loadingText="Loading..."
|
||||
multiSort={true}
|
||||
nestingLevelKey="_nestingLevel"
|
||||
nextText="Next"
|
||||
noDataText="No column data found"
|
||||
ofText="of"
|
||||
onFetchData={[Function]}
|
||||
originalKey="_original"
|
||||
pageJumpText="jump to page"
|
||||
pageSizeOptions={
|
||||
Array [
|
||||
25,
|
||||
50,
|
||||
100,
|
||||
]
|
||||
}
|
||||
pageText="Page"
|
||||
pivotDefaults={Object {}}
|
||||
pivotIDKey="_pivotID"
|
||||
pivotValKey="_pivotVal"
|
||||
previousText="Previous"
|
||||
resizable={true}
|
||||
resolveData={[Function]}
|
||||
rowsSelectorText="rows per page"
|
||||
rowsText="rows"
|
||||
showPageJump={true}
|
||||
showPageSizeOptions={true}
|
||||
showPagination={false}
|
||||
showPaginationBottom={true}
|
||||
showPaginationTop={false}
|
||||
sortable={true}
|
||||
style={Object {}}
|
||||
subRowsKey="_subRows"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DatasourceColumnsTable matches snapshot on loading 1`] = `
|
||||
<div
|
||||
className="datasource-columns-table"
|
||||
>
|
||||
<div
|
||||
className="main-area"
|
||||
>
|
||||
<Memo(Loader) />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DatasourceColumnsTable matches snapshot on no data 1`] = `
|
||||
<div
|
||||
className="datasource-columns-table"
|
||||
>
|
||||
<div
|
||||
className="main-area"
|
||||
>
|
||||
<ReactTable
|
||||
AggregatedComponent={[Function]}
|
||||
ExpanderComponent={[Function]}
|
||||
FilterComponent={[Function]}
|
||||
LoadingComponent={[Function]}
|
||||
NoDataComponent={[Function]}
|
||||
PadRowComponent={[Function]}
|
||||
PaginationComponent={[Function]}
|
||||
PivotValueComponent={[Function]}
|
||||
ResizerComponent={[Function]}
|
||||
TableComponent={[Function]}
|
||||
TbodyComponent={[Function]}
|
||||
TdComponent={[Function]}
|
||||
TfootComponent={[Function]}
|
||||
ThComponent={[Function]}
|
||||
TheadComponent={[Function]}
|
||||
TrComponent={[Function]}
|
||||
TrGroupComponent={[Function]}
|
||||
aggregatedKey="_aggregated"
|
||||
className=""
|
||||
collapseOnDataChange={true}
|
||||
collapseOnPageChange={true}
|
||||
collapseOnSortingChange={true}
|
||||
column={
|
||||
Object {
|
||||
"Aggregated": undefined,
|
||||
"Cell": undefined,
|
||||
"Expander": undefined,
|
||||
"Filter": undefined,
|
||||
"Footer": undefined,
|
||||
"Header": undefined,
|
||||
"Pivot": undefined,
|
||||
"PivotValue": undefined,
|
||||
"Placeholder": undefined,
|
||||
"aggregate": undefined,
|
||||
"className": "",
|
||||
"filterAll": false,
|
||||
"filterMethod": undefined,
|
||||
"filterable": undefined,
|
||||
"footerClassName": "",
|
||||
"footerStyle": Object {},
|
||||
"getFooterProps": [Function],
|
||||
"getHeaderProps": [Function],
|
||||
"getProps": [Function],
|
||||
"headerClassName": "",
|
||||
"headerStyle": Object {},
|
||||
"minResizeWidth": 11,
|
||||
"minWidth": 100,
|
||||
"resizable": undefined,
|
||||
"show": true,
|
||||
"sortMethod": undefined,
|
||||
"sortable": undefined,
|
||||
"style": Object {},
|
||||
}
|
||||
}
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"Header": "Column name",
|
||||
"accessor": "COLUMN_NAME",
|
||||
"className": "padded",
|
||||
"width": 300,
|
||||
},
|
||||
Object {
|
||||
"Header": "Data type",
|
||||
"accessor": "DATA_TYPE",
|
||||
"className": "padded",
|
||||
"width": 200,
|
||||
},
|
||||
]
|
||||
}
|
||||
data={Array []}
|
||||
defaultExpanded={Object {}}
|
||||
defaultFilterMethod={[Function]}
|
||||
defaultFiltered={Array []}
|
||||
defaultPage={0}
|
||||
defaultPageSize={25}
|
||||
defaultResized={Array []}
|
||||
defaultSortDesc={false}
|
||||
defaultSortMethod={[Function]}
|
||||
defaultSorted={Array []}
|
||||
expanderDefaults={
|
||||
Object {
|
||||
"filterable": false,
|
||||
"resizable": false,
|
||||
"sortable": false,
|
||||
"width": 35,
|
||||
}
|
||||
}
|
||||
filterable={true}
|
||||
freezeWhenExpanded={false}
|
||||
getLoadingProps={[Function]}
|
||||
getNoDataProps={[Function]}
|
||||
getPaginationProps={[Function]}
|
||||
getProps={[Function]}
|
||||
getResizerProps={[Function]}
|
||||
getTableProps={[Function]}
|
||||
getTbodyProps={[Function]}
|
||||
getTdProps={[Function]}
|
||||
getTfootProps={[Function]}
|
||||
getTfootTdProps={[Function]}
|
||||
getTfootTrProps={[Function]}
|
||||
getTheadFilterProps={[Function]}
|
||||
getTheadFilterThProps={[Function]}
|
||||
getTheadFilterTrProps={[Function]}
|
||||
getTheadGroupProps={[Function]}
|
||||
getTheadGroupThProps={[Function]}
|
||||
getTheadGroupTrProps={[Function]}
|
||||
getTheadProps={[Function]}
|
||||
getTheadThProps={[Function]}
|
||||
getTheadTrProps={[Function]}
|
||||
getTrGroupProps={[Function]}
|
||||
getTrProps={[Function]}
|
||||
groupedByPivotKey="_groupedByPivot"
|
||||
indexKey="_index"
|
||||
loading={false}
|
||||
loadingText="Loading..."
|
||||
multiSort={true}
|
||||
nestingLevelKey="_nestingLevel"
|
||||
nextText="Next"
|
||||
noDataText="No column data found"
|
||||
ofText="of"
|
||||
onFetchData={[Function]}
|
||||
originalKey="_original"
|
||||
pageJumpText="jump to page"
|
||||
pageSizeOptions={
|
||||
Array [
|
||||
25,
|
||||
50,
|
||||
100,
|
||||
]
|
||||
}
|
||||
pageText="Page"
|
||||
pivotDefaults={Object {}}
|
||||
pivotIDKey="_pivotID"
|
||||
pivotValKey="_pivotVal"
|
||||
previousText="Previous"
|
||||
resizable={true}
|
||||
resolveData={[Function]}
|
||||
rowsSelectorText="rows per page"
|
||||
rowsText="rows"
|
||||
showPageJump={true}
|
||||
showPageSizeOptions={true}
|
||||
showPagination={false}
|
||||
showPaginationBottom={true}
|
||||
showPaginationTop={false}
|
||||
sortable={true}
|
||||
style={Object {}}
|
||||
subRowsKey="_subRows"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DatasourceColumnsTable matches snapshot on some data 1`] = `
|
||||
<div
|
||||
className="datasource-columns-table"
|
||||
>
|
||||
<div
|
||||
className="main-area"
|
||||
>
|
||||
<ReactTable
|
||||
AggregatedComponent={[Function]}
|
||||
ExpanderComponent={[Function]}
|
||||
FilterComponent={[Function]}
|
||||
LoadingComponent={[Function]}
|
||||
NoDataComponent={[Function]}
|
||||
PadRowComponent={[Function]}
|
||||
PaginationComponent={[Function]}
|
||||
PivotValueComponent={[Function]}
|
||||
ResizerComponent={[Function]}
|
||||
TableComponent={[Function]}
|
||||
TbodyComponent={[Function]}
|
||||
TdComponent={[Function]}
|
||||
TfootComponent={[Function]}
|
||||
ThComponent={[Function]}
|
||||
TheadComponent={[Function]}
|
||||
TrComponent={[Function]}
|
||||
TrGroupComponent={[Function]}
|
||||
aggregatedKey="_aggregated"
|
||||
className=""
|
||||
collapseOnDataChange={true}
|
||||
collapseOnPageChange={true}
|
||||
collapseOnSortingChange={true}
|
||||
column={
|
||||
Object {
|
||||
"Aggregated": undefined,
|
||||
"Cell": undefined,
|
||||
"Expander": undefined,
|
||||
"Filter": undefined,
|
||||
"Footer": undefined,
|
||||
"Header": undefined,
|
||||
"Pivot": undefined,
|
||||
"PivotValue": undefined,
|
||||
"Placeholder": undefined,
|
||||
"aggregate": undefined,
|
||||
"className": "",
|
||||
"filterAll": false,
|
||||
"filterMethod": undefined,
|
||||
"filterable": undefined,
|
||||
"footerClassName": "",
|
||||
"footerStyle": Object {},
|
||||
"getFooterProps": [Function],
|
||||
"getHeaderProps": [Function],
|
||||
"getProps": [Function],
|
||||
"headerClassName": "",
|
||||
"headerStyle": Object {},
|
||||
"minResizeWidth": 11,
|
||||
"minWidth": 100,
|
||||
"resizable": undefined,
|
||||
"show": true,
|
||||
"sortMethod": undefined,
|
||||
"sortable": undefined,
|
||||
"style": Object {},
|
||||
}
|
||||
}
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"Header": "Column name",
|
||||
"accessor": "COLUMN_NAME",
|
||||
"className": "padded",
|
||||
"width": 300,
|
||||
},
|
||||
Object {
|
||||
"Header": "Data type",
|
||||
"accessor": "DATA_TYPE",
|
||||
"className": "padded",
|
||||
"width": 200,
|
||||
},
|
||||
]
|
||||
}
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"COLUMN_NAME": "channel",
|
||||
"DATA_TYPE": "VARCHAR",
|
||||
},
|
||||
Object {
|
||||
"COLUMN_NAME": "page",
|
||||
"DATA_TYPE": "VARCHAR",
|
||||
},
|
||||
]
|
||||
}
|
||||
defaultExpanded={Object {}}
|
||||
defaultFilterMethod={[Function]}
|
||||
defaultFiltered={Array []}
|
||||
defaultPage={0}
|
||||
defaultPageSize={25}
|
||||
defaultResized={Array []}
|
||||
defaultSortDesc={false}
|
||||
defaultSortMethod={[Function]}
|
||||
defaultSorted={Array []}
|
||||
expanderDefaults={
|
||||
Object {
|
||||
"filterable": false,
|
||||
"resizable": false,
|
||||
"sortable": false,
|
||||
"width": 35,
|
||||
}
|
||||
}
|
||||
filterable={true}
|
||||
freezeWhenExpanded={false}
|
||||
getLoadingProps={[Function]}
|
||||
getNoDataProps={[Function]}
|
||||
getPaginationProps={[Function]}
|
||||
getProps={[Function]}
|
||||
getResizerProps={[Function]}
|
||||
getTableProps={[Function]}
|
||||
getTbodyProps={[Function]}
|
||||
getTdProps={[Function]}
|
||||
getTfootProps={[Function]}
|
||||
getTfootTdProps={[Function]}
|
||||
getTfootTrProps={[Function]}
|
||||
getTheadFilterProps={[Function]}
|
||||
getTheadFilterThProps={[Function]}
|
||||
getTheadFilterTrProps={[Function]}
|
||||
getTheadGroupProps={[Function]}
|
||||
getTheadGroupThProps={[Function]}
|
||||
getTheadGroupTrProps={[Function]}
|
||||
getTheadProps={[Function]}
|
||||
getTheadThProps={[Function]}
|
||||
getTheadTrProps={[Function]}
|
||||
getTrGroupProps={[Function]}
|
||||
getTrProps={[Function]}
|
||||
groupedByPivotKey="_groupedByPivot"
|
||||
indexKey="_index"
|
||||
loading={false}
|
||||
loadingText="Loading..."
|
||||
multiSort={true}
|
||||
nestingLevelKey="_nestingLevel"
|
||||
nextText="Next"
|
||||
noDataText="No column data found"
|
||||
ofText="of"
|
||||
onFetchData={[Function]}
|
||||
originalKey="_original"
|
||||
pageJumpText="jump to page"
|
||||
pageSizeOptions={
|
||||
Array [
|
||||
25,
|
||||
50,
|
||||
100,
|
||||
]
|
||||
}
|
||||
pageText="Page"
|
||||
pivotDefaults={Object {}}
|
||||
pivotIDKey="_pivotID"
|
||||
pivotValKey="_pivotVal"
|
||||
previousText="Previous"
|
||||
resizable={true}
|
||||
resolveData={[Function]}
|
||||
rowsSelectorText="rows per page"
|
||||
rowsText="rows"
|
||||
showPageJump={true}
|
||||
showPageSizeOptions={true}
|
||||
showPagination={false}
|
||||
showPaginationBottom={true}
|
||||
showPaginationTop={false}
|
||||
sortable={true}
|
||||
style={Object {}}
|
||||
subRowsKey="_subRows"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
$side-bar-width: 120px;
|
||||
|
||||
.fancy-tab-pane {
|
||||
.side-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: $side-bar-width;
|
||||
height: 100%;
|
||||
border-right: 1px solid #1f2832;
|
||||
|
||||
.tab-button {
|
||||
width: 100%;
|
||||
height: 10vh;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 0;
|
||||
|
||||
&.active {
|
||||
background-color: #2c74a8;
|
||||
}
|
||||
|
||||
.#{$bp-ns}-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.#{$bp-ns}-button-text {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-section {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: $side-bar-width;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
padding: 10px 20px 15px 20px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 { Button, Icon, IconName, Intent } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
import { filterMap } from '../../utils';
|
||||
|
||||
import './fancy-tab-pane.scss';
|
||||
|
||||
export interface FancyTabButton {
|
||||
id: string;
|
||||
icon: IconName;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface FancyTabPaneProps {
|
||||
className?: string;
|
||||
tabs: (FancyTabButton | false | undefined)[];
|
||||
activeTab: string;
|
||||
onActivateTab(newActiveTab: string): void;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const FancyTabPane = React.memo(function FancyTabPane(props: FancyTabPaneProps) {
|
||||
const { className, tabs, activeTab, onActivateTab, children } = props;
|
||||
|
||||
return (
|
||||
<div className={classNames('fancy-tab-pane', className)}>
|
||||
<div className="side-bar">
|
||||
{filterMap(tabs, d => {
|
||||
if (!d) return;
|
||||
return (
|
||||
<Button
|
||||
className="tab-button"
|
||||
icon={<Icon icon={d.icon} size={20} />}
|
||||
key={d.id}
|
||||
text={d.label}
|
||||
intent={activeTab === d.id ? Intent.PRIMARY : Intent.NONE}
|
||||
minimal={activeTab !== d.id}
|
||||
onClick={() => onActivateTab(d.id)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="main-section">{children}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
|
@ -37,7 +37,7 @@ export const FormGroupWithInfo = React.memo(function FormGroupWithInfo(
|
|||
|
||||
const popover = (
|
||||
<Popover2 className="info-popover" content={info} position="left-bottom">
|
||||
<Icon icon={IconNames.INFO_SIGN} iconSize={14} />
|
||||
<Icon icon={IconNames.INFO_SIGN} size={14} />
|
||||
</Popover2>
|
||||
);
|
||||
|
||||
|
|
|
@ -15,24 +15,91 @@ exports[`HeaderBar matches snapshot 1`] = `
|
|||
<Blueprint4.NavbarDivider />
|
||||
<Blueprint4.AnchorButton
|
||||
active={true}
|
||||
className="header-entry"
|
||||
disabled={false}
|
||||
href="#load-data"
|
||||
icon="cloud-upload"
|
||||
intent="none"
|
||||
href="#workbench"
|
||||
icon="application"
|
||||
minimal={true}
|
||||
text="Load data"
|
||||
onClick={[Function]}
|
||||
text="Query"
|
||||
/>
|
||||
<Blueprint4.Popover2
|
||||
boundary="clippingParents"
|
||||
captureDismiss={false}
|
||||
content={
|
||||
<Blueprint4.Menu>
|
||||
<Blueprint4.MenuItem
|
||||
active={false}
|
||||
disabled={false}
|
||||
href="#streaming-data-loader"
|
||||
icon="feed"
|
||||
multiline={false}
|
||||
popoverProps={Object {}}
|
||||
selected={false}
|
||||
shouldDismissPopover={true}
|
||||
text="Streaming"
|
||||
/>
|
||||
<Blueprint4.MenuItem
|
||||
active={false}
|
||||
disabled={false}
|
||||
href="#sql-data-loader"
|
||||
icon="clean"
|
||||
labelElement={
|
||||
<Blueprint4.Tag
|
||||
minimal={true}
|
||||
>
|
||||
multi-stage-query
|
||||
</Blueprint4.Tag>
|
||||
}
|
||||
multiline={false}
|
||||
popoverProps={Object {}}
|
||||
selected={false}
|
||||
shouldDismissPopover={true}
|
||||
text="Batch - SQL"
|
||||
/>
|
||||
<Blueprint4.MenuItem
|
||||
active={false}
|
||||
disabled={false}
|
||||
href="#classic-batch-data-loader"
|
||||
icon="list"
|
||||
multiline={false}
|
||||
popoverProps={Object {}}
|
||||
selected={false}
|
||||
shouldDismissPopover={true}
|
||||
text="Batch - classic"
|
||||
/>
|
||||
</Blueprint4.Menu>
|
||||
}
|
||||
defaultIsOpen={false}
|
||||
disabled={false}
|
||||
fill={false}
|
||||
hasBackdrop={false}
|
||||
hoverCloseDelay={300}
|
||||
hoverOpenDelay={150}
|
||||
inheritDarkTheme={true}
|
||||
interactionKind="click"
|
||||
minimal={false}
|
||||
openOnTargetFocus={true}
|
||||
position="bottom-left"
|
||||
positioningStrategy="absolute"
|
||||
shouldReturnFocusOnClose={false}
|
||||
targetTagName="span"
|
||||
transitionDuration={300}
|
||||
usePortal={true}
|
||||
>
|
||||
<Blueprint4.Button
|
||||
active={false}
|
||||
className="header-entry"
|
||||
disabled={false}
|
||||
icon="cloud-upload"
|
||||
minimal={true}
|
||||
text="Load data"
|
||||
/>
|
||||
</Blueprint4.Popover2>
|
||||
<Blueprint4.NavbarDivider />
|
||||
<Blueprint4.AnchorButton
|
||||
active={false}
|
||||
disabled={false}
|
||||
href="#ingestion"
|
||||
icon="gantt-chart"
|
||||
minimal={true}
|
||||
text="Ingestion"
|
||||
/>
|
||||
<Blueprint4.AnchorButton
|
||||
active={false}
|
||||
className="header-entry"
|
||||
disabled={false}
|
||||
href="#datasources"
|
||||
icon="multi-select"
|
||||
|
@ -41,6 +108,16 @@ exports[`HeaderBar matches snapshot 1`] = `
|
|||
/>
|
||||
<Blueprint4.AnchorButton
|
||||
active={false}
|
||||
className="header-entry"
|
||||
disabled={false}
|
||||
href="#ingestion"
|
||||
icon="gantt-chart"
|
||||
minimal={true}
|
||||
text="Ingestion"
|
||||
/>
|
||||
<Blueprint4.AnchorButton
|
||||
active={false}
|
||||
className="header-entry"
|
||||
disabled={false}
|
||||
href="#segments"
|
||||
icon="stacked-chart"
|
||||
|
@ -49,21 +126,55 @@ exports[`HeaderBar matches snapshot 1`] = `
|
|||
/>
|
||||
<Blueprint4.AnchorButton
|
||||
active={false}
|
||||
className="header-entry"
|
||||
disabled={false}
|
||||
href="#services"
|
||||
icon="database"
|
||||
minimal={true}
|
||||
text="Services"
|
||||
/>
|
||||
<Blueprint4.NavbarDivider />
|
||||
<Blueprint4.AnchorButton
|
||||
active={false}
|
||||
<Blueprint4.Popover2
|
||||
boundary="clippingParents"
|
||||
captureDismiss={false}
|
||||
content={
|
||||
<Blueprint4.Menu>
|
||||
<Blueprint4.MenuItem
|
||||
active={false}
|
||||
disabled={false}
|
||||
href="#lookups"
|
||||
icon="properties"
|
||||
multiline={false}
|
||||
popoverProps={Object {}}
|
||||
selected={false}
|
||||
shouldDismissPopover={true}
|
||||
text="Lookups"
|
||||
/>
|
||||
</Blueprint4.Menu>
|
||||
}
|
||||
defaultIsOpen={false}
|
||||
disabled={false}
|
||||
href="#query"
|
||||
icon="application"
|
||||
minimal={true}
|
||||
text="Query"
|
||||
/>
|
||||
fill={false}
|
||||
hasBackdrop={false}
|
||||
hoverCloseDelay={300}
|
||||
hoverOpenDelay={150}
|
||||
inheritDarkTheme={true}
|
||||
interactionKind="click"
|
||||
minimal={false}
|
||||
openOnTargetFocus={true}
|
||||
position="bottom-left"
|
||||
positioningStrategy="absolute"
|
||||
shouldReturnFocusOnClose={false}
|
||||
targetTagName="span"
|
||||
transitionDuration={300}
|
||||
usePortal={true}
|
||||
>
|
||||
<Blueprint4.Button
|
||||
active={false}
|
||||
className="header-entry"
|
||||
icon="more"
|
||||
minimal={true}
|
||||
/>
|
||||
</Blueprint4.Popover2>
|
||||
</Blueprint4.NavbarGroup>
|
||||
<Blueprint4.NavbarGroup
|
||||
align="right"
|
||||
|
@ -72,6 +183,7 @@ exports[`HeaderBar matches snapshot 1`] = `
|
|||
capabilities={
|
||||
Capabilities {
|
||||
"coordinator": true,
|
||||
"multiStageQuery": true,
|
||||
"overlord": true,
|
||||
"queryType": "nativeAndSql",
|
||||
}
|
||||
|
@ -116,17 +228,6 @@ exports[`HeaderBar matches snapshot 1`] = `
|
|||
shouldDismissPopover={true}
|
||||
text="Overlord dynamic config"
|
||||
/>
|
||||
<Blueprint4.MenuItem
|
||||
active={false}
|
||||
disabled={false}
|
||||
href="#lookups"
|
||||
icon="properties"
|
||||
multiline={false}
|
||||
popoverProps={Object {}}
|
||||
selected={false}
|
||||
shouldDismissPopover={true}
|
||||
text="Lookups"
|
||||
/>
|
||||
<Blueprint4.MenuDivider />
|
||||
<Blueprint4.MenuItem
|
||||
active={false}
|
||||
|
@ -201,6 +302,7 @@ exports[`HeaderBar matches snapshot 1`] = `
|
|||
usePortal={true}
|
||||
>
|
||||
<Blueprint4.Button
|
||||
className="header-entry"
|
||||
icon="cog"
|
||||
minimal={true}
|
||||
/>
|
||||
|
@ -224,7 +326,7 @@ exports[`HeaderBar matches snapshot 1`] = `
|
|||
<Blueprint4.MenuItem
|
||||
active={false}
|
||||
disabled={false}
|
||||
href="https://druid.apache.org/docs/0.23.0"
|
||||
href="https://druid.apache.org/docs/latest"
|
||||
icon="th"
|
||||
multiline={false}
|
||||
popoverProps={Object {}}
|
||||
|
@ -289,6 +391,7 @@ exports[`HeaderBar matches snapshot 1`] = `
|
|||
usePortal={true}
|
||||
>
|
||||
<Blueprint4.Button
|
||||
className="header-entry"
|
||||
icon="help"
|
||||
minimal={true}
|
||||
/>
|
||||
|
|
|
@ -63,10 +63,7 @@
|
|||
margin: 0 11px;
|
||||
}
|
||||
|
||||
.#{$bp-ns}-button.#{$bp-ns}-minimal {
|
||||
border-radius: 20px;
|
||||
margin: 0 1px;
|
||||
|
||||
.header-entry {
|
||||
.#{$bp-ns}-icon {
|
||||
svg {
|
||||
fill: $blue3;
|
||||
|
@ -76,6 +73,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.#{$bp-ns}-button.#{$bp-ns}-minimal {
|
||||
border-radius: 20px;
|
||||
margin: 0 1px;
|
||||
|
||||
.#{$bp-ns}-dark & {
|
||||
&:hover {
|
||||
background: rgba($dark-gray5, 0.5);
|
||||
|
|
|
@ -26,7 +26,7 @@ import { HeaderBar } from './header-bar';
|
|||
describe('HeaderBar', () => {
|
||||
it('matches snapshot', () => {
|
||||
const headerBar = shallow(
|
||||
<HeaderBar active="load-data" capabilities={Capabilities.FULL} onUnrestrict={() => {}} />,
|
||||
<HeaderBar active="workbench" capabilities={Capabilities.FULL} onUnrestrict={() => {}} />,
|
||||
);
|
||||
expect(headerBar).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
NavbarDivider,
|
||||
NavbarGroup,
|
||||
Position,
|
||||
Tag,
|
||||
} from '@blueprintjs/core';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { Popover2 } from '@blueprintjs/popover2';
|
||||
|
@ -46,6 +47,7 @@ import {
|
|||
LocalStorageKeys,
|
||||
localStorageRemove,
|
||||
localStorageSetJson,
|
||||
oneOf,
|
||||
} from '../../utils';
|
||||
import { ExternalLink } from '../external-link/external-link';
|
||||
import { PopoverText } from '../popover-text/popover-text';
|
||||
|
@ -56,12 +58,16 @@ const capabilitiesOverride = localStorageGetJson(LocalStorageKeys.CAPABILITIES_O
|
|||
|
||||
export type HeaderActiveTab =
|
||||
| null
|
||||
| 'load-data'
|
||||
| 'data-loader'
|
||||
| 'streaming-data-loader'
|
||||
| 'classic-batch-data-loader'
|
||||
| 'ingestion'
|
||||
| 'datasources'
|
||||
| 'segments'
|
||||
| 'services'
|
||||
| 'query'
|
||||
| 'workbench'
|
||||
| 'sql-data-loader'
|
||||
| 'lookups';
|
||||
|
||||
const DruidLogo = React.memo(function DruidLogo() {
|
||||
|
@ -233,7 +239,52 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
|
|||
const [coordinatorDynamicConfigDialogOpen, setCoordinatorDynamicConfigDialogOpen] =
|
||||
useState(false);
|
||||
const [overlordDynamicConfigDialogOpen, setOverlordDynamicConfigDialogOpen] = useState(false);
|
||||
const loadDataPrimary = false;
|
||||
|
||||
const showSplitDataLoaderMenu = capabilities.hasMultiStageQuery();
|
||||
|
||||
const loadDataViewsMenuActive = oneOf(
|
||||
active,
|
||||
'data-loader',
|
||||
'streaming-data-loader',
|
||||
'classic-batch-data-loader',
|
||||
'sql-data-loader',
|
||||
);
|
||||
const loadDataViewsMenu = (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={IconNames.FEED}
|
||||
text="Streaming"
|
||||
href="#streaming-data-loader"
|
||||
selected={active === 'streaming-data-loader'}
|
||||
/>
|
||||
<MenuItem
|
||||
icon={IconNames.CLEAN}
|
||||
text="Batch - SQL"
|
||||
href="#sql-data-loader"
|
||||
labelElement={<Tag minimal>multi-stage-query</Tag>}
|
||||
selected={active === 'sql-data-loader'}
|
||||
/>
|
||||
<MenuItem
|
||||
icon={IconNames.LIST}
|
||||
text="Batch - classic"
|
||||
href="#classic-batch-data-loader"
|
||||
selected={active === 'classic-batch-data-loader'}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const moreViewsMenuActive = oneOf(active, 'lookups');
|
||||
const moreViewsMenu = (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={IconNames.PROPERTIES}
|
||||
text="Lookups"
|
||||
href="#lookups"
|
||||
disabled={!capabilities.hasCoordinatorAccess()}
|
||||
selected={active === 'lookups'}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const helpMenu = (
|
||||
<Menu>
|
||||
|
@ -290,13 +341,7 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
|
|||
onClick={() => setOverlordDynamicConfigDialogOpen(true)}
|
||||
disabled={!capabilities.hasOverlordAccess()}
|
||||
/>
|
||||
<MenuItem
|
||||
icon={IconNames.PROPERTIES}
|
||||
active={active === 'lookups'}
|
||||
text="Lookups"
|
||||
href="#lookups"
|
||||
disabled={!capabilities.hasCoordinatorAccess()}
|
||||
/>
|
||||
|
||||
<MenuDivider />
|
||||
<MenuItem icon={IconNames.COG} text="Console options">
|
||||
{capabilitiesOverride ? (
|
||||
|
@ -339,28 +384,50 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
|
|||
<a href="#">
|
||||
<DruidLogo />
|
||||
</a>
|
||||
|
||||
<NavbarDivider />
|
||||
<AnchorButton
|
||||
icon={IconNames.CLOUD_UPLOAD}
|
||||
text="Load data"
|
||||
active={active === 'load-data'}
|
||||
href="#load-data"
|
||||
minimal={!loadDataPrimary}
|
||||
intent={loadDataPrimary ? Intent.PRIMARY : Intent.NONE}
|
||||
disabled={!capabilities.hasEverything()}
|
||||
/>
|
||||
|
||||
<NavbarDivider />
|
||||
<AnchorButton
|
||||
className="header-entry"
|
||||
minimal
|
||||
active={active === 'ingestion'}
|
||||
icon={IconNames.GANTT_CHART}
|
||||
text="Ingestion"
|
||||
href="#ingestion"
|
||||
disabled={!capabilities.hasSqlOrOverlordAccess()}
|
||||
active={oneOf(active, 'workbench', 'query')}
|
||||
icon={IconNames.APPLICATION}
|
||||
text="Query"
|
||||
href="#workbench"
|
||||
disabled={!capabilities.hasQuerying()}
|
||||
onClick={e => {
|
||||
if (!e.altKey) return;
|
||||
e.preventDefault();
|
||||
location.hash = '#query';
|
||||
}}
|
||||
/>
|
||||
{showSplitDataLoaderMenu ? (
|
||||
<Popover2
|
||||
content={loadDataViewsMenu}
|
||||
disabled={!capabilities.hasEverything()}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
>
|
||||
<Button
|
||||
className="header-entry"
|
||||
icon={IconNames.CLOUD_UPLOAD}
|
||||
text="Load data"
|
||||
minimal
|
||||
active={loadDataViewsMenuActive}
|
||||
disabled={!capabilities.hasEverything()}
|
||||
/>
|
||||
</Popover2>
|
||||
) : (
|
||||
<AnchorButton
|
||||
className="header-entry"
|
||||
icon={IconNames.CLOUD_UPLOAD}
|
||||
text="Load data"
|
||||
href="#data-loader"
|
||||
minimal
|
||||
active={loadDataViewsMenuActive}
|
||||
disabled={!capabilities.hasEverything()}
|
||||
/>
|
||||
)}
|
||||
<NavbarDivider />
|
||||
<AnchorButton
|
||||
className="header-entry"
|
||||
minimal
|
||||
active={active === 'datasources'}
|
||||
icon={IconNames.MULTI_SELECT}
|
||||
|
@ -369,6 +436,16 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
|
|||
disabled={!capabilities.hasSqlOrCoordinatorAccess()}
|
||||
/>
|
||||
<AnchorButton
|
||||
className="header-entry"
|
||||
minimal
|
||||
active={active === 'ingestion'}
|
||||
icon={IconNames.GANTT_CHART}
|
||||
text="Ingestion"
|
||||
href="#ingestion"
|
||||
disabled={!capabilities.hasSqlOrOverlordAccess()}
|
||||
/>
|
||||
<AnchorButton
|
||||
className="header-entry"
|
||||
minimal
|
||||
active={active === 'segments'}
|
||||
icon={IconNames.STACKED_CHART}
|
||||
|
@ -377,6 +454,7 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
|
|||
disabled={!capabilities.hasSqlOrCoordinatorAccess()}
|
||||
/>
|
||||
<AnchorButton
|
||||
className="header-entry"
|
||||
minimal
|
||||
active={active === 'services'}
|
||||
icon={IconNames.DATABASE}
|
||||
|
@ -384,24 +462,22 @@ export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
|
|||
href="#services"
|
||||
disabled={!capabilities.hasSqlOrCoordinatorAccess()}
|
||||
/>
|
||||
|
||||
<NavbarDivider />
|
||||
<AnchorButton
|
||||
minimal
|
||||
active={active === 'query'}
|
||||
icon={IconNames.APPLICATION}
|
||||
text="Query"
|
||||
href="#query"
|
||||
disabled={!capabilities.hasQuerying()}
|
||||
/>
|
||||
<Popover2 content={moreViewsMenu} position={Position.BOTTOM_LEFT}>
|
||||
<Button
|
||||
className="header-entry"
|
||||
minimal
|
||||
icon={IconNames.MORE}
|
||||
active={moreViewsMenuActive}
|
||||
/>
|
||||
</Popover2>
|
||||
</NavbarGroup>
|
||||
<NavbarGroup align={Alignment.RIGHT}>
|
||||
<RestrictedMode capabilities={capabilities} onUnrestrict={onUnrestrict} />
|
||||
<Popover2 content={configMenu} position={Position.BOTTOM_RIGHT}>
|
||||
<Button minimal icon={IconNames.COG} />
|
||||
<Button className="header-entry" minimal icon={IconNames.COG} />
|
||||
</Popover2>
|
||||
<Popover2 content={helpMenu} position={Position.BOTTOM_RIGHT}>
|
||||
<Button minimal icon={IconNames.HELP} />
|
||||
<Button className="header-entry" minimal icon={IconNames.HELP} />
|
||||
</Popover2>
|
||||
</NavbarGroup>
|
||||
{aboutDialogOpen && <AboutDialog onClose={() => setAboutDialogOpen(false)} />}
|
||||
|
|
|
@ -23,27 +23,39 @@ export * from './auto-form/auto-form';
|
|||
export * from './braced-text/braced-text';
|
||||
export * from './center-message/center-message';
|
||||
export * from './clearable-input/clearable-input';
|
||||
export * from './click-to-copy/click-to-copy';
|
||||
export * from './deferred/deferred';
|
||||
export * from './deferred/deferred';
|
||||
export * from './external-link/external-link';
|
||||
export * from './fancy-tab-pane/fancy-tab-pane';
|
||||
export * from './form-group-with-info/form-group-with-info';
|
||||
export * from './form-json-selector/form-json-selector';
|
||||
export * from './formatted-input/formatted-input';
|
||||
export * from './header-bar/header-bar';
|
||||
export * from './highlight-text/highlight-text';
|
||||
export * from './json-collapse/json-collapse';
|
||||
export * from './json-input/json-input';
|
||||
export * from './learn-more/learn-more';
|
||||
export * from './loader/loader';
|
||||
export * from './menu-checkbox/menu-checkbox';
|
||||
export * from './menu-tristate/menu-tristate';
|
||||
export * from './more-button/more-button';
|
||||
export * from './plural-pair-if-needed/plural-pair-if-needed';
|
||||
export * from './popover-text/popover-text';
|
||||
export * from './query-error-pane/query-error-pane';
|
||||
export * from './record-table-pane/record-table-pane';
|
||||
export * from './refresh-button/refresh-button';
|
||||
export * from './rule-editor/rule-editor';
|
||||
export * from './segment-timeline/segment-timeline';
|
||||
export * from './show-json/show-json';
|
||||
export * from './show-log/show-log';
|
||||
export * from './show-value/show-value';
|
||||
export * from './suggestion-menu/suggestion-menu';
|
||||
export * from './table-cell/table-cell';
|
||||
export * from './table-cell-unparseable/table-cell-unparseable';
|
||||
export * from './table-clickable-cell/table-clickable-cell';
|
||||
export * from './table-column-selector/table-column-selector';
|
||||
export * from './table-filterable-cell/table-filterable-cell';
|
||||
export * from './timed-button/timed-button';
|
||||
export * from './view-control-bar/view-control-bar';
|
||||
export * from './warning-checklist/warning-checklist';
|
||||
|
|
|
@ -60,7 +60,7 @@ export const IntervalInput = React.memo(function IntervalInput(props: IntervalIn
|
|||
</div>
|
||||
}
|
||||
onChange={(e: any) => {
|
||||
const value = e.target.value.replace(/[^\-0-9T:/]/g, '').substring(0, 39);
|
||||
const value = e.target.value.replace(/[^\-\dT:/]/g, '').substring(0, 39);
|
||||
onValueChange(value);
|
||||
}}
|
||||
intent={intent}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { ExternalLink } from '../../../components';
|
||||
import { ExternalLink } from '../external-link/external-link';
|
||||
|
||||
export interface LearnMoreProps {
|
||||
href: string;
|
|
@ -17,10 +17,11 @@
|
|||
*/
|
||||
|
||||
import { MenuItem, MenuItemProps } from '@blueprintjs/core';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
import { checkedCircleIcon } from '../../utils';
|
||||
|
||||
export interface MenuCheckboxProps extends Omit<MenuItemProps, 'icon' | 'onClick'> {
|
||||
checked: boolean;
|
||||
onChange: () => void;
|
||||
|
@ -32,7 +33,7 @@ export function MenuCheckbox(props: MenuCheckboxProps) {
|
|||
return (
|
||||
<MenuItem
|
||||
className={classNames('menu-checkbox', className)}
|
||||
icon={checked ? IconNames.TICK_CIRCLE : IconNames.CIRCLE}
|
||||
icon={checkedCircleIcon(checked)}
|
||||
onClick={onChange}
|
||||
shouldDismissPopover={shouldDismissPopover ?? false}
|
||||
{...rest}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MenuTristate matches snapshot false 1`] = `
|
||||
<li
|
||||
class="bp4-submenu"
|
||||
>
|
||||
<span
|
||||
class="bp4-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
aria-haspopup="true"
|
||||
class="bp4-popover-target"
|
||||
>
|
||||
<a
|
||||
class="bp4-menu-item menu-tristate"
|
||||
label="false"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="bp4-fill bp4-text-overflow-ellipsis"
|
||||
>
|
||||
hello
|
||||
</div>
|
||||
<span
|
||||
class="bp4-menu-item-label"
|
||||
>
|
||||
false
|
||||
</span>
|
||||
<span
|
||||
class="bp4-icon bp4-icon-caret-right bp4-submenu-icon"
|
||||
icon="caret-right"
|
||||
>
|
||||
<svg
|
||||
data-icon="caret-right"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
>
|
||||
<desc>
|
||||
Open sub menu
|
||||
</desc>
|
||||
<path
|
||||
d="M11 8c0-.15-.07-.28-.17-.37l-4-3.5A.495.495 0 006 4.5v7a.495.495 0 00.83.37l4-3.5c.1-.09.17-.22.17-.37z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
`;
|
||||
|
||||
exports[`MenuTristate matches snapshot undefined 1`] = `
|
||||
<li
|
||||
class="bp4-submenu"
|
||||
>
|
||||
<span
|
||||
class="bp4-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
aria-haspopup="true"
|
||||
class="bp4-popover-target"
|
||||
>
|
||||
<a
|
||||
class="bp4-menu-item menu-tristate"
|
||||
label="auto (true)"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="bp4-fill bp4-text-overflow-ellipsis"
|
||||
>
|
||||
hello
|
||||
</div>
|
||||
<span
|
||||
class="bp4-menu-item-label"
|
||||
>
|
||||
auto (true)
|
||||
</span>
|
||||
<span
|
||||
class="bp4-icon bp4-icon-caret-right bp4-submenu-icon"
|
||||
icon="caret-right"
|
||||
>
|
||||
<svg
|
||||
data-icon="caret-right"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
>
|
||||
<desc>
|
||||
Open sub menu
|
||||
</desc>
|
||||
<path
|
||||
d="M11 8c0-.15-.07-.28-.17-.37l-4-3.5A.495.495 0 006 4.5v7a.495.495 0 00.83.37l4-3.5c.1-.09.17-.22.17-.37z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
`;
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { MenuTristate } from './menu-tristate';
|
||||
|
||||
describe('MenuTristate', () => {
|
||||
it('matches snapshot undefined', () => {
|
||||
const menuCheckbox = (
|
||||
<MenuTristate
|
||||
text="hello"
|
||||
value={undefined}
|
||||
undefinedEffectiveValue
|
||||
onValueChange={() => {}}
|
||||
/>
|
||||
);
|
||||
const { container } = render(menuCheckbox);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('matches snapshot false', () => {
|
||||
const menuCheckbox = (
|
||||
<MenuTristate
|
||||
text="hello"
|
||||
value={false}
|
||||
undefinedEffectiveValue={false}
|
||||
onValueChange={() => {}}
|
||||
/>
|
||||
);
|
||||
const { container } = render(menuCheckbox);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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 { MenuItem, MenuItemProps } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
import { tickIcon } from '../../utils';
|
||||
|
||||
export interface MenuTristateProps extends Omit<MenuItemProps, 'label'> {
|
||||
value: boolean | undefined;
|
||||
onValueChange(value: boolean | undefined): void;
|
||||
undefinedLabel?: string;
|
||||
undefinedEffectiveValue?: boolean;
|
||||
}
|
||||
|
||||
export function MenuTristate(props: MenuTristateProps) {
|
||||
const {
|
||||
value,
|
||||
onValueChange,
|
||||
undefinedLabel,
|
||||
undefinedEffectiveValue,
|
||||
className,
|
||||
shouldDismissPopover,
|
||||
...rest
|
||||
} = props;
|
||||
const shouldDismiss = shouldDismissPopover ?? false;
|
||||
|
||||
function formatValue(value: boolean | undefined): string {
|
||||
return String(value ?? undefinedLabel ?? 'auto');
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
className={classNames('menu-tristate', className)}
|
||||
shouldDismissPopover={shouldDismiss}
|
||||
label={
|
||||
formatValue(value) +
|
||||
(typeof value === 'undefined' && typeof undefinedEffectiveValue === 'boolean'
|
||||
? ` (${undefinedEffectiveValue})`
|
||||
: '')
|
||||
}
|
||||
{...rest}
|
||||
>
|
||||
{[undefined, true, false].map((v, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
icon={tickIcon(value === v)}
|
||||
text={formatValue(v)}
|
||||
onClick={() => onValueChange(v)}
|
||||
shouldDismissPopover={shouldDismiss}
|
||||
/>
|
||||
))}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
|
@ -9,6 +9,7 @@ exports[`MoreButton matches snapshot (empty) 1`] = `
|
|||
class="bp4-button bp4-disabled"
|
||||
disabled=""
|
||||
tabindex="-1"
|
||||
title="More actions"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
|
@ -40,6 +41,7 @@ exports[`MoreButton matches snapshot (full) 1`] = `
|
|||
>
|
||||
<button
|
||||
class="bp4-button"
|
||||
title="More actions"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
|
|
|
@ -55,7 +55,7 @@ export const MoreButton = React.memo(function MoreButton(props: MoreButtonProps)
|
|||
setOpenState(nextOpenState ? (e.altKey ? 'alt-open' : 'open') : undefined);
|
||||
}}
|
||||
>
|
||||
<Button icon={IconNames.MORE} disabled={!childCount} />
|
||||
<Button icon={IconNames.MORE} disabled={!childCount} title="More actions" />
|
||||
</Popover2>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`QueryError matches snapshot 1`] = `
|
||||
exports[`QueryErrorPane matches snapshot 1`] = `
|
||||
<div
|
||||
className="query-error"
|
||||
className="query-error-pane"
|
||||
>
|
||||
something went wrong in line 7, column 8.
|
||||
</div>
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 '../../blueprint-overrides/common/colors';
|
||||
@import '../../variables';
|
||||
|
||||
.query-error-pane {
|
||||
padding: 20px 22px;
|
||||
|
||||
.#{$bp-ns}-dark & {
|
||||
background: $dark-gray3;
|
||||
}
|
||||
}
|
|
@ -19,12 +19,12 @@
|
|||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { QueryError } from './query-error';
|
||||
import { QueryErrorPane } from './query-error-pane';
|
||||
|
||||
describe('QueryError', () => {
|
||||
describe('QueryErrorPane', () => {
|
||||
it('matches snapshot', () => {
|
||||
const queryError = shallow(
|
||||
<QueryError
|
||||
<QueryErrorPane
|
||||
error={new Error('something went wrong in line 7, column 8.')}
|
||||
moveCursorTo={() => {}}
|
||||
/>,
|
|
@ -18,24 +18,24 @@
|
|||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { HighlightText } from '../../../components';
|
||||
import { DruidError, RowColumn } from '../../../utils';
|
||||
import { DruidError, RowColumn } from '../../utils';
|
||||
import { HighlightText } from '../highlight-text/highlight-text';
|
||||
|
||||
import './query-error.scss';
|
||||
import './query-error-pane.scss';
|
||||
|
||||
export interface QueryErrorProps {
|
||||
export interface QueryErrorPaneProps {
|
||||
error: DruidError;
|
||||
moveCursorTo: (rowColumn: RowColumn) => void;
|
||||
queryString?: string;
|
||||
onQueryStringChange?: (newQueryString: string, run?: boolean) => void;
|
||||
}
|
||||
|
||||
export const QueryError = React.memo(function QueryError(props: QueryErrorProps) {
|
||||
export const QueryErrorPane = React.memo(function QueryErrorPane(props: QueryErrorPaneProps) {
|
||||
const { error, moveCursorTo, queryString, onQueryStringChange } = props;
|
||||
const [showMode, setShowMore] = useState(false);
|
||||
|
||||
if (!error.errorMessage) {
|
||||
return <div className="query-error">{error.message}</div>;
|
||||
return <div className="query-error-pane">{error.message}</div>;
|
||||
}
|
||||
|
||||
const { position, suggestion } = error;
|
||||
|
@ -46,21 +46,20 @@ export const QueryError = React.memo(function QueryError(props: QueryErrorProps)
|
|||
suggestionElement = (
|
||||
<p>
|
||||
Suggestion:{' '}
|
||||
<span
|
||||
className="suggestion"
|
||||
<a
|
||||
onClick={() => {
|
||||
onQueryStringChange(newQuery, true);
|
||||
}}
|
||||
>
|
||||
{suggestion.label}
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="query-error">
|
||||
<div className="query-error-pane">
|
||||
{suggestionElement}
|
||||
{error.error && <p>{`Error: ${error.error}`}</p>}
|
||||
{error.errorMessageWithoutExpectation && (
|
||||
|
@ -70,14 +69,13 @@ export const QueryError = React.memo(function QueryError(props: QueryErrorProps)
|
|||
text={error.errorMessageWithoutExpectation}
|
||||
find={position.match}
|
||||
replace={
|
||||
<span
|
||||
className="cursor-link"
|
||||
<a
|
||||
onClick={() => {
|
||||
moveCursorTo(position);
|
||||
}}
|
||||
>
|
||||
{position.match}
|
||||
</span>
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
|
@ -86,19 +84,14 @@ export const QueryError = React.memo(function QueryError(props: QueryErrorProps)
|
|||
{error.expectation && !showMode && (
|
||||
<>
|
||||
{' '}
|
||||
<span className="more-or-less" onClick={() => setShowMore(true)}>
|
||||
More...
|
||||
</span>
|
||||
<a onClick={() => setShowMore(true)}>More...</a>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
{error.expectation && showMode && (
|
||||
<p>
|
||||
{error.expectation}{' '}
|
||||
<span className="more-or-less" onClick={() => setShowMore(false)}>
|
||||
Less...
|
||||
</span>
|
||||
{error.expectation} <a onClick={() => setShowMore(false)}>Less...</a>
|
||||
</p>
|
||||
)}
|
||||
{error.errorClass && <p>{error.errorClass}</p>}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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';
|
||||
@import '../../blueprint-overrides/common/colors';
|
||||
|
||||
.record-table-pane {
|
||||
position: relative;
|
||||
|
||||
&.more-results .-totalPages {
|
||||
// Hide the total page counter as it can be confusing due to the auto limit
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dead-end {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 45%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 350px;
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
> * {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.ReactTable {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
font-feature-settings: tnum;
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
.rt-thead.-header {
|
||||
box-shadow: 0 1px 0 0 rgba(black, 0.2); // This is a hack! this line is sometimes too weak in tables.
|
||||
|
||||
.rt-th {
|
||||
&.aggregate-header {
|
||||
background: rgba($druid-brand, 0.06);
|
||||
}
|
||||
|
||||
.asc {
|
||||
box-shadow: inset 0 3px 0 0 rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.desc {
|
||||
box-shadow: inset 0 -3px 0 0 rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.#{$bp-ns}-icon {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.output-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
> * {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.type-icon {
|
||||
margin-top: 3px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.formula {
|
||||
font-family: monospace;
|
||||
font-size: 11px;
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rt-td {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.clickable-cell {
|
||||
padding: $table-cell-v-padding $table-cell-h-padding;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.#{$bp-ns}-popover2-target {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
* 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 { Button, Icon, Menu, MenuItem } from '@blueprintjs/core';
|
||||
import { IconName, IconNames } from '@blueprintjs/icons';
|
||||
import { Popover2 } from '@blueprintjs/popover2';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Column,
|
||||
QueryResult,
|
||||
SqlExpression,
|
||||
SqlLiteral,
|
||||
SqlRef,
|
||||
trimString,
|
||||
} from 'druid-query-toolkit';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
|
||||
import { ShowValueDialog } from '../../dialogs/show-value-dialog/show-value-dialog';
|
||||
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
|
||||
import {
|
||||
columnToIcon,
|
||||
columnToWidth,
|
||||
copyAndAlert,
|
||||
filterMap,
|
||||
formatNumber,
|
||||
getNumericColumnBraces,
|
||||
Pagination,
|
||||
prettyPrintSql,
|
||||
stringifyValue,
|
||||
} from '../../utils';
|
||||
import { BracedText } from '../braced-text/braced-text';
|
||||
import { Deferred } from '../deferred/deferred';
|
||||
import { TableCell } from '../table-cell/table-cell';
|
||||
|
||||
import './record-table-pane.scss';
|
||||
|
||||
function sqlLiteralForColumnValue(column: Column, value: unknown): SqlLiteral | undefined {
|
||||
if (column.sqlType === 'TIMESTAMP') {
|
||||
const asDate = new Date(value as any);
|
||||
if (!isNaN(asDate.valueOf())) {
|
||||
return SqlLiteral.create(asDate);
|
||||
}
|
||||
}
|
||||
|
||||
return SqlLiteral.maybe(value);
|
||||
}
|
||||
|
||||
function isComparable(x: unknown): boolean {
|
||||
return x !== null && x !== '';
|
||||
}
|
||||
|
||||
export interface RecordTablePaneProps {
|
||||
queryResult: QueryResult;
|
||||
initPageSize?: number;
|
||||
addFilter?(filter: string): void;
|
||||
}
|
||||
|
||||
export const RecordTablePane = React.memo(function RecordTablePane(props: RecordTablePaneProps) {
|
||||
const { queryResult, initPageSize, addFilter } = props;
|
||||
const parsedQuery = queryResult.sqlQuery;
|
||||
const [pagination, setPagination] = useState<Pagination>({
|
||||
page: 0,
|
||||
pageSize: initPageSize || 20,
|
||||
});
|
||||
const [showValue, setShowValue] = useState<string>();
|
||||
|
||||
// Reset page to 0 if number of results changes
|
||||
useEffect(() => {
|
||||
setPagination(pagination => {
|
||||
return pagination.page ? { ...pagination, page: 0 } : pagination;
|
||||
});
|
||||
}, [queryResult.rows.length]);
|
||||
|
||||
function hasFilterOnHeader(header: string, headerIndex: number): boolean {
|
||||
if (!parsedQuery || !parsedQuery.isRealOutputColumnAtSelectIndex(headerIndex)) return false;
|
||||
|
||||
return (
|
||||
parsedQuery.getEffectiveWhereExpression().containsColumn(header) ||
|
||||
parsedQuery.getEffectiveHavingExpression().containsColumn(header)
|
||||
);
|
||||
}
|
||||
|
||||
function filterOnMenuItem(icon: IconName, clause: SqlExpression) {
|
||||
if (!parsedQuery || !addFilter) return;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
icon={icon}
|
||||
text={`Filter on: ${prettyPrintSql(clause)}`}
|
||||
onClick={() => {
|
||||
addFilter(clause.toString());
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function actionMenuItem(clause: SqlExpression) {
|
||||
if (!addFilter) return;
|
||||
const prettyLabel = prettyPrintSql(clause);
|
||||
return (
|
||||
<MenuItem
|
||||
icon={IconNames.FILTER}
|
||||
text={`Filter: ${prettyLabel}`}
|
||||
onClick={() => addFilter(clause.toString())}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function getCellMenu(column: Column, headerIndex: number, value: unknown) {
|
||||
const showFullValueMenuItem = (
|
||||
<MenuItem
|
||||
icon={IconNames.EYE_OPEN}
|
||||
text="Show full value"
|
||||
onClick={() => {
|
||||
setShowValue(stringifyValue(value));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const val = sqlLiteralForColumnValue(column, value);
|
||||
|
||||
if (parsedQuery) {
|
||||
let ex: SqlExpression | undefined;
|
||||
if (parsedQuery.hasStarInSelect()) {
|
||||
ex = SqlRef.column(column.name);
|
||||
} else {
|
||||
const selectValue = parsedQuery.getSelectExpressionForIndex(headerIndex);
|
||||
if (selectValue) {
|
||||
ex = selectValue.getUnderlyingExpression();
|
||||
}
|
||||
}
|
||||
|
||||
const jsonColumn = column.nativeType === 'COMPLEX<json>';
|
||||
return (
|
||||
<Menu>
|
||||
{ex && val && !jsonColumn && (
|
||||
<>
|
||||
{filterOnMenuItem(IconNames.FILTER, ex.equal(val))}
|
||||
{filterOnMenuItem(IconNames.FILTER, ex.unequal(val))}
|
||||
{isComparable(value) && (
|
||||
<>
|
||||
{filterOnMenuItem(IconNames.FILTER, ex.greaterThanOrEqual(val))}
|
||||
{filterOnMenuItem(IconNames.FILTER, ex.lessThanOrEqual(val))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{showFullValueMenuItem}
|
||||
</Menu>
|
||||
);
|
||||
} else {
|
||||
const ref = SqlRef.column(column.name);
|
||||
const stringValue = stringifyValue(value);
|
||||
const trimmedValue = trimString(stringValue, 50);
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={IconNames.CLIPBOARD}
|
||||
text={`Copy: ${trimmedValue}`}
|
||||
onClick={() => copyAndAlert(stringValue, `${trimmedValue} copied to clipboard`)}
|
||||
/>
|
||||
{val && (
|
||||
<>
|
||||
{actionMenuItem(ref.equal(val))}
|
||||
{actionMenuItem(ref.unequal(val))}
|
||||
</>
|
||||
)}
|
||||
{showFullValueMenuItem}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getHeaderClassName(header: string) {
|
||||
if (!parsedQuery) return;
|
||||
|
||||
const className = [];
|
||||
|
||||
const orderBy = parsedQuery.getOrderByForOutputColumn(header);
|
||||
if (orderBy) {
|
||||
className.push(orderBy.getEffectiveDirection() === 'DESC' ? '-sort-desc' : '-sort-asc');
|
||||
}
|
||||
|
||||
return className.join(' ');
|
||||
}
|
||||
|
||||
const outerLimit = queryResult.getSqlOuterLimit();
|
||||
const hasMoreResults = queryResult.rows.length === outerLimit;
|
||||
const finalPage =
|
||||
hasMoreResults && Math.floor(queryResult.rows.length / pagination.pageSize) === pagination.page; // on the last page
|
||||
|
||||
const numericColumnBraces = getNumericColumnBraces(queryResult, pagination);
|
||||
return (
|
||||
<div className={classNames('record-table-pane', { 'more-results': hasMoreResults })}>
|
||||
{finalPage ? (
|
||||
<div className="dead-end">
|
||||
<p>This is the end of the inline results but there are more results in this query.</p>
|
||||
<Button
|
||||
icon={IconNames.ARROW_LEFT}
|
||||
text="Go to previous page"
|
||||
fill
|
||||
onClick={() => setPagination({ ...pagination, page: pagination.page - 1 })}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<ReactTable
|
||||
className="-striped -highlight"
|
||||
data={queryResult.rows as any[][]}
|
||||
ofText={hasMoreResults ? '' : 'of'}
|
||||
noDataText={queryResult.rows.length ? '' : 'Query returned no data'}
|
||||
page={pagination.page}
|
||||
pageSize={pagination.pageSize}
|
||||
onPageChange={page => setPagination({ ...pagination, page })}
|
||||
onPageSizeChange={(pageSize, page) => setPagination({ page, pageSize })}
|
||||
sortable={false}
|
||||
defaultPageSize={SMALL_TABLE_PAGE_SIZE}
|
||||
pageSizeOptions={SMALL_TABLE_PAGE_SIZE_OPTIONS}
|
||||
showPagination={
|
||||
queryResult.rows.length > Math.min(SMALL_TABLE_PAGE_SIZE, pagination.pageSize)
|
||||
}
|
||||
columns={filterMap(queryResult.header, (column, i) => {
|
||||
const h = column.name;
|
||||
const icon = columnToIcon(column);
|
||||
|
||||
return {
|
||||
Header() {
|
||||
return (
|
||||
<div className="clickable-cell">
|
||||
<div className="output-name">
|
||||
{icon && <Icon className="type-icon" icon={icon} size={12} />}
|
||||
{h}
|
||||
{hasFilterOnHeader(h, i) && <Icon icon={IconNames.FILTER} size={14} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
headerClassName: getHeaderClassName(h),
|
||||
accessor: String(i),
|
||||
Cell(row) {
|
||||
const value = row.value;
|
||||
return (
|
||||
<div>
|
||||
<Popover2 content={<Deferred content={() => getCellMenu(column, i, value)} />}>
|
||||
{numericColumnBraces[i] ? (
|
||||
<BracedText
|
||||
className="table-padding"
|
||||
text={formatNumber(value)}
|
||||
braces={numericColumnBraces[i]}
|
||||
padFractionalPart
|
||||
/>
|
||||
) : (
|
||||
<TableCell value={value} unlimited />
|
||||
)}
|
||||
</Popover2>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
width: columnToWidth(column),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{showValue && <ShowValueDialog onClose={() => setShowValue(undefined)} str={showValue} />}
|
||||
</div>
|
||||
);
|
||||
});
|
|
@ -87,7 +87,7 @@ export const ShowJson = React.memo(function ShowJson(props: ShowJsonProps) {
|
|||
) : (
|
||||
<AceEditor
|
||||
mode="hjson"
|
||||
theme="tomorrow"
|
||||
theme="solarized_dark"
|
||||
readOnly
|
||||
fontSize={12}
|
||||
width="100%"
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@import '../../variables';
|
||||
|
||||
.table-cell-unparseable {
|
||||
padding: $table-cell-v-padding $table-cell-h-padding;
|
||||
color: #9e2b0e;
|
||||
}
|
||||
|
|
|
@ -17,23 +17,26 @@
|
|||
*/
|
||||
|
||||
import { Icon, IconName } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import React, { MouseEventHandler, ReactNode } from 'react';
|
||||
|
||||
import './table-clickable-cell.scss';
|
||||
|
||||
export interface TableClickableCellProps {
|
||||
className?: string;
|
||||
onClick: MouseEventHandler<any>;
|
||||
hoverIcon?: IconName;
|
||||
title?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const TableClickableCell = React.memo(function TableClickableCell(
|
||||
props: TableClickableCellProps,
|
||||
) {
|
||||
const { onClick, hoverIcon, children } = props;
|
||||
const { className, onClick, hoverIcon, title, children } = props;
|
||||
|
||||
return (
|
||||
<div className="table-clickable-cell" onClick={onClick}>
|
||||
<div className={classNames('table-clickable-cell', className)} title={title} onClick={onClick}>
|
||||
{children}
|
||||
{hoverIcon && <Icon className="hover-icon" icon={hoverIcon} />}
|
||||
</div>
|
||||
|
|
|
@ -20,9 +20,11 @@ import { HotkeysProvider, Intent } from '@blueprintjs/core';
|
|||
import { IconNames } from '@blueprintjs/icons';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { HashRouter, Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { HeaderActiveTab, HeaderBar, Loader } from './components';
|
||||
import { DruidEngine, QueryWithContext } from './druid-models';
|
||||
import { AppToaster } from './singletons';
|
||||
import { Capabilities, QueryManager } from './utils';
|
||||
import {
|
||||
|
@ -34,6 +36,8 @@ import {
|
|||
QueryView,
|
||||
SegmentsView,
|
||||
ServicesView,
|
||||
SqlDataLoaderView,
|
||||
WorkbenchView,
|
||||
} from './views';
|
||||
|
||||
import './console-application.scss';
|
||||
|
@ -75,7 +79,7 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
private openDialog?: string;
|
||||
private datasource?: string;
|
||||
private onlyUnavailable?: boolean;
|
||||
private initQuery?: string;
|
||||
private queryWithContext?: QueryWithContext;
|
||||
|
||||
constructor(props: ConsoleApplicationProps, context: any) {
|
||||
super(props, context);
|
||||
|
@ -119,14 +123,19 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
this.openDialog = undefined;
|
||||
this.datasource = undefined;
|
||||
this.onlyUnavailable = undefined;
|
||||
this.initQuery = undefined;
|
||||
this.queryWithContext = undefined;
|
||||
}, 50);
|
||||
}
|
||||
|
||||
private readonly goToLoadData = (supervisorId?: string, taskId?: string) => {
|
||||
if (taskId) this.taskId = taskId;
|
||||
private readonly goToStreamingDataLoader = (supervisorId?: string) => {
|
||||
if (supervisorId) this.supervisorId = supervisorId;
|
||||
window.location.hash = 'load-data';
|
||||
window.location.hash = 'streaming-data-loader';
|
||||
this.resetInitialsWithDelay();
|
||||
};
|
||||
|
||||
private readonly goToClassicBatchDataLoader = (taskId?: string) => {
|
||||
if (taskId) this.taskId = taskId;
|
||||
window.location.hash = 'classic-batch-data-loader';
|
||||
this.resetInitialsWithDelay();
|
||||
};
|
||||
|
||||
|
@ -143,6 +152,12 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
this.resetInitialsWithDelay();
|
||||
};
|
||||
|
||||
private readonly goToIngestionWithTaskId = (taskId?: string) => {
|
||||
this.taskId = taskId;
|
||||
window.location.hash = 'ingestion';
|
||||
this.resetInitialsWithDelay();
|
||||
};
|
||||
|
||||
private readonly goToIngestionWithTaskGroupId = (taskGroupId?: string, openDialog?: string) => {
|
||||
this.taskGroupId = taskGroupId;
|
||||
if (openDialog) this.openDialog = openDialog;
|
||||
|
@ -157,9 +172,9 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
this.resetInitialsWithDelay();
|
||||
};
|
||||
|
||||
private readonly goToQuery = (initQuery: string) => {
|
||||
this.initQuery = initQuery;
|
||||
window.location.hash = 'query';
|
||||
private readonly goToQuery = (queryWithContext: QueryWithContext) => {
|
||||
this.queryWithContext = queryWithContext;
|
||||
window.location.hash = 'workbench';
|
||||
this.resetInitialsWithDelay();
|
||||
};
|
||||
|
||||
|
@ -187,13 +202,41 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
return this.wrapInViewContainer(null, <HomeView capabilities={capabilities} />);
|
||||
};
|
||||
|
||||
private readonly wrappedLoadDataView = () => {
|
||||
private readonly wrappedDataLoaderView = () => {
|
||||
const { exampleManifestsUrl } = this.props;
|
||||
|
||||
return this.wrapInViewContainer(
|
||||
'load-data',
|
||||
'data-loader',
|
||||
<LoadDataView
|
||||
mode="all"
|
||||
initTaskId={this.taskId}
|
||||
initSupervisorId={this.supervisorId}
|
||||
exampleManifestsUrl={exampleManifestsUrl}
|
||||
goToIngestion={this.goToIngestionWithTaskGroupId}
|
||||
/>,
|
||||
'narrow-pad',
|
||||
);
|
||||
};
|
||||
|
||||
private readonly wrappedStreamingDataLoaderView = () => {
|
||||
return this.wrapInViewContainer(
|
||||
'streaming-data-loader',
|
||||
<LoadDataView
|
||||
mode="streaming"
|
||||
initSupervisorId={this.supervisorId}
|
||||
goToIngestion={this.goToIngestionWithTaskGroupId}
|
||||
/>,
|
||||
'narrow-pad',
|
||||
);
|
||||
};
|
||||
|
||||
private readonly wrappedClassicBatchDataLoaderView = () => {
|
||||
const { exampleManifestsUrl } = this.props;
|
||||
|
||||
return this.wrapInViewContainer(
|
||||
'classic-batch-data-loader',
|
||||
<LoadDataView
|
||||
mode="batch"
|
||||
initTaskId={this.taskId}
|
||||
exampleManifestsUrl={exampleManifestsUrl}
|
||||
goToIngestion={this.goToIngestionWithTaskGroupId}
|
||||
|
@ -208,7 +251,7 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
return this.wrapInViewContainer(
|
||||
'query',
|
||||
<QueryView
|
||||
initQuery={this.initQuery}
|
||||
initQuery={this.queryWithContext?.queryString}
|
||||
defaultQueryContext={defaultQueryContext}
|
||||
mandatoryQueryContext={mandatoryQueryContext}
|
||||
/>,
|
||||
|
@ -216,6 +259,43 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
);
|
||||
};
|
||||
|
||||
private readonly wrappedWorkbenchView = (p: RouteComponentProps<any>) => {
|
||||
const { defaultQueryContext, mandatoryQueryContext } = this.props;
|
||||
const { capabilities } = this.state;
|
||||
|
||||
const queryEngines: DruidEngine[] = ['native'];
|
||||
if (capabilities.hasSql()) {
|
||||
queryEngines.push('sql-native');
|
||||
}
|
||||
if (capabilities.hasMultiStageQuery()) {
|
||||
queryEngines.push('sql-msq-task');
|
||||
}
|
||||
|
||||
return this.wrapInViewContainer(
|
||||
'workbench',
|
||||
<WorkbenchView
|
||||
tabId={p.match.params.tabId}
|
||||
onTabChange={newTabId => {
|
||||
location.hash = `#workbench/${newTabId}`;
|
||||
}}
|
||||
initQueryWithContext={this.queryWithContext}
|
||||
defaultQueryContext={defaultQueryContext}
|
||||
mandatoryQueryContext={mandatoryQueryContext}
|
||||
queryEngines={queryEngines}
|
||||
allowExplain
|
||||
goToIngestion={this.goToIngestionWithTaskId}
|
||||
/>,
|
||||
'thin',
|
||||
);
|
||||
};
|
||||
|
||||
private readonly wrappedSqlDataLoaderView = () => {
|
||||
return this.wrapInViewContainer(
|
||||
'sql-data-loader',
|
||||
<SqlDataLoaderView goToQuery={this.goToQuery} goToIngestion={this.goToIngestionWithTaskId} />,
|
||||
);
|
||||
};
|
||||
|
||||
private readonly wrappedDatasourcesView = () => {
|
||||
const { capabilities } = this.state;
|
||||
return this.wrapInViewContainer(
|
||||
|
@ -248,12 +328,14 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
return this.wrapInViewContainer(
|
||||
'ingestion',
|
||||
<IngestionView
|
||||
taskId={this.taskId}
|
||||
taskGroupId={this.taskGroupId}
|
||||
datasourceId={this.datasource}
|
||||
openDialog={this.openDialog}
|
||||
goToDatasource={this.goToDatasources}
|
||||
goToQuery={this.goToQuery}
|
||||
goToLoadData={this.goToLoadData}
|
||||
goToStreamingDataLoader={this.goToStreamingDataLoader}
|
||||
goToClassicBatchDataLoader={this.goToClassicBatchDataLoader}
|
||||
capabilities={capabilities}
|
||||
/>,
|
||||
);
|
||||
|
@ -263,11 +345,7 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
const { capabilities } = this.state;
|
||||
return this.wrapInViewContainer(
|
||||
'services',
|
||||
<ServicesView
|
||||
goToQuery={this.goToQuery}
|
||||
goToTask={this.goToIngestionWithTaskGroupId}
|
||||
capabilities={capabilities}
|
||||
/>,
|
||||
<ServicesView goToQuery={this.goToQuery} capabilities={capabilities} />,
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -291,7 +369,15 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
<HashRouter hashType="noslash">
|
||||
<div className="console-application">
|
||||
<Switch>
|
||||
<Route path="/load-data" component={this.wrappedLoadDataView} />
|
||||
<Route path="/data-loader" component={this.wrappedDataLoaderView} />
|
||||
<Route
|
||||
path="/streaming-data-loader"
|
||||
component={this.wrappedStreamingDataLoaderView}
|
||||
/>
|
||||
<Route
|
||||
path="/classic-batch-data-loader"
|
||||
component={this.wrappedClassicBatchDataLoaderView}
|
||||
/>
|
||||
|
||||
<Route path="/ingestion" component={this.wrappedIngestionView} />
|
||||
<Route path="/datasources" component={this.wrappedDatasourcesView} />
|
||||
|
@ -299,6 +385,11 @@ export class ConsoleApplication extends React.PureComponent<
|
|||
<Route path="/services" component={this.wrappedServicesView} />
|
||||
|
||||
<Route path="/query" component={this.wrappedQueryView} />
|
||||
<Route
|
||||
path={['/workbench/:tabId', '/workbench']}
|
||||
component={this.wrappedWorkbenchView}
|
||||
/>
|
||||
<Route path="/sql-data-loader" component={this.wrappedSqlDataLoaderView} />
|
||||
|
||||
<Route path="/lookups" component={this.wrappedLookupsView} />
|
||||
<Route component={this.wrappedHomeView} />
|
||||
|
|
|
@ -30,7 +30,7 @@ import { IconNames } from '@blueprintjs/icons';
|
|||
import classNames from 'classnames';
|
||||
import React, { ReactNode, useState } from 'react';
|
||||
|
||||
import { WarningChecklist } from '../../components/warning-checklist/warning-checklist';
|
||||
import { WarningChecklist } from '../../components';
|
||||
import { AppToaster } from '../../singletons';
|
||||
|
||||
import './async-action-dialog.scss';
|
||||
|
|
|
@ -280,6 +280,20 @@ exports[`CompactionDialog matches snapshot with compactionConfig (dynamic partit
|
|||
"name": "tuningConfig.maxNumConcurrentSubTasks",
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": -1,
|
||||
"info": <React.Fragment>
|
||||
<p>
|
||||
Limit of the number of segments to merge in a single phase when merging segments for publishing. This limit affects the total number of columns present in a set of segments to merge. If the limit is exceeded, segment merging occurs in multiple phases. Druid merges at least 2 segments per phase, regardless of this setting.
|
||||
</p>
|
||||
<p>
|
||||
Default: -1 (unlimited)
|
||||
</p>
|
||||
</React.Fragment>,
|
||||
"min": -1,
|
||||
"name": "tuningConfig.maxColumnsToMerge",
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": 10,
|
||||
"defined": [Function],
|
||||
|
@ -642,6 +656,20 @@ exports[`CompactionDialog matches snapshot with compactionConfig (hashed partiti
|
|||
"name": "tuningConfig.maxNumConcurrentSubTasks",
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": -1,
|
||||
"info": <React.Fragment>
|
||||
<p>
|
||||
Limit of the number of segments to merge in a single phase when merging segments for publishing. This limit affects the total number of columns present in a set of segments to merge. If the limit is exceeded, segment merging occurs in multiple phases. Druid merges at least 2 segments per phase, regardless of this setting.
|
||||
</p>
|
||||
<p>
|
||||
Default: -1 (unlimited)
|
||||
</p>
|
||||
</React.Fragment>,
|
||||
"min": -1,
|
||||
"name": "tuningConfig.maxColumnsToMerge",
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": 10,
|
||||
"defined": [Function],
|
||||
|
@ -1004,6 +1032,20 @@ exports[`CompactionDialog matches snapshot with compactionConfig (range partitio
|
|||
"name": "tuningConfig.maxNumConcurrentSubTasks",
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": -1,
|
||||
"info": <React.Fragment>
|
||||
<p>
|
||||
Limit of the number of segments to merge in a single phase when merging segments for publishing. This limit affects the total number of columns present in a set of segments to merge. If the limit is exceeded, segment merging occurs in multiple phases. Druid merges at least 2 segments per phase, regardless of this setting.
|
||||
</p>
|
||||
<p>
|
||||
Default: -1 (unlimited)
|
||||
</p>
|
||||
</React.Fragment>,
|
||||
"min": -1,
|
||||
"name": "tuningConfig.maxColumnsToMerge",
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": 10,
|
||||
"defined": [Function],
|
||||
|
@ -1366,6 +1408,20 @@ exports[`CompactionDialog matches snapshot without compactionConfig 1`] = `
|
|||
"name": "tuningConfig.maxNumConcurrentSubTasks",
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": -1,
|
||||
"info": <React.Fragment>
|
||||
<p>
|
||||
Limit of the number of segments to merge in a single phase when merging segments for publishing. This limit affects the total number of columns present in a set of segments to merge. If the limit is exceeded, segment merging occurs in multiple phases. Druid merges at least 2 segments per phase, regardless of this setting.
|
||||
</p>
|
||||
<p>
|
||||
Default: -1 (unlimited)
|
||||
</p>
|
||||
</React.Fragment>,
|
||||
"min": -1,
|
||||
"name": "tuningConfig.maxColumnsToMerge",
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": 10,
|
||||
"defined": [Function],
|
||||
|
|
|
@ -12,7 +12,7 @@ exports[`CoordinatorDynamicConfigDialog matches snapshot 1`] = `
|
|||
Edit the coordinator dynamic configuration on the fly. For more information please refer to the
|
||||
|
||||
<Memo(ExternalLink)
|
||||
href="https://druid.apache.org/docs/0.23.0/configuration/index.html#dynamic-configuration"
|
||||
href="https://druid.apache.org/docs/latest/configuration/index.html#dynamic-configuration"
|
||||
>
|
||||
documentation
|
||||
</Memo(ExternalLink)>
|
||||
|
|
|
@ -65,6 +65,33 @@ exports[`Datasource table action dialog matches snapshot 1`] = `
|
|||
<button
|
||||
class="bp4-button bp4-intent-primary tab-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="bp4-icon bp4-icon-th"
|
||||
icon="th"
|
||||
>
|
||||
<svg
|
||||
data-icon="th"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
>
|
||||
<path
|
||||
d="M19 1H1c-.6 0-1 .5-1 1v16c0 .5.4 1 1 1h18c.5 0 1-.5 1-1V2c0-.5-.5-1-1-1zM7 17H2v-3h5v3zm0-4H2v-3h5v3zm0-4H2V6h5v3zm11 8H8v-3h10v3zm0-4H8v-3h10v3zm0-4H8V6h10v3z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="bp4-button-text"
|
||||
>
|
||||
Records
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="bp4-button bp4-minimal tab-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
|
@ -94,43 +121,39 @@ exports[`Datasource table action dialog matches snapshot 1`] = `
|
|||
class="main-section"
|
||||
>
|
||||
<div
|
||||
class="datasource-columns-table"
|
||||
class="datasource-preview-pane"
|
||||
>
|
||||
<div
|
||||
class="main-area"
|
||||
class="loader"
|
||||
>
|
||||
<div
|
||||
class="loader"
|
||||
class="loader-logo"
|
||||
>
|
||||
<div
|
||||
class="loader-logo"
|
||||
<svg
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<path
|
||||
class="one"
|
||||
d="M54.2,69.8h-2.7c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h2.7c11.5,0,23.8-7.4,23.8-23.7
|
||||
<path
|
||||
class="one"
|
||||
d="M54.2,69.8h-2.7c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h2.7c11.5,0,23.8-7.4,23.8-23.7
|
||||
c0-9.1-6.9-15.8-16.4-15.8H38c-0.7,0-1.3-0.6-1.3-1.3s0.6-1.3,1.3-1.3h23.6c5.3,0,10.1,1.9,13.6,5.3c3.5,3.4,5.4,8,5.4,13.1
|
||||
c0,6.6-2.3,13-6.3,17.7C69.5,66.8,62.5,69.8,54.2,69.8z"
|
||||
/>
|
||||
<path
|
||||
class="two"
|
||||
d="M55.7,59.5h-26c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h26c7.5,0,11.5-5.8,11.5-11.5
|
||||
/>
|
||||
<path
|
||||
class="two"
|
||||
d="M55.7,59.5h-26c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h26c7.5,0,11.5-5.8,11.5-11.5
|
||||
c0-4.2-3.2-7.3-7.7-7.3h-26c-0.7,0-1.3-0.6-1.3-1.3s0.6-1.3,1.3-1.3h26c5.9,0,10.3,4.3,10.3,9.9c0,3.7-1.3,7.2-3.7,9.8
|
||||
C63.5,58,59.9,59.5,55.7,59.5z"
|
||||
/>
|
||||
<path
|
||||
class="three"
|
||||
d="M27.2,38h-6.3c-0.7,0-1.3-0.6-1.3-1.3s0.6-1.3,1.3-1.3h6.3c0.7,0,1.3,0.6,1.3,1.3S27.9,38,27.2,38z"
|
||||
/>
|
||||
<path
|
||||
class="four"
|
||||
d="M45.1,69.8h-5.8c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h5.8c0.7,0,1.3,0.6,1.3,1.3
|
||||
/>
|
||||
<path
|
||||
class="three"
|
||||
d="M27.2,38h-6.3c-0.7,0-1.3-0.6-1.3-1.3s0.6-1.3,1.3-1.3h6.3c0.7,0,1.3,0.6,1.3,1.3S27.9,38,27.2,38z"
|
||||
/>
|
||||
<path
|
||||
class="four"
|
||||
d="M45.1,69.8h-5.8c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h5.8c0.7,0,1.3,0.6,1.3,1.3
|
||||
C46.4,69.2,45.8,69.8,45.1,69.8z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,648 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DatasourceColumnsTable matches snapshot on error 1`] = `
|
||||
<div
|
||||
className="datasource-columns-table"
|
||||
>
|
||||
<ReactTable
|
||||
AggregatedComponent={[Function]}
|
||||
ExpanderComponent={[Function]}
|
||||
FilterComponent={[Function]}
|
||||
LoadingComponent={[Function]}
|
||||
NoDataComponent={[Function]}
|
||||
PadRowComponent={[Function]}
|
||||
PaginationComponent={[Function]}
|
||||
PivotValueComponent={[Function]}
|
||||
ResizerComponent={[Function]}
|
||||
TableComponent={[Function]}
|
||||
TbodyComponent={[Function]}
|
||||
TdComponent={[Function]}
|
||||
TfootComponent={[Function]}
|
||||
ThComponent={[Function]}
|
||||
TheadComponent={[Function]}
|
||||
TrComponent={[Function]}
|
||||
TrGroupComponent={[Function]}
|
||||
aggregatedKey="_aggregated"
|
||||
className=""
|
||||
collapseOnDataChange={true}
|
||||
collapseOnPageChange={true}
|
||||
collapseOnSortingChange={true}
|
||||
column={
|
||||
Object {
|
||||
"Aggregated": undefined,
|
||||
"Cell": undefined,
|
||||
"Expander": undefined,
|
||||
"Filter": undefined,
|
||||
"Footer": undefined,
|
||||
"Header": undefined,
|
||||
"Pivot": undefined,
|
||||
"PivotValue": undefined,
|
||||
"Placeholder": undefined,
|
||||
"aggregate": undefined,
|
||||
"className": "",
|
||||
"filterAll": false,
|
||||
"filterMethod": undefined,
|
||||
"filterable": undefined,
|
||||
"footerClassName": "",
|
||||
"footerStyle": Object {},
|
||||
"getFooterProps": [Function],
|
||||
"getHeaderProps": [Function],
|
||||
"getProps": [Function],
|
||||
"headerClassName": "",
|
||||
"headerStyle": Object {},
|
||||
"minResizeWidth": 11,
|
||||
"minWidth": 100,
|
||||
"resizable": undefined,
|
||||
"show": true,
|
||||
"sortMethod": undefined,
|
||||
"sortable": undefined,
|
||||
"style": Object {},
|
||||
}
|
||||
}
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"Header": "Column name",
|
||||
"accessor": "COLUMN_NAME",
|
||||
"className": "padded",
|
||||
"width": 300,
|
||||
},
|
||||
Object {
|
||||
"Header": "Data type",
|
||||
"accessor": "DATA_TYPE",
|
||||
"className": "padded",
|
||||
"width": 200,
|
||||
},
|
||||
]
|
||||
}
|
||||
data={Array []}
|
||||
defaultExpanded={Object {}}
|
||||
defaultFilterMethod={[Function]}
|
||||
defaultFiltered={Array []}
|
||||
defaultPage={0}
|
||||
defaultPageSize={25}
|
||||
defaultResized={Array []}
|
||||
defaultSortDesc={false}
|
||||
defaultSortMethod={[Function]}
|
||||
defaultSorted={Array []}
|
||||
expanderDefaults={
|
||||
Object {
|
||||
"filterable": false,
|
||||
"resizable": false,
|
||||
"sortable": false,
|
||||
"width": 35,
|
||||
}
|
||||
}
|
||||
filterable={true}
|
||||
freezeWhenExpanded={false}
|
||||
getLoadingProps={[Function]}
|
||||
getNoDataProps={[Function]}
|
||||
getPaginationProps={[Function]}
|
||||
getProps={[Function]}
|
||||
getResizerProps={[Function]}
|
||||
getTableProps={[Function]}
|
||||
getTbodyProps={[Function]}
|
||||
getTdProps={[Function]}
|
||||
getTfootProps={[Function]}
|
||||
getTfootTdProps={[Function]}
|
||||
getTfootTrProps={[Function]}
|
||||
getTheadFilterProps={[Function]}
|
||||
getTheadFilterThProps={[Function]}
|
||||
getTheadFilterTrProps={[Function]}
|
||||
getTheadGroupProps={[Function]}
|
||||
getTheadGroupThProps={[Function]}
|
||||
getTheadGroupTrProps={[Function]}
|
||||
getTheadProps={[Function]}
|
||||
getTheadThProps={[Function]}
|
||||
getTheadTrProps={[Function]}
|
||||
getTrGroupProps={[Function]}
|
||||
getTrProps={[Function]}
|
||||
groupedByPivotKey="_groupedByPivot"
|
||||
indexKey="_index"
|
||||
loading={false}
|
||||
loadingText="Loading..."
|
||||
multiSort={true}
|
||||
nestingLevelKey="_nestingLevel"
|
||||
nextText="Next"
|
||||
noDataText="test error"
|
||||
ofText="of"
|
||||
onFetchData={[Function]}
|
||||
originalKey="_original"
|
||||
pageJumpText="jump to page"
|
||||
pageSizeOptions={
|
||||
Array [
|
||||
25,
|
||||
50,
|
||||
100,
|
||||
]
|
||||
}
|
||||
pageText="Page"
|
||||
pivotDefaults={Object {}}
|
||||
pivotIDKey="_pivotID"
|
||||
pivotValKey="_pivotVal"
|
||||
previousText="Previous"
|
||||
resizable={true}
|
||||
resolveData={[Function]}
|
||||
rowsSelectorText="rows per page"
|
||||
rowsText="rows"
|
||||
showPageJump={true}
|
||||
showPageSizeOptions={true}
|
||||
showPagination={false}
|
||||
showPaginationBottom={true}
|
||||
showPaginationTop={false}
|
||||
sortable={true}
|
||||
style={Object {}}
|
||||
subRowsKey="_subRows"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DatasourceColumnsTable matches snapshot on init 1`] = `
|
||||
<div
|
||||
className="datasource-columns-table"
|
||||
>
|
||||
<ReactTable
|
||||
AggregatedComponent={[Function]}
|
||||
ExpanderComponent={[Function]}
|
||||
FilterComponent={[Function]}
|
||||
LoadingComponent={[Function]}
|
||||
NoDataComponent={[Function]}
|
||||
PadRowComponent={[Function]}
|
||||
PaginationComponent={[Function]}
|
||||
PivotValueComponent={[Function]}
|
||||
ResizerComponent={[Function]}
|
||||
TableComponent={[Function]}
|
||||
TbodyComponent={[Function]}
|
||||
TdComponent={[Function]}
|
||||
TfootComponent={[Function]}
|
||||
ThComponent={[Function]}
|
||||
TheadComponent={[Function]}
|
||||
TrComponent={[Function]}
|
||||
TrGroupComponent={[Function]}
|
||||
aggregatedKey="_aggregated"
|
||||
className=""
|
||||
collapseOnDataChange={true}
|
||||
collapseOnPageChange={true}
|
||||
collapseOnSortingChange={true}
|
||||
column={
|
||||
Object {
|
||||
"Aggregated": undefined,
|
||||
"Cell": undefined,
|
||||
"Expander": undefined,
|
||||
"Filter": undefined,
|
||||
"Footer": undefined,
|
||||
"Header": undefined,
|
||||
"Pivot": undefined,
|
||||
"PivotValue": undefined,
|
||||
"Placeholder": undefined,
|
||||
"aggregate": undefined,
|
||||
"className": "",
|
||||
"filterAll": false,
|
||||
"filterMethod": undefined,
|
||||
"filterable": undefined,
|
||||
"footerClassName": "",
|
||||
"footerStyle": Object {},
|
||||
"getFooterProps": [Function],
|
||||
"getHeaderProps": [Function],
|
||||
"getProps": [Function],
|
||||
"headerClassName": "",
|
||||
"headerStyle": Object {},
|
||||
"minResizeWidth": 11,
|
||||
"minWidth": 100,
|
||||
"resizable": undefined,
|
||||
"show": true,
|
||||
"sortMethod": undefined,
|
||||
"sortable": undefined,
|
||||
"style": Object {},
|
||||
}
|
||||
}
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"Header": "Column name",
|
||||
"accessor": "COLUMN_NAME",
|
||||
"className": "padded",
|
||||
"width": 300,
|
||||
},
|
||||
Object {
|
||||
"Header": "Data type",
|
||||
"accessor": "DATA_TYPE",
|
||||
"className": "padded",
|
||||
"width": 200,
|
||||
},
|
||||
]
|
||||
}
|
||||
data={Array []}
|
||||
defaultExpanded={Object {}}
|
||||
defaultFilterMethod={[Function]}
|
||||
defaultFiltered={Array []}
|
||||
defaultPage={0}
|
||||
defaultPageSize={25}
|
||||
defaultResized={Array []}
|
||||
defaultSortDesc={false}
|
||||
defaultSortMethod={[Function]}
|
||||
defaultSorted={Array []}
|
||||
expanderDefaults={
|
||||
Object {
|
||||
"filterable": false,
|
||||
"resizable": false,
|
||||
"sortable": false,
|
||||
"width": 35,
|
||||
}
|
||||
}
|
||||
filterable={true}
|
||||
freezeWhenExpanded={false}
|
||||
getLoadingProps={[Function]}
|
||||
getNoDataProps={[Function]}
|
||||
getPaginationProps={[Function]}
|
||||
getProps={[Function]}
|
||||
getResizerProps={[Function]}
|
||||
getTableProps={[Function]}
|
||||
getTbodyProps={[Function]}
|
||||
getTdProps={[Function]}
|
||||
getTfootProps={[Function]}
|
||||
getTfootTdProps={[Function]}
|
||||
getTfootTrProps={[Function]}
|
||||
getTheadFilterProps={[Function]}
|
||||
getTheadFilterThProps={[Function]}
|
||||
getTheadFilterTrProps={[Function]}
|
||||
getTheadGroupProps={[Function]}
|
||||
getTheadGroupThProps={[Function]}
|
||||
getTheadGroupTrProps={[Function]}
|
||||
getTheadProps={[Function]}
|
||||
getTheadThProps={[Function]}
|
||||
getTheadTrProps={[Function]}
|
||||
getTrGroupProps={[Function]}
|
||||
getTrProps={[Function]}
|
||||
groupedByPivotKey="_groupedByPivot"
|
||||
indexKey="_index"
|
||||
loading={false}
|
||||
loadingText="Loading..."
|
||||
multiSort={true}
|
||||
nestingLevelKey="_nestingLevel"
|
||||
nextText="Next"
|
||||
noDataText="No column data found"
|
||||
ofText="of"
|
||||
onFetchData={[Function]}
|
||||
originalKey="_original"
|
||||
pageJumpText="jump to page"
|
||||
pageSizeOptions={
|
||||
Array [
|
||||
25,
|
||||
50,
|
||||
100,
|
||||
]
|
||||
}
|
||||
pageText="Page"
|
||||
pivotDefaults={Object {}}
|
||||
pivotIDKey="_pivotID"
|
||||
pivotValKey="_pivotVal"
|
||||
previousText="Previous"
|
||||
resizable={true}
|
||||
resolveData={[Function]}
|
||||
rowsSelectorText="rows per page"
|
||||
rowsText="rows"
|
||||
showPageJump={true}
|
||||
showPageSizeOptions={true}
|
||||
showPagination={false}
|
||||
showPaginationBottom={true}
|
||||
showPaginationTop={false}
|
||||
sortable={true}
|
||||
style={Object {}}
|
||||
subRowsKey="_subRows"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DatasourceColumnsTable matches snapshot on loading 1`] = `
|
||||
<div
|
||||
className="datasource-columns-table"
|
||||
>
|
||||
<Memo(Loader) />
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DatasourceColumnsTable matches snapshot on no data 1`] = `
|
||||
<div
|
||||
className="datasource-columns-table"
|
||||
>
|
||||
<ReactTable
|
||||
AggregatedComponent={[Function]}
|
||||
ExpanderComponent={[Function]}
|
||||
FilterComponent={[Function]}
|
||||
LoadingComponent={[Function]}
|
||||
NoDataComponent={[Function]}
|
||||
PadRowComponent={[Function]}
|
||||
PaginationComponent={[Function]}
|
||||
PivotValueComponent={[Function]}
|
||||
ResizerComponent={[Function]}
|
||||
TableComponent={[Function]}
|
||||
TbodyComponent={[Function]}
|
||||
TdComponent={[Function]}
|
||||
TfootComponent={[Function]}
|
||||
ThComponent={[Function]}
|
||||
TheadComponent={[Function]}
|
||||
TrComponent={[Function]}
|
||||
TrGroupComponent={[Function]}
|
||||
aggregatedKey="_aggregated"
|
||||
className=""
|
||||
collapseOnDataChange={true}
|
||||
collapseOnPageChange={true}
|
||||
collapseOnSortingChange={true}
|
||||
column={
|
||||
Object {
|
||||
"Aggregated": undefined,
|
||||
"Cell": undefined,
|
||||
"Expander": undefined,
|
||||
"Filter": undefined,
|
||||
"Footer": undefined,
|
||||
"Header": undefined,
|
||||
"Pivot": undefined,
|
||||
"PivotValue": undefined,
|
||||
"Placeholder": undefined,
|
||||
"aggregate": undefined,
|
||||
"className": "",
|
||||
"filterAll": false,
|
||||
"filterMethod": undefined,
|
||||
"filterable": undefined,
|
||||
"footerClassName": "",
|
||||
"footerStyle": Object {},
|
||||
"getFooterProps": [Function],
|
||||
"getHeaderProps": [Function],
|
||||
"getProps": [Function],
|
||||
"headerClassName": "",
|
||||
"headerStyle": Object {},
|
||||
"minResizeWidth": 11,
|
||||
"minWidth": 100,
|
||||
"resizable": undefined,
|
||||
"show": true,
|
||||
"sortMethod": undefined,
|
||||
"sortable": undefined,
|
||||
"style": Object {},
|
||||
}
|
||||
}
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"Header": "Column name",
|
||||
"accessor": "COLUMN_NAME",
|
||||
"className": "padded",
|
||||
"width": 300,
|
||||
},
|
||||
Object {
|
||||
"Header": "Data type",
|
||||
"accessor": "DATA_TYPE",
|
||||
"className": "padded",
|
||||
"width": 200,
|
||||
},
|
||||
]
|
||||
}
|
||||
data={Array []}
|
||||
defaultExpanded={Object {}}
|
||||
defaultFilterMethod={[Function]}
|
||||
defaultFiltered={Array []}
|
||||
defaultPage={0}
|
||||
defaultPageSize={25}
|
||||
defaultResized={Array []}
|
||||
defaultSortDesc={false}
|
||||
defaultSortMethod={[Function]}
|
||||
defaultSorted={Array []}
|
||||
expanderDefaults={
|
||||
Object {
|
||||
"filterable": false,
|
||||
"resizable": false,
|
||||
"sortable": false,
|
||||
"width": 35,
|
||||
}
|
||||
}
|
||||
filterable={true}
|
||||
freezeWhenExpanded={false}
|
||||
getLoadingProps={[Function]}
|
||||
getNoDataProps={[Function]}
|
||||
getPaginationProps={[Function]}
|
||||
getProps={[Function]}
|
||||
getResizerProps={[Function]}
|
||||
getTableProps={[Function]}
|
||||
getTbodyProps={[Function]}
|
||||
getTdProps={[Function]}
|
||||
getTfootProps={[Function]}
|
||||
getTfootTdProps={[Function]}
|
||||
getTfootTrProps={[Function]}
|
||||
getTheadFilterProps={[Function]}
|
||||
getTheadFilterThProps={[Function]}
|
||||
getTheadFilterTrProps={[Function]}
|
||||
getTheadGroupProps={[Function]}
|
||||
getTheadGroupThProps={[Function]}
|
||||
getTheadGroupTrProps={[Function]}
|
||||
getTheadProps={[Function]}
|
||||
getTheadThProps={[Function]}
|
||||
getTheadTrProps={[Function]}
|
||||
getTrGroupProps={[Function]}
|
||||
getTrProps={[Function]}
|
||||
groupedByPivotKey="_groupedByPivot"
|
||||
indexKey="_index"
|
||||
loading={false}
|
||||
loadingText="Loading..."
|
||||
multiSort={true}
|
||||
nestingLevelKey="_nestingLevel"
|
||||
nextText="Next"
|
||||
noDataText="No column data found"
|
||||
ofText="of"
|
||||
onFetchData={[Function]}
|
||||
originalKey="_original"
|
||||
pageJumpText="jump to page"
|
||||
pageSizeOptions={
|
||||
Array [
|
||||
25,
|
||||
50,
|
||||
100,
|
||||
]
|
||||
}
|
||||
pageText="Page"
|
||||
pivotDefaults={Object {}}
|
||||
pivotIDKey="_pivotID"
|
||||
pivotValKey="_pivotVal"
|
||||
previousText="Previous"
|
||||
resizable={true}
|
||||
resolveData={[Function]}
|
||||
rowsSelectorText="rows per page"
|
||||
rowsText="rows"
|
||||
showPageJump={true}
|
||||
showPageSizeOptions={true}
|
||||
showPagination={false}
|
||||
showPaginationBottom={true}
|
||||
showPaginationTop={false}
|
||||
sortable={true}
|
||||
style={Object {}}
|
||||
subRowsKey="_subRows"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DatasourceColumnsTable matches snapshot on some data 1`] = `
|
||||
<div
|
||||
className="datasource-columns-table"
|
||||
>
|
||||
<ReactTable
|
||||
AggregatedComponent={[Function]}
|
||||
ExpanderComponent={[Function]}
|
||||
FilterComponent={[Function]}
|
||||
LoadingComponent={[Function]}
|
||||
NoDataComponent={[Function]}
|
||||
PadRowComponent={[Function]}
|
||||
PaginationComponent={[Function]}
|
||||
PivotValueComponent={[Function]}
|
||||
ResizerComponent={[Function]}
|
||||
TableComponent={[Function]}
|
||||
TbodyComponent={[Function]}
|
||||
TdComponent={[Function]}
|
||||
TfootComponent={[Function]}
|
||||
ThComponent={[Function]}
|
||||
TheadComponent={[Function]}
|
||||
TrComponent={[Function]}
|
||||
TrGroupComponent={[Function]}
|
||||
aggregatedKey="_aggregated"
|
||||
className=""
|
||||
collapseOnDataChange={true}
|
||||
collapseOnPageChange={true}
|
||||
collapseOnSortingChange={true}
|
||||
column={
|
||||
Object {
|
||||
"Aggregated": undefined,
|
||||
"Cell": undefined,
|
||||
"Expander": undefined,
|
||||
"Filter": undefined,
|
||||
"Footer": undefined,
|
||||
"Header": undefined,
|
||||
"Pivot": undefined,
|
||||
"PivotValue": undefined,
|
||||
"Placeholder": undefined,
|
||||
"aggregate": undefined,
|
||||
"className": "",
|
||||
"filterAll": false,
|
||||
"filterMethod": undefined,
|
||||
"filterable": undefined,
|
||||
"footerClassName": "",
|
||||
"footerStyle": Object {},
|
||||
"getFooterProps": [Function],
|
||||
"getHeaderProps": [Function],
|
||||
"getProps": [Function],
|
||||
"headerClassName": "",
|
||||
"headerStyle": Object {},
|
||||
"minResizeWidth": 11,
|
||||
"minWidth": 100,
|
||||
"resizable": undefined,
|
||||
"show": true,
|
||||
"sortMethod": undefined,
|
||||
"sortable": undefined,
|
||||
"style": Object {},
|
||||
}
|
||||
}
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"Header": "Column name",
|
||||
"accessor": "COLUMN_NAME",
|
||||
"className": "padded",
|
||||
"width": 300,
|
||||
},
|
||||
Object {
|
||||
"Header": "Data type",
|
||||
"accessor": "DATA_TYPE",
|
||||
"className": "padded",
|
||||
"width": 200,
|
||||
},
|
||||
]
|
||||
}
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"COLUMN_NAME": "channel",
|
||||
"DATA_TYPE": "VARCHAR",
|
||||
},
|
||||
Object {
|
||||
"COLUMN_NAME": "page",
|
||||
"DATA_TYPE": "VARCHAR",
|
||||
},
|
||||
]
|
||||
}
|
||||
defaultExpanded={Object {}}
|
||||
defaultFilterMethod={[Function]}
|
||||
defaultFiltered={Array []}
|
||||
defaultPage={0}
|
||||
defaultPageSize={25}
|
||||
defaultResized={Array []}
|
||||
defaultSortDesc={false}
|
||||
defaultSortMethod={[Function]}
|
||||
defaultSorted={Array []}
|
||||
expanderDefaults={
|
||||
Object {
|
||||
"filterable": false,
|
||||
"resizable": false,
|
||||
"sortable": false,
|
||||
"width": 35,
|
||||
}
|
||||
}
|
||||
filterable={true}
|
||||
freezeWhenExpanded={false}
|
||||
getLoadingProps={[Function]}
|
||||
getNoDataProps={[Function]}
|
||||
getPaginationProps={[Function]}
|
||||
getProps={[Function]}
|
||||
getResizerProps={[Function]}
|
||||
getTableProps={[Function]}
|
||||
getTbodyProps={[Function]}
|
||||
getTdProps={[Function]}
|
||||
getTfootProps={[Function]}
|
||||
getTfootTdProps={[Function]}
|
||||
getTfootTrProps={[Function]}
|
||||
getTheadFilterProps={[Function]}
|
||||
getTheadFilterThProps={[Function]}
|
||||
getTheadFilterTrProps={[Function]}
|
||||
getTheadGroupProps={[Function]}
|
||||
getTheadGroupThProps={[Function]}
|
||||
getTheadGroupTrProps={[Function]}
|
||||
getTheadProps={[Function]}
|
||||
getTheadThProps={[Function]}
|
||||
getTheadTrProps={[Function]}
|
||||
getTrGroupProps={[Function]}
|
||||
getTrProps={[Function]}
|
||||
groupedByPivotKey="_groupedByPivot"
|
||||
indexKey="_index"
|
||||
loading={false}
|
||||
loadingText="Loading..."
|
||||
multiSort={true}
|
||||
nestingLevelKey="_nestingLevel"
|
||||
nextText="Next"
|
||||
noDataText="No column data found"
|
||||
ofText="of"
|
||||
onFetchData={[Function]}
|
||||
originalKey="_original"
|
||||
pageJumpText="jump to page"
|
||||
pageSizeOptions={
|
||||
Array [
|
||||
25,
|
||||
50,
|
||||
100,
|
||||
]
|
||||
}
|
||||
pageText="Page"
|
||||
pivotDefaults={Object {}}
|
||||
pivotIDKey="_pivotID"
|
||||
pivotValKey="_pivotVal"
|
||||
previousText="Previous"
|
||||
resizable={true}
|
||||
resolveData={[Function]}
|
||||
rowsSelectorText="rows per page"
|
||||
rowsText="rows"
|
||||
showPageJump={true}
|
||||
showPageSizeOptions={true}
|
||||
showPagination={false}
|
||||
showPaginationBottom={true}
|
||||
showPaginationTop={false}
|
||||
sortable={true}
|
||||
style={Object {}}
|
||||
subRowsKey="_subRows"
|
||||
/>
|
||||
</div>
|
||||
`;
|
|
@ -29,21 +29,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.main-area {
|
||||
height: calc(100% - 5px);
|
||||
.loader {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.loader {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ReactTable {
|
||||
height: 100%;
|
||||
}
|
||||
.ReactTable {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
|
@ -19,12 +19,12 @@
|
|||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { QueryState } from '../../utils';
|
||||
import { QueryState } from '../../../utils';
|
||||
|
||||
import { DatasourceColumnsTable, DatasourceColumnsTableRow } from './datasource-columns-table';
|
||||
|
||||
let columnsState: QueryState<DatasourceColumnsTableRow[]> = QueryState.INIT;
|
||||
jest.mock('../../hooks', () => {
|
||||
jest.mock('../../../hooks', () => {
|
||||
return {
|
||||
useQueryManager: () => [columnsState],
|
||||
};
|
||||
|
@ -32,7 +32,7 @@ jest.mock('../../hooks', () => {
|
|||
|
||||
describe('DatasourceColumnsTable', () => {
|
||||
function makeDatasourceColumnsTable() {
|
||||
return <DatasourceColumnsTable datasourceId="test" downloadFilename="test" />;
|
||||
return <DatasourceColumnsTable datasource="test" />;
|
||||
}
|
||||
|
||||
it('matches snapshot on init', () => {
|
|
@ -20,10 +20,10 @@ import { SqlLiteral } from 'druid-query-toolkit';
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
|
||||
import { useQueryManager } from '../../hooks';
|
||||
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
|
||||
import { ColumnMetadata, queryDruidSql } from '../../utils';
|
||||
import { Loader } from '../loader/loader';
|
||||
import { Loader } from '../../../components';
|
||||
import { useQueryManager } from '../../../hooks';
|
||||
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../../react-table';
|
||||
import { ColumnMetadata, queryDruidSql } from '../../../utils';
|
||||
|
||||
import './datasource-columns-table.scss';
|
||||
|
||||
|
@ -33,21 +33,20 @@ export interface DatasourceColumnsTableRow {
|
|||
}
|
||||
|
||||
export interface DatasourceColumnsTableProps {
|
||||
datasourceId: string;
|
||||
downloadFilename?: string;
|
||||
datasource: string;
|
||||
}
|
||||
|
||||
export const DatasourceColumnsTable = React.memo(function DatasourceColumnsTable(
|
||||
props: DatasourceColumnsTableProps,
|
||||
) {
|
||||
const [columnsState] = useQueryManager<string, DatasourceColumnsTableRow[]>({
|
||||
initQuery: props.datasource,
|
||||
processQuery: async (datasourceId: string) => {
|
||||
return await queryDruidSql<ColumnMetadata>({
|
||||
query: `SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'druid' AND TABLE_NAME = ${SqlLiteral.create(datasourceId)}`,
|
||||
});
|
||||
},
|
||||
initQuery: props.datasourceId,
|
||||
});
|
||||
|
||||
function renderTable() {
|
||||
|
@ -80,7 +79,7 @@ export const DatasourceColumnsTable = React.memo(function DatasourceColumnsTable
|
|||
|
||||
return (
|
||||
<div className="datasource-columns-table">
|
||||
<div className="main-area">{columnsState.loading ? <Loader /> : renderTable()}</div>
|
||||
{columnsState.loading ? <Loader /> : renderTable()}
|
||||
</div>
|
||||
);
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.datasource-preview-pane {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
.record-table-pane {
|
||||
height: 100%;
|
||||
}
|
||||
.datasource-preview-error {
|
||||
color: #9e2b0e;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { QueryResult, QueryRunner, SqlTableRef } from 'druid-query-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
import { Loader, RecordTablePane } from '../../../components';
|
||||
import { useQueryManager } from '../../../hooks';
|
||||
import { DruidError } from '../../../utils';
|
||||
|
||||
import './datasource-preview-pane.scss';
|
||||
|
||||
const queryRunner = new QueryRunner({
|
||||
inflateDateStrategy: 'none',
|
||||
});
|
||||
|
||||
export interface DatasourcePreviewPaneProps {
|
||||
datasource: string;
|
||||
}
|
||||
|
||||
export const DatasourcePreviewPane = React.memo(function DatasourcePreviewPane(
|
||||
props: DatasourcePreviewPaneProps,
|
||||
) {
|
||||
const [recordState] = useQueryManager<string, QueryResult>({
|
||||
initQuery: props.datasource,
|
||||
processQuery: async (datasource, cancelToken) => {
|
||||
let result: QueryResult;
|
||||
try {
|
||||
result = await queryRunner.runQuery({
|
||||
query: `SELECT * FROM ${SqlTableRef.create(datasource)}`,
|
||||
extraQueryContext: { sqlOuterLimit: 100 },
|
||||
cancelToken,
|
||||
});
|
||||
} catch (e) {
|
||||
throw new DruidError(e);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="datasource-preview-pane">
|
||||
{recordState.loading && <Loader />}
|
||||
{recordState.data && <RecordTablePane queryResult={recordState.data} />}
|
||||
{recordState.error && (
|
||||
<div className="datasource-preview-error">{recordState.error.message}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
|
@ -25,7 +25,7 @@ describe('Datasource table action dialog', () => {
|
|||
it('matches snapshot', () => {
|
||||
const datasourceTableActionDialog = (
|
||||
<DatasourceTableActionDialog
|
||||
datasourceId="test"
|
||||
datasource="test"
|
||||
actions={[{ title: 'test', onAction: () => null }]}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
|
|
|
@ -16,27 +16,36 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { DatasourceColumnsTable } from '../../components/datasource-columns-table/datasource-columns-table';
|
||||
import { BasicAction } from '../../utils/basic-action';
|
||||
import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
|
||||
|
||||
import { DatasourceColumnsTable } from './datasource-columns-table/datasource-columns-table';
|
||||
import { DatasourcePreviewPane } from './datasource-preview-pane/datasource-preview-pane';
|
||||
|
||||
interface DatasourceTableActionDialogProps {
|
||||
datasourceId?: string;
|
||||
datasource: string;
|
||||
actions: BasicAction[];
|
||||
onClose: () => void;
|
||||
onClose(): void;
|
||||
}
|
||||
|
||||
export const DatasourceTableActionDialog = React.memo(function DatasourceTableActionDialog(
|
||||
props: DatasourceTableActionDialogProps,
|
||||
) {
|
||||
const { onClose, datasourceId, actions } = props;
|
||||
const [activeTab, setActiveTab] = useState('columns');
|
||||
const { datasource, actions, onClose } = props;
|
||||
const [activeTab, setActiveTab] = useState<'records' | 'columns'>('records');
|
||||
|
||||
const taskTableSideButtonMetadata: SideButtonMetaData[] = [
|
||||
const sideButtonMetadata: SideButtonMetaData[] = [
|
||||
{
|
||||
icon: 'list-columns',
|
||||
icon: IconNames.TH,
|
||||
text: 'Records',
|
||||
active: activeTab === 'records',
|
||||
onClick: () => setActiveTab('records'),
|
||||
},
|
||||
{
|
||||
icon: IconNames.LIST_COLUMNS,
|
||||
text: 'Columns',
|
||||
active: activeTab === 'columns',
|
||||
onClick: () => setActiveTab('columns'),
|
||||
|
@ -45,17 +54,13 @@ export const DatasourceTableActionDialog = React.memo(function DatasourceTableAc
|
|||
|
||||
return (
|
||||
<TableActionDialog
|
||||
sideButtonMetadata={taskTableSideButtonMetadata}
|
||||
sideButtonMetadata={sideButtonMetadata}
|
||||
onClose={onClose}
|
||||
title={`Datasource: ${datasourceId}`}
|
||||
title={`Datasource: ${datasource}`}
|
||||
actions={actions}
|
||||
>
|
||||
{activeTab === 'columns' && (
|
||||
<DatasourceColumnsTable
|
||||
datasourceId={datasourceId ? datasourceId : ''}
|
||||
downloadFilename={`datasource-dimensions-${datasourceId}.json`}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'records' && <DatasourcePreviewPane datasource={datasource} />}
|
||||
{activeTab === 'columns' && <DatasourceColumnsTable datasource={datasource} />}
|
||||
</TableActionDialog>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ import Hjson from 'hjson';
|
|||
import * as JSONBig from 'json-bigint-native';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { QueryContext } from '../../utils/query-context';
|
||||
import { QueryContext } from '../../druid-models';
|
||||
|
||||
import './edit-context-dialog.scss';
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import classNames from 'classnames';
|
|||
import * as JSONBig from 'json-bigint-native';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { ShowValue } from '../../components/show-value/show-value';
|
||||
import { ShowValue } from '../../components';
|
||||
import { DiffDialog } from '../diff-dialog/diff-dialog';
|
||||
|
||||
import './history-dialog.scss';
|
||||
|
|
|
@ -22,12 +22,15 @@ export * from './compaction-dialog/compaction-dialog';
|
|||
export * from './coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog';
|
||||
export * from './diff-dialog/diff-dialog';
|
||||
export * from './doctor-dialog/doctor-dialog';
|
||||
export * from './edit-context-dialog/edit-context-dialog';
|
||||
export * from './history-dialog/history-dialog';
|
||||
export * from './lookup-edit-dialog/lookup-edit-dialog';
|
||||
export * from './numeric-input-dialog/numeric-input-dialog';
|
||||
export * from './overlord-dynamic-config-dialog/overlord-dynamic-config-dialog';
|
||||
export * from './retention-dialog/retention-dialog';
|
||||
export * from './snitch-dialog/snitch-dialog';
|
||||
export * from './spec-dialog/spec-dialog';
|
||||
export * from './string-input-dialog/string-input-dialog';
|
||||
export * from './supervisor-table-action-dialog/supervisor-table-action-dialog';
|
||||
export * from './table-action-dialog/table-action-dialog';
|
||||
export * from './task-table-action-dialog/task-table-action-dialog';
|
||||
|
|
|
@ -18,10 +18,11 @@
|
|||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { LookupValuesTable } from '../../components/lookup-values-table/lookup-values-table';
|
||||
import { BasicAction } from '../../utils/basic-action';
|
||||
import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
|
||||
|
||||
import { LookupValuesTable } from './lookup-values-table/lookup-values-table';
|
||||
|
||||
interface LookupTableActionDialogProps {
|
||||
lookupId?: string;
|
||||
actions: BasicAction[];
|
||||
|
|
|
@ -20,10 +20,10 @@ import { SqlRef } from 'druid-query-toolkit';
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
|
||||
import { useQueryManager } from '../../hooks';
|
||||
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
|
||||
import { queryDruidSql } from '../../utils';
|
||||
import { Loader } from '../loader/loader';
|
||||
import { Loader } from '../../../components/loader/loader';
|
||||
import { useQueryManager } from '../../../hooks';
|
||||
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../../react-table';
|
||||
import { queryDruidSql } from '../../../utils';
|
||||
|
||||
import './lookup-values-table.scss';
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 { Button, Classes, Dialog, Intent, NumericInput } from '@blueprintjs/core';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const DEFAULT_MIN_VALUE = 1;
|
||||
|
||||
interface NumericInputDialogProps {
|
||||
title: string;
|
||||
message?: JSX.Element;
|
||||
minValue?: number;
|
||||
initValue: number;
|
||||
onSubmit(value: number): void;
|
||||
onClose(): void;
|
||||
}
|
||||
|
||||
export const NumericInputDialog = React.memo(function NumericInputDialog(
|
||||
props: NumericInputDialogProps,
|
||||
) {
|
||||
const { title, message, minValue, initValue, onSubmit, onClose } = props;
|
||||
|
||||
const [value, setValue] = useState<number>(initValue);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
className="numeric-input-dialog"
|
||||
onClose={onClose}
|
||||
isOpen
|
||||
title={title}
|
||||
canOutsideClickClose={false}
|
||||
>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
{message}
|
||||
<NumericInput
|
||||
value={value}
|
||||
onValueChange={(v: number) => {
|
||||
if (isNaN(v)) return;
|
||||
setValue(Math.max(v, DEFAULT_MIN_VALUE));
|
||||
}}
|
||||
min={minValue ?? DEFAULT_MIN_VALUE}
|
||||
stepSize={1}
|
||||
minorStepSize={null}
|
||||
majorStepSize={10}
|
||||
fill
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button text="Close" onClick={onClose} />
|
||||
<Button
|
||||
text="OK"
|
||||
intent={Intent.PRIMARY}
|
||||
onClick={() => {
|
||||
onSubmit(value);
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
});
|
|
@ -11,7 +11,7 @@ exports[`OverlordDynamicConfigDialog matches snapshot 1`] = `
|
|||
Edit the overlord dynamic configuration on the fly. For more information please refer to the
|
||||
|
||||
<Memo(ExternalLink)
|
||||
href="https://druid.apache.org/docs/0.23.0/configuration/index.html#overlord-dynamic-configuration"
|
||||
href="https://druid.apache.org/docs/latest/configuration/index.html#overlord-dynamic-configuration"
|
||||
>
|
||||
documentation
|
||||
</Memo(ExternalLink)>
|
||||
|
|
|
@ -63,7 +63,7 @@ exports[`RetentionDialog matches snapshot 1`] = `
|
|||
Druid uses rules to determine what data should be retained in the cluster. The rules are evaluated in order from top to bottom. For more information please refer to the
|
||||
|
||||
<a
|
||||
href="https://druid.apache.org/docs/0.23.0/operations/rule-configuration.html"
|
||||
href="https://druid.apache.org/docs/latest/operations/rule-configuration.html"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
|
|
|
@ -89,6 +89,33 @@ exports[`SegmentTableActionDialog matches snapshot 1`] = `
|
|||
Metadata
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="bp4-button bp4-minimal tab-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="bp4-icon bp4-icon-th"
|
||||
icon="th"
|
||||
>
|
||||
<svg
|
||||
data-icon="th"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
>
|
||||
<path
|
||||
d="M19 1H1c-.6 0-1 .5-1 1v16c0 .5.4 1 1 1h18c.5 0 1-.5 1-1V2c0-.5-.5-1-1-1zM7 17H2v-3h5v3zm0-4H2v-3h5v3zm0-4H2V6h5v3zm11 8H8v-3h10v3zm0-4H8v-3h10v3zm0-4H8V6h10v3z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="bp4-button-text"
|
||||
>
|
||||
Records
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="main-section"
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { ShowJson } from '../../components';
|
||||
|
@ -23,6 +24,8 @@ import { Api } from '../../singletons';
|
|||
import { BasicAction } from '../../utils/basic-action';
|
||||
import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
|
||||
|
||||
import { SegmentsPreviewPane } from './segments-preview-pane/segments-preview-pane';
|
||||
|
||||
interface SegmentTableActionDialogProps {
|
||||
segmentId: string;
|
||||
datasourceId: string;
|
||||
|
@ -38,11 +41,17 @@ export const SegmentTableActionDialog = React.memo(function SegmentTableActionDi
|
|||
|
||||
const taskTableSideButtonMetadata: SideButtonMetaData[] = [
|
||||
{
|
||||
icon: 'manually-entered-data',
|
||||
icon: IconNames.MANUALLY_ENTERED_DATA,
|
||||
text: 'Metadata',
|
||||
active: activeTab === 'metadata',
|
||||
onClick: () => setActiveTab('metadata'),
|
||||
},
|
||||
{
|
||||
icon: IconNames.TH,
|
||||
text: 'Records',
|
||||
active: activeTab === 'records',
|
||||
onClick: () => setActiveTab('records'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
@ -60,6 +69,7 @@ export const SegmentTableActionDialog = React.memo(function SegmentTableActionDi
|
|||
downloadFilename={`Segment-metadata-${segmentId}.json`}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'records' && <SegmentsPreviewPane segmentId={segmentId} />}
|
||||
</TableActionDialog>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { parseSegmentId } from './segments-preview-pane';
|
||||
|
||||
describe('parseSegmentId', () => {
|
||||
it('correctly identifies segment ID parts', () => {
|
||||
const segmentId =
|
||||
'kttm_reingest_2019-08-25T23:00:00.000Z_2019-08-26T00:00:00.000Z_2022-08-02T18:58:41.697Z';
|
||||
expect(parseSegmentId(segmentId).datasource).toEqual('kttm_reingest');
|
||||
expect(parseSegmentId(segmentId).interval).toEqual(
|
||||
'2019-08-25T23:00:00.000Z/2019-08-26T00:00:00.000Z',
|
||||
);
|
||||
expect(parseSegmentId(segmentId).version).toEqual('2022-08-02T18:58:41.697Z');
|
||||
expect(parseSegmentId(segmentId).partitionNumber).toEqual(0);
|
||||
});
|
||||
|
||||
it('correctly identifies segment ID parts with partitionNumber', () => {
|
||||
const segmentId =
|
||||
'test_segment_id1_2019-08-25T23:00:00.000Z_2019-08-26T00:00:00.000Z_2022-08-02T18:58:41.697Z_1';
|
||||
expect(parseSegmentId(segmentId).datasource).toEqual('test_segment_id1');
|
||||
expect(parseSegmentId(segmentId).interval).toEqual(
|
||||
'2019-08-25T23:00:00.000Z/2019-08-26T00:00:00.000Z',
|
||||
);
|
||||
expect(parseSegmentId(segmentId).version).toEqual('2022-08-02T18:58:41.697Z');
|
||||
expect(parseSegmentId(segmentId).partitionNumber).toEqual(1);
|
||||
});
|
||||
|
||||
it('correctly identifies segment ID parts with without partition number and _ in name', () => {
|
||||
const segmentId =
|
||||
'test___2019-08-25T23:00:00.000Z_2019-08-26T00:00:00.000Z_2022-08-02T18:58:41.697Z';
|
||||
expect(parseSegmentId(segmentId).datasource).toEqual('test__');
|
||||
expect(parseSegmentId(segmentId).interval).toEqual(
|
||||
'2019-08-25T23:00:00.000Z/2019-08-26T00:00:00.000Z',
|
||||
);
|
||||
expect(parseSegmentId(segmentId).version).toEqual('2022-08-02T18:58:41.697Z');
|
||||
expect(parseSegmentId(segmentId).partitionNumber).toEqual(0);
|
||||
});
|
||||
|
||||
it('correctly identifies segment ID parts with long partition number', () => {
|
||||
const segmentId =
|
||||
'test___2019-08-25T23:00:00.000Z_2019-08-26T00:00:00.000Z_2022-08-02T18:58:41.697Z_1234567';
|
||||
expect(parseSegmentId(segmentId).datasource).toEqual('test__');
|
||||
expect(parseSegmentId(segmentId).interval).toEqual(
|
||||
'2019-08-25T23:00:00.000Z/2019-08-26T00:00:00.000Z',
|
||||
);
|
||||
expect(parseSegmentId(segmentId).version).toEqual('2022-08-02T18:58:41.697Z');
|
||||
expect(parseSegmentId(segmentId).partitionNumber).toEqual(1234567);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.segments-preview-pane {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
.record-table-pane {
|
||||
height: 100%;
|
||||
}
|
||||
.segments-preview-error {
|
||||
color: #9e2b0e;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* 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 { QueryResult, QueryRunner } from 'druid-query-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
import { Loader, RecordTablePane } from '../../../components';
|
||||
// import { Loader, RecordTablePane } from '../../../components';
|
||||
import { useQueryManager } from '../../../hooks/use-query-manager';
|
||||
import { DruidError } from '../../../utils';
|
||||
|
||||
import './segments-preview-pane.scss';
|
||||
|
||||
const queryRunner = new QueryRunner({
|
||||
inflateDateStrategy: 'none',
|
||||
});
|
||||
|
||||
interface ParsedSegmentId {
|
||||
datasource: string;
|
||||
interval: string;
|
||||
partitionNumber: number;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export function parseSegmentId(segmentId: string): ParsedSegmentId {
|
||||
const segmentIdParts = segmentId.split('_');
|
||||
const tail = Number(segmentIdParts[segmentIdParts.length - 1]);
|
||||
let bump = 1;
|
||||
let partitionNumber = 0;
|
||||
|
||||
// Check if segmentId includes a partitionNumber
|
||||
if (!isNaN(tail)) {
|
||||
partitionNumber = tail;
|
||||
bump++;
|
||||
}
|
||||
|
||||
const version = segmentIdParts[segmentIdParts.length - bump];
|
||||
const interval =
|
||||
segmentIdParts[segmentIdParts.length - bump - 2] +
|
||||
'/' +
|
||||
segmentIdParts[segmentIdParts.length - bump - 1];
|
||||
const datasource = segmentIdParts.slice(0, segmentIdParts.length - bump - 2).join('_');
|
||||
|
||||
return {
|
||||
datasource: datasource,
|
||||
version: version,
|
||||
interval: interval,
|
||||
partitionNumber: partitionNumber,
|
||||
};
|
||||
}
|
||||
|
||||
export interface DatasourcePreviewPaneProps {
|
||||
segmentId: string;
|
||||
}
|
||||
|
||||
export const SegmentsPreviewPane = React.memo(function DatasourcePreviewPane(
|
||||
props: DatasourcePreviewPaneProps,
|
||||
) {
|
||||
const segmentIdParts = parseSegmentId(props.segmentId);
|
||||
|
||||
const [recordState] = useQueryManager<string, QueryResult>({
|
||||
initQuery: segmentIdParts.datasource,
|
||||
processQuery: async (datasource, cancelToken) => {
|
||||
let result: QueryResult;
|
||||
try {
|
||||
result = await queryRunner.runQuery({
|
||||
query: {
|
||||
queryType: 'scan',
|
||||
dataSource: datasource,
|
||||
intervals: {
|
||||
type: 'segments',
|
||||
segments: [
|
||||
{
|
||||
itvl: segmentIdParts.interval,
|
||||
ver: segmentIdParts.version,
|
||||
part: segmentIdParts.partitionNumber,
|
||||
},
|
||||
],
|
||||
},
|
||||
resultFormat: 'compactedList',
|
||||
limit: 1001,
|
||||
columns: [],
|
||||
granularity: 'all',
|
||||
},
|
||||
extraQueryContext: { sqlOuterLimit: 100 },
|
||||
cancelToken,
|
||||
});
|
||||
} catch (e) {
|
||||
throw new DruidError(e);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="segments-preview-pane">
|
||||
{recordState.loading && <Loader />}
|
||||
{recordState.data && <RecordTablePane queryResult={recordState.data} />}
|
||||
{recordState.error && (
|
||||
<div className="segments-preview-error">{recordState.error.message}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
|
@ -19,7 +19,7 @@
|
|||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { anywhereMatcher, StatusDialog } from './status-dialog';
|
||||
import { StatusDialog } from './status-dialog';
|
||||
|
||||
describe('StatusDialog', () => {
|
||||
it('matches snapshot', () => {
|
||||
|
@ -27,18 +27,4 @@ describe('StatusDialog', () => {
|
|||
render(statusDialog);
|
||||
expect(document.body.lastChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('filters data that contains input', () => {
|
||||
const row = [
|
||||
'org.apache.druid.common.gcp.GcpModule',
|
||||
'org.apache.druid.common.aws.AWSModule',
|
||||
'org.apache.druid.OtherModule',
|
||||
];
|
||||
|
||||
expect(anywhereMatcher({ id: '0', value: 'common' }, row)).toEqual(true);
|
||||
expect(anywhereMatcher({ id: '1', value: 'common' }, row)).toEqual(true);
|
||||
expect(anywhereMatcher({ id: '0', value: 'org' }, row)).toEqual(true);
|
||||
expect(anywhereMatcher({ id: '1', value: 'org' }, row)).toEqual(true);
|
||||
expect(anywhereMatcher({ id: '2', value: 'common' }, row)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,20 +17,16 @@
|
|||
*/
|
||||
|
||||
import { Button, Classes, Dialog, Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import ReactTable, { Filter } from 'react-table';
|
||||
|
||||
import { Loader } from '../../components';
|
||||
import { Loader, TableFilterableCell } from '../../components';
|
||||
import { useQueryManager } from '../../hooks';
|
||||
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
|
||||
import { Api, UrlBaser } from '../../singletons';
|
||||
|
||||
import './status-dialog.scss';
|
||||
|
||||
export function anywhereMatcher(filter: Filter, row: any): boolean {
|
||||
return String(row[filter.id]).includes(filter.value);
|
||||
}
|
||||
|
||||
interface StatusModule {
|
||||
artifact: string;
|
||||
name: string;
|
||||
|
@ -43,64 +39,78 @@ interface StatusResponse {
|
|||
}
|
||||
|
||||
interface StatusDialogProps {
|
||||
onClose: () => void;
|
||||
onClose(): void;
|
||||
}
|
||||
|
||||
export const StatusDialog = React.memo(function StatusDialog(props: StatusDialogProps) {
|
||||
const { onClose } = props;
|
||||
const [moduleFilter, setModuleFilter] = useState<Filter[]>([]);
|
||||
|
||||
const [responseState] = useQueryManager<null, StatusResponse>({
|
||||
initQuery: null,
|
||||
processQuery: async () => {
|
||||
const resp = await Api.instance.get(`/status`);
|
||||
return resp.data;
|
||||
},
|
||||
initQuery: null,
|
||||
});
|
||||
|
||||
function renderContent(): JSX.Element | undefined {
|
||||
if (responseState.loading) return <Loader />;
|
||||
|
||||
if (responseState.error) {
|
||||
return <span>{`Error while loading status: ${responseState.error}`}</span>;
|
||||
return <div>{`Error while loading status: ${responseState.error}`}</div>;
|
||||
}
|
||||
|
||||
const response = responseState.data;
|
||||
if (!response) return;
|
||||
|
||||
const renderModuleFilterableCell = (field: string) => {
|
||||
return function ModuleFilterableCell(row: { value: any }) {
|
||||
return (
|
||||
<TableFilterableCell
|
||||
field={field}
|
||||
value={row.value}
|
||||
filters={moduleFilter}
|
||||
onFiltersChange={setModuleFilter}
|
||||
>
|
||||
{row.value}
|
||||
</TableFilterableCell>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="main-container">
|
||||
<div className="version">
|
||||
Version: <strong>{response.version}</strong>
|
||||
Version: <strong>{response.version}</strong>
|
||||
</div>
|
||||
<ReactTable
|
||||
data={response.modules}
|
||||
loading={responseState.loading}
|
||||
filterable
|
||||
defaultFilterMethod={anywhereMatcher}
|
||||
filtered={moduleFilter}
|
||||
onFilteredChange={setModuleFilter}
|
||||
defaultPageSize={SMALL_TABLE_PAGE_SIZE}
|
||||
pageSizeOptions={SMALL_TABLE_PAGE_SIZE_OPTIONS}
|
||||
showPagination={response.modules.length > SMALL_TABLE_PAGE_SIZE}
|
||||
columns={[
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
Header: 'Extension name',
|
||||
accessor: 'artifact',
|
||||
width: 200,
|
||||
className: 'padded',
|
||||
},
|
||||
{
|
||||
Header: 'Version',
|
||||
accessor: 'version',
|
||||
width: 200,
|
||||
className: 'padded',
|
||||
},
|
||||
{
|
||||
Header: 'Fully qualified name',
|
||||
accessor: 'name',
|
||||
width: 500,
|
||||
className: 'padded',
|
||||
},
|
||||
],
|
||||
Header: 'Extension name',
|
||||
accessor: 'artifact',
|
||||
width: 200,
|
||||
Cell: renderModuleFilterableCell('artifact'),
|
||||
},
|
||||
{
|
||||
Header: 'Version',
|
||||
accessor: 'version',
|
||||
width: 200,
|
||||
Cell: renderModuleFilterableCell('version'),
|
||||
},
|
||||
{
|
||||
Header: 'Fully qualified name',
|
||||
accessor: 'name',
|
||||
width: 500,
|
||||
Cell: renderModuleFilterableCell('name'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { Button, Classes, Dialog, InputGroup, Intent } from '@blueprintjs/core';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export interface StringInputDialogProps {
|
||||
title: string;
|
||||
initValue?: string;
|
||||
placeholder?: string;
|
||||
maxLength?: number;
|
||||
onSubmit(str: string): void;
|
||||
onClose(): void;
|
||||
}
|
||||
|
||||
export const StringInputDialog = React.memo(function StringSubmitDialog(
|
||||
props: StringInputDialogProps,
|
||||
) {
|
||||
const { title, initValue, placeholder, maxLength, onSubmit, onClose } = props;
|
||||
|
||||
const [value, setValue] = useState(initValue || '');
|
||||
|
||||
function handleSubmit() {
|
||||
onSubmit(value);
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog className="string-input-dialog" isOpen onClose={onClose} title={title}>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<InputGroup
|
||||
value={value}
|
||||
onChange={e => setValue(String(e.target.value).substring(0, maxLength || 280))}
|
||||
autoFocus
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button text="Close" onClick={onClose} />
|
||||
<Button text="Submit" intent={Intent.PRIMARY} onClick={handleSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
});
|
|
@ -19,7 +19,7 @@
|
|||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { QueryState } from '../../utils';
|
||||
import { QueryState } from '../../../utils';
|
||||
|
||||
import {
|
||||
normalizeSupervisorStatisticsResults,
|
||||
|
@ -28,7 +28,7 @@ import {
|
|||
} from './supervisor-statistics-table';
|
||||
|
||||
let supervisorStatisticsState: QueryState<SupervisorStatisticsTableRow[]> = QueryState.INIT;
|
||||
jest.mock('../../hooks', () => {
|
||||
jest.mock('../../../hooks', () => {
|
||||
return {
|
||||
useQueryManager: () => [supervisorStatisticsState],
|
||||
};
|
|
@ -20,11 +20,11 @@ import { Button, ButtonGroup } from '@blueprintjs/core';
|
|||
import React from 'react';
|
||||
import ReactTable, { CellInfo, Column } from 'react-table';
|
||||
|
||||
import { useQueryManager } from '../../hooks';
|
||||
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
|
||||
import { Api, UrlBaser } from '../../singletons';
|
||||
import { deepGet } from '../../utils';
|
||||
import { Loader } from '../loader/loader';
|
||||
import { Loader } from '../../../components/loader/loader';
|
||||
import { useQueryManager } from '../../../hooks';
|
||||
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../../react-table';
|
||||
import { Api, UrlBaser } from '../../../singletons';
|
||||
import { deepGet } from '../../../utils';
|
||||
|
||||
import './supervisor-statistics-table.scss';
|
||||
|
|
@ -20,13 +20,14 @@ import React, { useState } from 'react';
|
|||
|
||||
import { ShowJson } from '../../components';
|
||||
import { ShowHistory } from '../../components/show-history/show-history';
|
||||
import { SupervisorStatisticsTable } from '../../components/supervisor-statistics-table/supervisor-statistics-table';
|
||||
import { cleanSpec } from '../../druid-models';
|
||||
import { Api } from '../../singletons';
|
||||
import { deepGet } from '../../utils';
|
||||
import { BasicAction } from '../../utils/basic-action';
|
||||
import { SideButtonMetaData, TableActionDialog } from '../table-action-dialog/table-action-dialog';
|
||||
|
||||
import { SupervisorStatisticsTable } from './supervisor-statistics-table/supervisor-statistics-table';
|
||||
|
||||
interface SupervisorTableActionDialogProps {
|
||||
supervisorId: string;
|
||||
actions: BasicAction[];
|
||||
|
|
|
@ -53,7 +53,7 @@ export const TableActionDialog = React.memo(function TableActionDialog(
|
|||
{sideButtonMetadata.map((d, i) => (
|
||||
<Button
|
||||
className="tab-button"
|
||||
icon={<Icon icon={d.icon} iconSize={20} />}
|
||||
icon={<Icon icon={d.icon} size={20} />}
|
||||
key={i}
|
||||
text={d.text}
|
||||
intent={d.active ? Intent.PRIMARY : Intent.NONE}
|
||||
|
|
|
@ -75,21 +75,21 @@ export const TaskTableActionDialog = React.memo(function TaskTableActionDialog(
|
|||
{activeTab === 'status' && (
|
||||
<ShowJson
|
||||
endpoint={`${taskEndpointBase}/status`}
|
||||
transform={x => deepGet(x, 'status')}
|
||||
transform={x => deepGet(x, 'status') || x}
|
||||
downloadFilename={`task-status-${taskId}.json`}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'payload' && (
|
||||
<ShowJson
|
||||
endpoint={taskEndpointBase}
|
||||
transform={x => deepGet(x, 'payload')}
|
||||
transform={x => deepGet(x, 'payload') || x}
|
||||
downloadFilename={`task-payload-${taskId}.json`}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'reports' && (
|
||||
<ShowJson
|
||||
endpoint={`${taskEndpointBase}/reports`}
|
||||
transform={x => deepGet(x, 'ingestionStatsAndErrors.payload')}
|
||||
transform={x => deepGet(x, 'ingestionStatsAndErrors.payload') || x}
|
||||
downloadFilename={`task-reports-${taskId}.json`}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
import { Code } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
|
||||
import { Field } from '../components';
|
||||
import { deepGet, deepSet, oneOf } from '../utils';
|
||||
import { Field } from '../../components';
|
||||
import { deepGet, deepSet, oneOf } from '../../utils';
|
||||
|
||||
export type CompactionConfig = Record<string, any>;
|
||||
|
||||
|
@ -245,6 +245,23 @@ export const COMPACTION_CONFIG_FIELDS: Field<CompactionConfig>[] = [
|
|||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'tuningConfig.maxColumnsToMerge',
|
||||
type: 'number',
|
||||
defaultValue: -1,
|
||||
min: -1,
|
||||
info: (
|
||||
<>
|
||||
<p>
|
||||
Limit of the number of segments to merge in a single phase when merging segments for
|
||||
publishing. This limit affects the total number of columns present in a set of segments to
|
||||
merge. If the limit is exceeded, segment merging occurs in multiple phases. Druid merges
|
||||
at least 2 segments per phase, regardless of this setting.
|
||||
</p>
|
||||
<p>Default: -1 (unlimited)</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'tuningConfig.totalNumMergeTasks',
|
||||
type: 'number',
|
|
@ -16,7 +16,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CompactionConfig } from './compaction-config';
|
||||
import { CompactionConfig } from '../compaction-config/compaction-config';
|
||||
|
||||
import {
|
||||
CompactionStatus,
|
||||
formatCompactionConfigAndStatus,
|
|
@ -16,7 +16,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CompactionConfig } from './compaction-config';
|
||||
import { CompactionConfig } from '../compaction-config/compaction-config';
|
||||
|
||||
function capitalizeFirst(str: string): string {
|
||||
return str.slice(0, 1).toUpperCase() + str.slice(1).toLowerCase();
|
|
@ -19,7 +19,7 @@
|
|||
import { Code } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
|
||||
import { Field } from '../components';
|
||||
import { Field } from '../../components';
|
||||
|
||||
export interface CoordinatorDynamicConfig {
|
||||
maxSegmentsToMove?: number;
|
|
@ -16,8 +16,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CSV_SAMPLE, JSON_SAMPLE } from '../../utils/sampler.mock';
|
||||
|
||||
import { getDimensionSpecs } from './dimension-spec';
|
||||
import { CSV_SAMPLE, JSON_SAMPLE } from './test-fixtures';
|
||||
|
||||
describe('dimension-spec', () => {
|
||||
describe('getDimensionSpecs', () => {
|
|
@ -16,11 +16,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Field } from '../components';
|
||||
import { filterMap, typeIs } from '../utils';
|
||||
import { SampleHeaderAndRows } from '../utils/sampler';
|
||||
|
||||
import { guessColumnTypeFromHeaderAndRows } from './ingestion-spec';
|
||||
import { Field } from '../../components';
|
||||
import { filterMap, typeIs } from '../../utils';
|
||||
import { SampleHeaderAndRows } from '../../utils/sampler';
|
||||
import { guessColumnTypeFromHeaderAndRows } from '../ingestion-spec/ingestion-spec';
|
||||
|
||||
export interface DimensionsSpec {
|
||||
readonly dimensions?: (string | DimensionSpec)[];
|
||||
|
@ -46,7 +45,7 @@ export const DIMENSION_SPEC_FIELDS: Field<DimensionSpec>[] = [
|
|||
name: 'type',
|
||||
type: 'string',
|
||||
required: true,
|
||||
suggestions: ['string', 'long', 'float', 'double'],
|
||||
suggestions: ['string', 'long', 'float', 'double', 'json'],
|
||||
},
|
||||
{
|
||||
name: 'createBitmapIndex',
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export type DruidEngine = 'native' | 'sql-native' | 'sql-msq-task';
|
||||
|
||||
export const DRUID_ENGINES: DruidEngine[] = ['native', 'sql-native', 'sql-msq-task'];
|
||||
|
||||
export function validDruidEngine(
|
||||
possibleDruidEngine: string | undefined,
|
||||
): possibleDruidEngine is DruidEngine {
|
||||
return Boolean(possibleDruidEngine && DRUID_ENGINES.includes(possibleDruidEngine as DruidEngine));
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* 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 { Execution } from './execution';
|
||||
|
||||
/*
|
||||
For query:
|
||||
|
||||
REPLACE INTO "kttm_simple" OVERWRITE ALL
|
||||
SELECT TIME_PARSE("timestamp") AS "__time", agent_type
|
||||
FROM TABLE(
|
||||
EXTERN(
|
||||
'{"type":"http","uris":["https://static.imply.io/example-data/kttm-v2/kttm-v2-2019-08-25.json.gz"]}',
|
||||
'{"type":"json"}',
|
||||
'[{"name":"timestamp","type":"string"},{"name":"agent_type","type":"string"}]'
|
||||
)
|
||||
)
|
||||
PARTITIONED BY ALL TIME
|
||||
*/
|
||||
|
||||
export const EXECUTION_INGEST_COMPLETE = Execution.fromTaskPayloadAndReport(
|
||||
{
|
||||
task: 'query-32ced762-7679-4a25-9220-3915c5976961',
|
||||
payload: {
|
||||
type: 'query_controller',
|
||||
id: 'query-32ced762-7679-4a25-9220-3915c5976961',
|
||||
spec: {
|
||||
query: {
|
||||
queryType: 'scan',
|
||||
dataSource: {
|
||||
type: 'external',
|
||||
inputSource: {
|
||||
type: 'http',
|
||||
uris: ['https://static.imply.io/example-data/kttm-v2/kttm-v2-2019-08-25.json.gz'],
|
||||
httpAuthenticationUsername: null,
|
||||
httpAuthenticationPassword: null,
|
||||
},
|
||||
inputFormat: {
|
||||
type: 'json',
|
||||
flattenSpec: null,
|
||||
featureSpec: {},
|
||||
keepNullColumns: false,
|
||||
},
|
||||
signature: [
|
||||
{ name: 'timestamp', type: 'STRING' },
|
||||
{ name: 'agent_type', type: 'STRING' },
|
||||
],
|
||||
},
|
||||
intervals: {
|
||||
type: 'intervals',
|
||||
intervals: ['-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z'],
|
||||
},
|
||||
virtualColumns: [
|
||||
{
|
||||
type: 'expression',
|
||||
name: 'v0',
|
||||
expression: 'timestamp_parse("timestamp",null,\'UTC\')',
|
||||
outputType: 'LONG',
|
||||
},
|
||||
],
|
||||
resultFormat: 'compactedList',
|
||||
columns: ['agent_type', 'v0'],
|
||||
legacy: false,
|
||||
context: {
|
||||
finalize: false,
|
||||
finalizeAggregations: false,
|
||||
groupByEnableMultiValueUnnesting: false,
|
||||
scanSignature: '[{"name":"agent_type","type":"STRING"},{"name":"v0","type":"LONG"}]',
|
||||
sqlInsertSegmentGranularity: '{"type":"all"}',
|
||||
sqlQueryId: '32ced762-7679-4a25-9220-3915c5976961',
|
||||
sqlReplaceTimeChunks: 'all',
|
||||
},
|
||||
granularity: { type: 'all' },
|
||||
},
|
||||
columnMappings: [
|
||||
{ queryColumn: 'v0', outputColumn: '__time' },
|
||||
{ queryColumn: 'agent_type', outputColumn: 'agent_type' },
|
||||
],
|
||||
destination: {
|
||||
type: 'dataSource',
|
||||
dataSource: 'kttm_simple',
|
||||
segmentGranularity: { type: 'all' },
|
||||
replaceTimeChunks: ['-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z'],
|
||||
},
|
||||
assignmentStrategy: 'max',
|
||||
tuningConfig: { maxNumWorkers: 1, maxRowsInMemory: 100000, rowsPerSegment: 3000000 },
|
||||
},
|
||||
sqlQuery:
|
||||
'REPLACE INTO "kttm_simple" OVERWRITE ALL\nSELECT TIME_PARSE("timestamp") AS "__time", agent_type\nFROM TABLE(\n EXTERN(\n \'{"type":"http","uris":["https://static.imply.io/example-data/kttm-v2/kttm-v2-2019-08-25.json.gz"]}\',\n \'{"type":"json"}\',\n \'[{"name":"timestamp","type":"string"},{"name":"agent_type","type":"string"}]\'\n )\n)\nPARTITIONED BY ALL TIME',
|
||||
sqlQueryContext: {
|
||||
finalizeAggregations: false,
|
||||
groupByEnableMultiValueUnnesting: false,
|
||||
maxParseExceptions: 0,
|
||||
sqlInsertSegmentGranularity: '{"type":"all"}',
|
||||
sqlQueryId: '32ced762-7679-4a25-9220-3915c5976961',
|
||||
sqlReplaceTimeChunks: 'all',
|
||||
},
|
||||
sqlTypeNames: ['TIMESTAMP', 'VARCHAR'],
|
||||
context: { forceTimeChunkLock: true, useLineageBasedSegmentAllocation: true },
|
||||
groupId: 'query-32ced762-7679-4a25-9220-3915c5976961',
|
||||
dataSource: 'kttm_simple',
|
||||
resource: {
|
||||
availabilityGroup: 'query-32ced762-7679-4a25-9220-3915c5976961',
|
||||
requiredCapacity: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
multiStageQuery: {
|
||||
taskId: 'query-32ced762-7679-4a25-9220-3915c5976961',
|
||||
payload: {
|
||||
status: { status: 'SUCCESS', startTime: '2022-08-22T20:12:51.391Z', durationMs: 25097 },
|
||||
stages: [
|
||||
{
|
||||
stageNumber: 0,
|
||||
definition: {
|
||||
id: '0b353011-6ea1-480a-8ca8-386771621672_0',
|
||||
input: [
|
||||
{
|
||||
type: 'external',
|
||||
inputSource: {
|
||||
type: 'http',
|
||||
uris: [
|
||||
'https://static.imply.io/example-data/kttm-v2/kttm-v2-2019-08-25.json.gz',
|
||||
],
|
||||
httpAuthenticationUsername: null,
|
||||
httpAuthenticationPassword: null,
|
||||
},
|
||||
inputFormat: {
|
||||
type: 'json',
|
||||
flattenSpec: null,
|
||||
featureSpec: {},
|
||||
keepNullColumns: false,
|
||||
},
|
||||
signature: [
|
||||
{ name: 'timestamp', type: 'STRING' },
|
||||
{ name: 'agent_type', type: 'STRING' },
|
||||
],
|
||||
},
|
||||
],
|
||||
processor: {
|
||||
type: 'scan',
|
||||
query: {
|
||||
queryType: 'scan',
|
||||
dataSource: { type: 'inputNumber', inputNumber: 0 },
|
||||
intervals: {
|
||||
type: 'intervals',
|
||||
intervals: ['-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z'],
|
||||
},
|
||||
virtualColumns: [
|
||||
{
|
||||
type: 'expression',
|
||||
name: 'v0',
|
||||
expression: 'timestamp_parse("timestamp",null,\'UTC\')',
|
||||
outputType: 'LONG',
|
||||
},
|
||||
],
|
||||
resultFormat: 'compactedList',
|
||||
columns: ['agent_type', 'v0'],
|
||||
legacy: false,
|
||||
context: {
|
||||
__timeColumn: 'v0',
|
||||
finalize: false,
|
||||
finalizeAggregations: false,
|
||||
groupByEnableMultiValueUnnesting: false,
|
||||
scanSignature:
|
||||
'[{"name":"agent_type","type":"STRING"},{"name":"v0","type":"LONG"}]',
|
||||
sqlInsertSegmentGranularity: '{"type":"all"}',
|
||||
sqlQueryId: '32ced762-7679-4a25-9220-3915c5976961',
|
||||
sqlReplaceTimeChunks: 'all',
|
||||
},
|
||||
granularity: { type: 'all' },
|
||||
},
|
||||
},
|
||||
signature: [
|
||||
{ name: '__boost', type: 'LONG' },
|
||||
{ name: 'agent_type', type: 'STRING' },
|
||||
{ name: 'v0', type: 'LONG' },
|
||||
],
|
||||
shuffleSpec: {
|
||||
type: 'targetSize',
|
||||
clusterBy: { columns: [{ columnName: '__boost' }] },
|
||||
targetSize: 3000000,
|
||||
},
|
||||
maxWorkerCount: 1,
|
||||
shuffleCheckHasMultipleValues: true,
|
||||
},
|
||||
phase: 'FINISHED',
|
||||
workerCount: 1,
|
||||
partitionCount: 1,
|
||||
startTime: '2022-08-22T20:12:53.790Z',
|
||||
duration: 20229,
|
||||
sort: true,
|
||||
},
|
||||
{
|
||||
stageNumber: 1,
|
||||
definition: {
|
||||
id: '0b353011-6ea1-480a-8ca8-386771621672_1',
|
||||
input: [{ type: 'stage', stage: 0 }],
|
||||
processor: {
|
||||
type: 'segmentGenerator',
|
||||
dataSchema: {
|
||||
dataSource: 'kttm_simple',
|
||||
timestampSpec: { column: '__time', format: 'millis', missingValue: null },
|
||||
dimensionsSpec: {
|
||||
dimensions: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'agent_type',
|
||||
multiValueHandling: 'SORTED_ARRAY',
|
||||
createBitmapIndex: true,
|
||||
},
|
||||
],
|
||||
dimensionExclusions: ['__time'],
|
||||
includeAllDimensions: false,
|
||||
},
|
||||
metricsSpec: [],
|
||||
granularitySpec: {
|
||||
type: 'arbitrary',
|
||||
queryGranularity: { type: 'none' },
|
||||
rollup: false,
|
||||
intervals: ['-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z'],
|
||||
},
|
||||
transformSpec: { filter: null, transforms: [] },
|
||||
},
|
||||
columnMappings: [
|
||||
{ queryColumn: 'v0', outputColumn: '__time' },
|
||||
{ queryColumn: 'agent_type', outputColumn: 'agent_type' },
|
||||
],
|
||||
tuningConfig: {
|
||||
maxNumWorkers: 1,
|
||||
maxRowsInMemory: 100000,
|
||||
rowsPerSegment: 3000000,
|
||||
},
|
||||
},
|
||||
signature: [],
|
||||
maxWorkerCount: 1,
|
||||
},
|
||||
phase: 'FINISHED',
|
||||
workerCount: 1,
|
||||
partitionCount: 1,
|
||||
startTime: '2022-08-22T20:13:13.991Z',
|
||||
duration: 2497,
|
||||
},
|
||||
],
|
||||
counters: {
|
||||
'0': {
|
||||
'0': {
|
||||
input0: { type: 'channel', rows: [465346], files: [1], totalFiles: [1] },
|
||||
output: { type: 'channel', rows: [465346], bytes: [25430674], frames: [4] },
|
||||
shuffle: { type: 'channel', rows: [465346], bytes: [23570446], frames: [38] },
|
||||
sortProgress: {
|
||||
type: 'sortProgress',
|
||||
totalMergingLevels: 3,
|
||||
levelToTotalBatches: { '0': 1, '1': 1, '2': 1 },
|
||||
levelToMergedBatches: { '0': 1, '1': 1, '2': 1 },
|
||||
totalMergersForUltimateLevel: 1,
|
||||
progressDigest: 1.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'1': {
|
||||
'0': { input0: { type: 'channel', rows: [465346], bytes: [23570446], frames: [38] } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,530 @@
|
|||
/*
|
||||
* 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 { Execution } from './execution';
|
||||
import { EXECUTION_INGEST_COMPLETE } from './execution-ingest-complete.mock';
|
||||
|
||||
describe('Execution', () => {
|
||||
describe('.fromTaskDetail', () => {
|
||||
it('fails for bad status (error: null)', () => {
|
||||
expect(() =>
|
||||
Execution.fromTaskPayloadAndReport(
|
||||
{} as any,
|
||||
{
|
||||
asyncResultId: 'multi-stage-query-sql-1392d806-c17f-4937-94ee-8fa0a3ce1566',
|
||||
error: null,
|
||||
} as any,
|
||||
),
|
||||
).toThrowError('Invalid payload');
|
||||
});
|
||||
|
||||
it('works in a general case', () => {
|
||||
expect(EXECUTION_INGEST_COMPLETE).toMatchInlineSnapshot(`
|
||||
Execution {
|
||||
"_payload": Object {
|
||||
"payload": Object {
|
||||
"context": Object {
|
||||
"forceTimeChunkLock": true,
|
||||
"useLineageBasedSegmentAllocation": true,
|
||||
},
|
||||
"dataSource": "kttm_simple",
|
||||
"groupId": "query-32ced762-7679-4a25-9220-3915c5976961",
|
||||
"id": "query-32ced762-7679-4a25-9220-3915c5976961",
|
||||
"resource": Object {
|
||||
"availabilityGroup": "query-32ced762-7679-4a25-9220-3915c5976961",
|
||||
"requiredCapacity": 1,
|
||||
},
|
||||
"spec": Object {
|
||||
"assignmentStrategy": "max",
|
||||
"columnMappings": Array [
|
||||
Object {
|
||||
"outputColumn": "__time",
|
||||
"queryColumn": "v0",
|
||||
},
|
||||
Object {
|
||||
"outputColumn": "agent_type",
|
||||
"queryColumn": "agent_type",
|
||||
},
|
||||
],
|
||||
"destination": Object {
|
||||
"dataSource": "kttm_simple",
|
||||
"replaceTimeChunks": Array [
|
||||
"-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z",
|
||||
],
|
||||
"segmentGranularity": Object {
|
||||
"type": "all",
|
||||
},
|
||||
"type": "dataSource",
|
||||
},
|
||||
"query": Object {
|
||||
"columns": Array [
|
||||
"agent_type",
|
||||
"v0",
|
||||
],
|
||||
"context": Object {
|
||||
"finalize": false,
|
||||
"finalizeAggregations": false,
|
||||
"groupByEnableMultiValueUnnesting": false,
|
||||
"scanSignature": "[{\\"name\\":\\"agent_type\\",\\"type\\":\\"STRING\\"},{\\"name\\":\\"v0\\",\\"type\\":\\"LONG\\"}]",
|
||||
"sqlInsertSegmentGranularity": "{\\"type\\":\\"all\\"}",
|
||||
"sqlQueryId": "32ced762-7679-4a25-9220-3915c5976961",
|
||||
"sqlReplaceTimeChunks": "all",
|
||||
},
|
||||
"dataSource": Object {
|
||||
"inputFormat": Object {
|
||||
"featureSpec": Object {},
|
||||
"flattenSpec": null,
|
||||
"keepNullColumns": false,
|
||||
"type": "json",
|
||||
},
|
||||
"inputSource": Object {
|
||||
"httpAuthenticationPassword": null,
|
||||
"httpAuthenticationUsername": null,
|
||||
"type": "http",
|
||||
"uris": Array [
|
||||
"https://static.imply.io/example-data/kttm-v2/kttm-v2-2019-08-25.json.gz",
|
||||
],
|
||||
},
|
||||
"signature": Array [
|
||||
Object {
|
||||
"name": "timestamp",
|
||||
"type": "STRING",
|
||||
},
|
||||
Object {
|
||||
"name": "agent_type",
|
||||
"type": "STRING",
|
||||
},
|
||||
],
|
||||
"type": "external",
|
||||
},
|
||||
"granularity": Object {
|
||||
"type": "all",
|
||||
},
|
||||
"intervals": Object {
|
||||
"intervals": Array [
|
||||
"-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z",
|
||||
],
|
||||
"type": "intervals",
|
||||
},
|
||||
"legacy": false,
|
||||
"queryType": "scan",
|
||||
"resultFormat": "compactedList",
|
||||
"virtualColumns": Array [
|
||||
Object {
|
||||
"expression": "timestamp_parse(\\"timestamp\\",null,'UTC')",
|
||||
"name": "v0",
|
||||
"outputType": "LONG",
|
||||
"type": "expression",
|
||||
},
|
||||
],
|
||||
},
|
||||
"tuningConfig": Object {
|
||||
"maxNumWorkers": 1,
|
||||
"maxRowsInMemory": 100000,
|
||||
"rowsPerSegment": 3000000,
|
||||
},
|
||||
},
|
||||
"sqlQuery": "REPLACE INTO \\"kttm_simple\\" OVERWRITE ALL
|
||||
SELECT TIME_PARSE(\\"timestamp\\") AS \\"__time\\", agent_type
|
||||
FROM TABLE(
|
||||
EXTERN(
|
||||
'{\\"type\\":\\"http\\",\\"uris\\":[\\"https://static.imply.io/example-data/kttm-v2/kttm-v2-2019-08-25.json.gz\\"]}',
|
||||
'{\\"type\\":\\"json\\"}',
|
||||
'[{\\"name\\":\\"timestamp\\",\\"type\\":\\"string\\"},{\\"name\\":\\"agent_type\\",\\"type\\":\\"string\\"}]'
|
||||
)
|
||||
)
|
||||
PARTITIONED BY ALL TIME",
|
||||
"sqlQueryContext": Object {
|
||||
"finalizeAggregations": false,
|
||||
"groupByEnableMultiValueUnnesting": false,
|
||||
"maxParseExceptions": 0,
|
||||
"sqlInsertSegmentGranularity": "{\\"type\\":\\"all\\"}",
|
||||
"sqlQueryId": "32ced762-7679-4a25-9220-3915c5976961",
|
||||
"sqlReplaceTimeChunks": "all",
|
||||
},
|
||||
"sqlTypeNames": Array [
|
||||
"TIMESTAMP",
|
||||
"VARCHAR",
|
||||
],
|
||||
"type": "query_controller",
|
||||
},
|
||||
"task": "query-32ced762-7679-4a25-9220-3915c5976961",
|
||||
},
|
||||
"destination": Object {
|
||||
"dataSource": "kttm_simple",
|
||||
"replaceTimeChunks": Array [
|
||||
"-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z",
|
||||
],
|
||||
"segmentGranularity": Object {
|
||||
"type": "all",
|
||||
},
|
||||
"type": "dataSource",
|
||||
},
|
||||
"duration": 25097,
|
||||
"engine": "sql-msq-task",
|
||||
"error": undefined,
|
||||
"id": "query-32ced762-7679-4a25-9220-3915c5976961",
|
||||
"nativeQuery": Object {
|
||||
"columns": Array [
|
||||
"agent_type",
|
||||
"v0",
|
||||
],
|
||||
"context": Object {
|
||||
"finalize": false,
|
||||
"finalizeAggregations": false,
|
||||
"groupByEnableMultiValueUnnesting": false,
|
||||
"scanSignature": "[{\\"name\\":\\"agent_type\\",\\"type\\":\\"STRING\\"},{\\"name\\":\\"v0\\",\\"type\\":\\"LONG\\"}]",
|
||||
"sqlInsertSegmentGranularity": "{\\"type\\":\\"all\\"}",
|
||||
"sqlQueryId": "32ced762-7679-4a25-9220-3915c5976961",
|
||||
"sqlReplaceTimeChunks": "all",
|
||||
},
|
||||
"dataSource": Object {
|
||||
"inputFormat": Object {
|
||||
"featureSpec": Object {},
|
||||
"flattenSpec": null,
|
||||
"keepNullColumns": false,
|
||||
"type": "json",
|
||||
},
|
||||
"inputSource": Object {
|
||||
"httpAuthenticationPassword": null,
|
||||
"httpAuthenticationUsername": null,
|
||||
"type": "http",
|
||||
"uris": Array [
|
||||
"https://static.imply.io/example-data/kttm-v2/kttm-v2-2019-08-25.json.gz",
|
||||
],
|
||||
},
|
||||
"signature": Array [
|
||||
Object {
|
||||
"name": "timestamp",
|
||||
"type": "STRING",
|
||||
},
|
||||
Object {
|
||||
"name": "agent_type",
|
||||
"type": "STRING",
|
||||
},
|
||||
],
|
||||
"type": "external",
|
||||
},
|
||||
"granularity": Object {
|
||||
"type": "all",
|
||||
},
|
||||
"intervals": Object {
|
||||
"intervals": Array [
|
||||
"-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z",
|
||||
],
|
||||
"type": "intervals",
|
||||
},
|
||||
"legacy": false,
|
||||
"queryType": "scan",
|
||||
"resultFormat": "compactedList",
|
||||
"virtualColumns": Array [
|
||||
Object {
|
||||
"expression": "timestamp_parse(\\"timestamp\\",null,'UTC')",
|
||||
"name": "v0",
|
||||
"outputType": "LONG",
|
||||
"type": "expression",
|
||||
},
|
||||
],
|
||||
},
|
||||
"queryContext": Object {
|
||||
"finalizeAggregations": false,
|
||||
"groupByEnableMultiValueUnnesting": false,
|
||||
"maxParseExceptions": 0,
|
||||
},
|
||||
"result": undefined,
|
||||
"sqlQuery": "REPLACE INTO \\"kttm_simple\\" OVERWRITE ALL
|
||||
SELECT TIME_PARSE(\\"timestamp\\") AS \\"__time\\", agent_type
|
||||
FROM TABLE(
|
||||
EXTERN(
|
||||
'{\\"type\\":\\"http\\",\\"uris\\":[\\"https://static.imply.io/example-data/kttm-v2/kttm-v2-2019-08-25.json.gz\\"]}',
|
||||
'{\\"type\\":\\"json\\"}',
|
||||
'[{\\"name\\":\\"timestamp\\",\\"type\\":\\"string\\"},{\\"name\\":\\"agent_type\\",\\"type\\":\\"string\\"}]'
|
||||
)
|
||||
)
|
||||
PARTITIONED BY ALL TIME",
|
||||
"stages": Stages {
|
||||
"counters": Object {
|
||||
"0": Object {
|
||||
"0": Object {
|
||||
"input0": Object {
|
||||
"files": Array [
|
||||
1,
|
||||
],
|
||||
"rows": Array [
|
||||
465346,
|
||||
],
|
||||
"totalFiles": Array [
|
||||
1,
|
||||
],
|
||||
"type": "channel",
|
||||
},
|
||||
"output": Object {
|
||||
"bytes": Array [
|
||||
25430674,
|
||||
],
|
||||
"frames": Array [
|
||||
4,
|
||||
],
|
||||
"rows": Array [
|
||||
465346,
|
||||
],
|
||||
"type": "channel",
|
||||
},
|
||||
"shuffle": Object {
|
||||
"bytes": Array [
|
||||
23570446,
|
||||
],
|
||||
"frames": Array [
|
||||
38,
|
||||
],
|
||||
"rows": Array [
|
||||
465346,
|
||||
],
|
||||
"type": "channel",
|
||||
},
|
||||
"sortProgress": Object {
|
||||
"levelToMergedBatches": Object {
|
||||
"0": 1,
|
||||
"1": 1,
|
||||
"2": 1,
|
||||
},
|
||||
"levelToTotalBatches": Object {
|
||||
"0": 1,
|
||||
"1": 1,
|
||||
"2": 1,
|
||||
},
|
||||
"progressDigest": 1,
|
||||
"totalMergersForUltimateLevel": 1,
|
||||
"totalMergingLevels": 3,
|
||||
"type": "sortProgress",
|
||||
},
|
||||
},
|
||||
},
|
||||
"1": Object {
|
||||
"0": Object {
|
||||
"input0": Object {
|
||||
"bytes": Array [
|
||||
23570446,
|
||||
],
|
||||
"frames": Array [
|
||||
38,
|
||||
],
|
||||
"rows": Array [
|
||||
465346,
|
||||
],
|
||||
"type": "channel",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"stages": Array [
|
||||
Object {
|
||||
"definition": Object {
|
||||
"id": "0b353011-6ea1-480a-8ca8-386771621672_0",
|
||||
"input": Array [
|
||||
Object {
|
||||
"inputFormat": Object {
|
||||
"featureSpec": Object {},
|
||||
"flattenSpec": null,
|
||||
"keepNullColumns": false,
|
||||
"type": "json",
|
||||
},
|
||||
"inputSource": Object {
|
||||
"httpAuthenticationPassword": null,
|
||||
"httpAuthenticationUsername": null,
|
||||
"type": "http",
|
||||
"uris": Array [
|
||||
"https://static.imply.io/example-data/kttm-v2/kttm-v2-2019-08-25.json.gz",
|
||||
],
|
||||
},
|
||||
"signature": Array [
|
||||
Object {
|
||||
"name": "timestamp",
|
||||
"type": "STRING",
|
||||
},
|
||||
Object {
|
||||
"name": "agent_type",
|
||||
"type": "STRING",
|
||||
},
|
||||
],
|
||||
"type": "external",
|
||||
},
|
||||
],
|
||||
"maxWorkerCount": 1,
|
||||
"processor": Object {
|
||||
"query": Object {
|
||||
"columns": Array [
|
||||
"agent_type",
|
||||
"v0",
|
||||
],
|
||||
"context": Object {
|
||||
"__timeColumn": "v0",
|
||||
"finalize": false,
|
||||
"finalizeAggregations": false,
|
||||
"groupByEnableMultiValueUnnesting": false,
|
||||
"scanSignature": "[{\\"name\\":\\"agent_type\\",\\"type\\":\\"STRING\\"},{\\"name\\":\\"v0\\",\\"type\\":\\"LONG\\"}]",
|
||||
"sqlInsertSegmentGranularity": "{\\"type\\":\\"all\\"}",
|
||||
"sqlQueryId": "32ced762-7679-4a25-9220-3915c5976961",
|
||||
"sqlReplaceTimeChunks": "all",
|
||||
},
|
||||
"dataSource": Object {
|
||||
"inputNumber": 0,
|
||||
"type": "inputNumber",
|
||||
},
|
||||
"granularity": Object {
|
||||
"type": "all",
|
||||
},
|
||||
"intervals": Object {
|
||||
"intervals": Array [
|
||||
"-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z",
|
||||
],
|
||||
"type": "intervals",
|
||||
},
|
||||
"legacy": false,
|
||||
"queryType": "scan",
|
||||
"resultFormat": "compactedList",
|
||||
"virtualColumns": Array [
|
||||
Object {
|
||||
"expression": "timestamp_parse(\\"timestamp\\",null,'UTC')",
|
||||
"name": "v0",
|
||||
"outputType": "LONG",
|
||||
"type": "expression",
|
||||
},
|
||||
],
|
||||
},
|
||||
"type": "scan",
|
||||
},
|
||||
"shuffleCheckHasMultipleValues": true,
|
||||
"shuffleSpec": Object {
|
||||
"clusterBy": Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"columnName": "__boost",
|
||||
},
|
||||
],
|
||||
},
|
||||
"targetSize": 3000000,
|
||||
"type": "targetSize",
|
||||
},
|
||||
"signature": Array [
|
||||
Object {
|
||||
"name": "__boost",
|
||||
"type": "LONG",
|
||||
},
|
||||
Object {
|
||||
"name": "agent_type",
|
||||
"type": "STRING",
|
||||
},
|
||||
Object {
|
||||
"name": "v0",
|
||||
"type": "LONG",
|
||||
},
|
||||
],
|
||||
},
|
||||
"duration": 20229,
|
||||
"partitionCount": 1,
|
||||
"phase": "FINISHED",
|
||||
"sort": true,
|
||||
"stageNumber": 0,
|
||||
"startTime": "2022-08-22T20:12:53.790Z",
|
||||
"workerCount": 1,
|
||||
},
|
||||
Object {
|
||||
"definition": Object {
|
||||
"id": "0b353011-6ea1-480a-8ca8-386771621672_1",
|
||||
"input": Array [
|
||||
Object {
|
||||
"stage": 0,
|
||||
"type": "stage",
|
||||
},
|
||||
],
|
||||
"maxWorkerCount": 1,
|
||||
"processor": Object {
|
||||
"columnMappings": Array [
|
||||
Object {
|
||||
"outputColumn": "__time",
|
||||
"queryColumn": "v0",
|
||||
},
|
||||
Object {
|
||||
"outputColumn": "agent_type",
|
||||
"queryColumn": "agent_type",
|
||||
},
|
||||
],
|
||||
"dataSchema": Object {
|
||||
"dataSource": "kttm_simple",
|
||||
"dimensionsSpec": Object {
|
||||
"dimensionExclusions": Array [
|
||||
"__time",
|
||||
],
|
||||
"dimensions": Array [
|
||||
Object {
|
||||
"createBitmapIndex": true,
|
||||
"multiValueHandling": "SORTED_ARRAY",
|
||||
"name": "agent_type",
|
||||
"type": "string",
|
||||
},
|
||||
],
|
||||
"includeAllDimensions": false,
|
||||
},
|
||||
"granularitySpec": Object {
|
||||
"intervals": Array [
|
||||
"-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z",
|
||||
],
|
||||
"queryGranularity": Object {
|
||||
"type": "none",
|
||||
},
|
||||
"rollup": false,
|
||||
"type": "arbitrary",
|
||||
},
|
||||
"metricsSpec": Array [],
|
||||
"timestampSpec": Object {
|
||||
"column": "__time",
|
||||
"format": "millis",
|
||||
"missingValue": null,
|
||||
},
|
||||
"transformSpec": Object {
|
||||
"filter": null,
|
||||
"transforms": Array [],
|
||||
},
|
||||
},
|
||||
"tuningConfig": Object {
|
||||
"maxNumWorkers": 1,
|
||||
"maxRowsInMemory": 100000,
|
||||
"rowsPerSegment": 3000000,
|
||||
},
|
||||
"type": "segmentGenerator",
|
||||
},
|
||||
"signature": Array [],
|
||||
},
|
||||
"duration": 2497,
|
||||
"partitionCount": 1,
|
||||
"phase": "FINISHED",
|
||||
"stageNumber": 1,
|
||||
"startTime": "2022-08-22T20:13:13.991Z",
|
||||
"workerCount": 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
"startTime": 2022-08-22T20:12:51.391Z,
|
||||
"status": "SUCCESS",
|
||||
"warnings": undefined,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,467 @@
|
|||
/*
|
||||
* 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 { Column, QueryResult, SqlExpression, SqlQuery, SqlWithQuery } from 'druid-query-toolkit';
|
||||
|
||||
import { deepGet, deleteKeys, nonEmptyArray, oneOf } from '../../utils';
|
||||
import { DruidEngine, validDruidEngine } from '../druid-engine/druid-engine';
|
||||
import { QueryContext } from '../query-context/query-context';
|
||||
import { Stages } from '../stages/stages';
|
||||
|
||||
const IGNORE_CONTEXT_KEYS = [
|
||||
'__asyncIdentity__',
|
||||
'__timeColumn',
|
||||
'queryId',
|
||||
'sqlQueryId',
|
||||
'sqlInsertSegmentGranularity',
|
||||
'signature',
|
||||
'scanSignature',
|
||||
'sqlReplaceTimeChunks',
|
||||
];
|
||||
|
||||
// Hack around the concept that we might get back a SqlWithQuery and will need to unpack it
|
||||
function parseSqlQuery(queryString: string): SqlQuery | undefined {
|
||||
const q = SqlExpression.maybeParse(queryString);
|
||||
if (!q) return;
|
||||
if (q instanceof SqlWithQuery) return q.flattenWith();
|
||||
if (q instanceof SqlQuery) return q;
|
||||
return;
|
||||
}
|
||||
|
||||
export interface ExecutionError {
|
||||
error: {
|
||||
errorCode: string;
|
||||
errorMessage?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
host?: string;
|
||||
taskId?: string;
|
||||
stageNumber?: number;
|
||||
exceptionStackTrace?: string;
|
||||
}
|
||||
|
||||
type ExecutionDestination =
|
||||
| {
|
||||
type: 'taskReport';
|
||||
}
|
||||
| { type: 'dataSource'; dataSource: string; exists?: boolean }
|
||||
| { type: 'download' };
|
||||
|
||||
export type ExecutionStatus = 'RUNNING' | 'FAILED' | 'SUCCESS';
|
||||
|
||||
export interface LastExecution {
|
||||
engine: DruidEngine;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export function validateLastExecution(possibleLastExecution: any): LastExecution | undefined {
|
||||
if (
|
||||
!possibleLastExecution ||
|
||||
!validDruidEngine(possibleLastExecution.engine) ||
|
||||
typeof possibleLastExecution.id !== 'string'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
engine: possibleLastExecution.engine,
|
||||
id: possibleLastExecution.id,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ExecutionValue {
|
||||
engine: DruidEngine;
|
||||
id: string;
|
||||
sqlQuery?: string;
|
||||
nativeQuery?: any;
|
||||
queryContext?: QueryContext;
|
||||
status?: ExecutionStatus;
|
||||
startTime?: Date;
|
||||
duration?: number;
|
||||
stages?: Stages;
|
||||
destination?: ExecutionDestination;
|
||||
result?: QueryResult;
|
||||
error?: ExecutionError;
|
||||
warnings?: ExecutionError[];
|
||||
_payload?: { payload: any; task: string };
|
||||
}
|
||||
|
||||
export class Execution {
|
||||
static validAsyncStatus(
|
||||
status: string | undefined,
|
||||
): status is 'INITIALIZED' | 'RUNNING' | 'COMPLETE' | 'FAILED' | 'UNDETERMINED' {
|
||||
return oneOf(status, 'INITIALIZED', 'RUNNING', 'COMPLETE', 'FAILED', 'UNDETERMINED');
|
||||
}
|
||||
|
||||
static validTaskStatus(
|
||||
status: string | undefined,
|
||||
): status is 'WAITING' | 'PENDING' | 'RUNNING' | 'FAILED' | 'SUCCESS' {
|
||||
return oneOf(status, 'WAITING', 'PENDING', 'RUNNING', 'FAILED', 'SUCCESS');
|
||||
}
|
||||
|
||||
static normalizeAsyncStatus(
|
||||
state: 'INITIALIZED' | 'RUNNING' | 'COMPLETE' | 'FAILED' | 'UNDETERMINED',
|
||||
): ExecutionStatus {
|
||||
switch (state) {
|
||||
case 'COMPLETE':
|
||||
return 'SUCCESS';
|
||||
|
||||
case 'INITIALIZED':
|
||||
case 'UNDETERMINED':
|
||||
return 'RUNNING';
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// Treat WAITING as PENDING since they are all the same as far as the UI is concerned
|
||||
static normalizeTaskStatus(
|
||||
status: 'WAITING' | 'PENDING' | 'RUNNING' | 'FAILED' | 'SUCCESS',
|
||||
): ExecutionStatus {
|
||||
switch (status) {
|
||||
case 'SUCCESS':
|
||||
case 'FAILED':
|
||||
return status;
|
||||
|
||||
default:
|
||||
return 'RUNNING';
|
||||
}
|
||||
}
|
||||
|
||||
static fromTaskSubmit(
|
||||
taskSubmitResult: { state: any; taskId: string; error: any },
|
||||
sqlQuery?: string,
|
||||
queryContext?: QueryContext,
|
||||
): Execution {
|
||||
const status = Execution.normalizeTaskStatus(taskSubmitResult.state);
|
||||
return new Execution({
|
||||
engine: 'sql-msq-task',
|
||||
id: taskSubmitResult.taskId,
|
||||
status: taskSubmitResult.error ? 'FAILED' : status,
|
||||
sqlQuery,
|
||||
queryContext,
|
||||
error: taskSubmitResult.error
|
||||
? {
|
||||
error: {
|
||||
errorCode: 'AsyncError',
|
||||
errorMessage: JSON.stringify(taskSubmitResult.error),
|
||||
},
|
||||
}
|
||||
: status === 'FAILED'
|
||||
? {
|
||||
error: {
|
||||
errorCode: 'UnknownError',
|
||||
errorMessage:
|
||||
'Execution failed, there is no detail information, and there is no error in the status response',
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
destination: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
static fromTaskStatus(
|
||||
taskStatus: { status: any; task: string },
|
||||
sqlQuery?: string,
|
||||
queryContext?: QueryContext,
|
||||
): Execution {
|
||||
const status = Execution.normalizeTaskStatus(taskStatus.status.status);
|
||||
return new Execution({
|
||||
engine: 'sql-msq-task',
|
||||
id: taskStatus.task,
|
||||
status: taskStatus.status.error ? 'FAILED' : status,
|
||||
sqlQuery,
|
||||
queryContext,
|
||||
error: taskStatus.status.error
|
||||
? {
|
||||
error: {
|
||||
errorCode: 'AsyncError',
|
||||
errorMessage: JSON.stringify(taskStatus.status.error),
|
||||
},
|
||||
}
|
||||
: status === 'FAILED'
|
||||
? {
|
||||
error: {
|
||||
errorCode: 'UnknownError',
|
||||
errorMessage:
|
||||
'Execution failed, there is no detail information, and there is no error in the status response',
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
destination: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
static fromTaskPayloadAndReport(
|
||||
taskPayload: { payload: any; task: string },
|
||||
taskReport: {
|
||||
multiStageQuery: { payload: any; taskId: string };
|
||||
error?: any;
|
||||
},
|
||||
): Execution {
|
||||
// Must have status set for a valid report
|
||||
const id = deepGet(taskReport, 'multiStageQuery.taskId');
|
||||
const status = deepGet(taskReport, 'multiStageQuery.payload.status.status');
|
||||
const warnings = deepGet(taskReport, 'multiStageQuery.payload.status.warningReports');
|
||||
|
||||
if (typeof id !== 'string' || !Execution.validTaskStatus(status)) {
|
||||
throw new Error('Invalid payload');
|
||||
}
|
||||
|
||||
let error: ExecutionError | undefined;
|
||||
if (status === 'FAILED') {
|
||||
error =
|
||||
deepGet(taskReport, 'multiStageQuery.payload.status.errorReport') ||
|
||||
(typeof taskReport.error === 'string'
|
||||
? { error: { errorCode: 'UnknownError', errorMessage: taskReport.error } }
|
||||
: undefined);
|
||||
}
|
||||
|
||||
const stages = deepGet(taskReport, 'multiStageQuery.payload.stages');
|
||||
const startTime = new Date(deepGet(taskReport, 'multiStageQuery.payload.status.startTime'));
|
||||
const durationMs = deepGet(taskReport, 'multiStageQuery.payload.status.durationMs');
|
||||
|
||||
let result: QueryResult | undefined;
|
||||
const resultsPayload: {
|
||||
signature: { name: string; type: string }[];
|
||||
sqlTypeNames: string[];
|
||||
results: any[];
|
||||
} = deepGet(taskReport, 'multiStageQuery.payload.results');
|
||||
if (resultsPayload) {
|
||||
const { signature, sqlTypeNames, results } = resultsPayload;
|
||||
result = new QueryResult({
|
||||
header: signature.map(
|
||||
(sig, i: number) =>
|
||||
new Column({ name: sig.name, nativeType: sig.type, sqlType: sqlTypeNames?.[i] }),
|
||||
),
|
||||
rows: results,
|
||||
}).inflateDatesFromSqlTypes();
|
||||
}
|
||||
|
||||
let res = new Execution({
|
||||
engine: 'sql-msq-task',
|
||||
id,
|
||||
status: Execution.normalizeTaskStatus(status),
|
||||
startTime: isNaN(startTime.getTime()) ? undefined : startTime,
|
||||
duration: typeof durationMs === 'number' ? durationMs : undefined,
|
||||
stages: Array.isArray(stages)
|
||||
? new Stages(stages, deepGet(taskReport, 'multiStageQuery.payload.counters'))
|
||||
: undefined,
|
||||
error,
|
||||
warnings: Array.isArray(warnings) ? warnings : undefined,
|
||||
destination: deepGet(taskPayload, 'payload.spec.destination'),
|
||||
result,
|
||||
nativeQuery: deepGet(taskPayload, 'payload.spec.query'),
|
||||
|
||||
_payload: taskPayload,
|
||||
});
|
||||
|
||||
if (deepGet(taskPayload, 'payload.sqlQuery')) {
|
||||
res = res.changeSqlQuery(
|
||||
deepGet(taskPayload, 'payload.sqlQuery'),
|
||||
deleteKeys(deepGet(taskPayload, 'payload.sqlQueryContext'), IGNORE_CONTEXT_KEYS),
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static fromResult(engine: DruidEngine, result: QueryResult): Execution {
|
||||
return new Execution({
|
||||
engine,
|
||||
id: result.sqlQueryId || result.queryId || 'direct_result',
|
||||
status: 'SUCCESS',
|
||||
result,
|
||||
duration: result.queryDuration,
|
||||
});
|
||||
}
|
||||
|
||||
public readonly engine: DruidEngine;
|
||||
public readonly id: string;
|
||||
public readonly sqlQuery?: string;
|
||||
public readonly nativeQuery?: any;
|
||||
public readonly queryContext?: QueryContext;
|
||||
public readonly status?: ExecutionStatus;
|
||||
public readonly startTime?: Date;
|
||||
public readonly duration?: number;
|
||||
public readonly stages?: Stages;
|
||||
public readonly destination?: ExecutionDestination;
|
||||
public readonly result?: QueryResult;
|
||||
public readonly error?: ExecutionError;
|
||||
public readonly warnings?: ExecutionError[];
|
||||
|
||||
public readonly _payload?: { payload: any; task: string };
|
||||
|
||||
constructor(value: ExecutionValue) {
|
||||
this.engine = value.engine;
|
||||
this.id = value.id;
|
||||
if (!this.id) throw new Error('must have an id');
|
||||
this.sqlQuery = value.sqlQuery;
|
||||
this.nativeQuery = value.nativeQuery;
|
||||
this.queryContext = value.queryContext;
|
||||
this.status = value.status;
|
||||
this.startTime = value.startTime;
|
||||
this.duration = value.duration;
|
||||
this.stages = value.stages;
|
||||
this.destination = value.destination;
|
||||
this.result = value.result;
|
||||
this.error = value.error;
|
||||
this.warnings = nonEmptyArray(value.warnings) ? value.warnings : undefined;
|
||||
|
||||
this._payload = value._payload;
|
||||
}
|
||||
|
||||
valueOf(): ExecutionValue {
|
||||
return {
|
||||
engine: this.engine,
|
||||
id: this.id,
|
||||
sqlQuery: this.sqlQuery,
|
||||
nativeQuery: this.nativeQuery,
|
||||
queryContext: this.queryContext,
|
||||
status: this.status,
|
||||
startTime: this.startTime,
|
||||
duration: this.duration,
|
||||
stages: this.stages,
|
||||
destination: this.destination,
|
||||
result: this.result,
|
||||
error: this.error,
|
||||
warnings: this.warnings,
|
||||
|
||||
_payload: this._payload,
|
||||
};
|
||||
}
|
||||
|
||||
public changeSqlQuery(sqlQuery: string, queryContext?: QueryContext): Execution {
|
||||
const value = this.valueOf();
|
||||
|
||||
value.sqlQuery = sqlQuery;
|
||||
value.queryContext = queryContext;
|
||||
const parsedQuery = parseSqlQuery(sqlQuery);
|
||||
if (value.result && (parsedQuery || queryContext)) {
|
||||
value.result = value.result.attachQuery({ context: queryContext }, parsedQuery);
|
||||
}
|
||||
|
||||
return new Execution(value);
|
||||
}
|
||||
|
||||
public changeDestination(destination: ExecutionDestination): Execution {
|
||||
return new Execution({
|
||||
...this.valueOf(),
|
||||
destination,
|
||||
});
|
||||
}
|
||||
|
||||
public changeResult(result: QueryResult): Execution {
|
||||
return new Execution({
|
||||
...this.valueOf(),
|
||||
result: result.attachQuery({}, this.sqlQuery ? parseSqlQuery(this.sqlQuery) : undefined),
|
||||
});
|
||||
}
|
||||
|
||||
public updateWith(newSummary: Execution): Execution {
|
||||
let nextSummary = newSummary;
|
||||
if (this.sqlQuery && !nextSummary.sqlQuery) {
|
||||
nextSummary = nextSummary.changeSqlQuery(this.sqlQuery, this.queryContext);
|
||||
}
|
||||
if (this.destination && !nextSummary.destination) {
|
||||
nextSummary = nextSummary.changeDestination(this.destination);
|
||||
}
|
||||
|
||||
return nextSummary;
|
||||
}
|
||||
|
||||
public attachErrorFromStatus(status: any): Execution {
|
||||
const errorMsg = deepGet(status, 'status.errorMsg');
|
||||
|
||||
return new Execution({
|
||||
...this.valueOf(),
|
||||
error: {
|
||||
error: {
|
||||
errorCode: 'UnknownError',
|
||||
errorMessage: errorMsg,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public markDestinationDatasourceExists(): Execution {
|
||||
const { destination } = this;
|
||||
if (destination?.type !== 'dataSource') return this;
|
||||
|
||||
return new Execution({
|
||||
...this.valueOf(),
|
||||
destination: {
|
||||
...destination,
|
||||
exists: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public isProcessingData(): boolean {
|
||||
const { status, stages } = this;
|
||||
return Boolean(
|
||||
status === 'RUNNING' &&
|
||||
stages &&
|
||||
stages.getTotalInputForStage(stages.getStage(0), 'rows') > 0,
|
||||
);
|
||||
}
|
||||
|
||||
public isWaitingForQuery(): boolean {
|
||||
const { status } = this;
|
||||
return status !== 'SUCCESS' && status !== 'FAILED';
|
||||
}
|
||||
|
||||
public isFullyComplete(): boolean {
|
||||
if (this.isWaitingForQuery()) return false;
|
||||
|
||||
const { status, destination } = this;
|
||||
if (status === 'SUCCESS' && destination?.type === 'dataSource') {
|
||||
return Boolean(destination.exists);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public getIngestDatasource(): string | undefined {
|
||||
const { destination } = this;
|
||||
if (destination?.type !== 'dataSource') return;
|
||||
return destination.dataSource;
|
||||
}
|
||||
|
||||
public isSuccessfulInsert(): boolean {
|
||||
return Boolean(
|
||||
this.isFullyComplete() && this.getIngestDatasource() && this.status === 'SUCCESS',
|
||||
);
|
||||
}
|
||||
|
||||
public getErrorMessage(): string | undefined {
|
||||
const { error } = this;
|
||||
if (!error) return;
|
||||
return (
|
||||
(error.error.errorCode ? `${error.error.errorCode}: ` : '') +
|
||||
(error.error.errorMessage || (error.exceptionStackTrace || '').split('\n')[0])
|
||||
);
|
||||
}
|
||||
|
||||
public getEndTime(): Date | undefined {
|
||||
const { startTime, duration } = this;
|
||||
if (!startTime || !duration) return;
|
||||
return new Date(startTime.valueOf() + duration);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* 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 {
|
||||
filterMap,
|
||||
SqlExpression,
|
||||
SqlFunction,
|
||||
SqlLiteral,
|
||||
SqlQuery,
|
||||
SqlRef,
|
||||
SqlStar,
|
||||
} from 'druid-query-toolkit';
|
||||
import * as JSONBig from 'json-bigint-native';
|
||||
|
||||
import { nonEmptyArray } from '../../utils';
|
||||
import { InputFormat } from '../input-format/input-format';
|
||||
import { InputSource } from '../input-source/input-source';
|
||||
|
||||
export const MULTI_STAGE_QUERY_MAX_COLUMNS = 2000;
|
||||
const MAX_LINES = 10;
|
||||
|
||||
function joinLinesMax(lines: string[], max: number) {
|
||||
if (lines.length > max) {
|
||||
lines = lines.slice(0, max).concat(`(and ${lines.length - max} more)`);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export interface ExternalConfig {
|
||||
inputSource: InputSource;
|
||||
inputFormat: InputFormat;
|
||||
signature: SignatureColumn[];
|
||||
}
|
||||
|
||||
export interface SignatureColumn {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export function summarizeInputSource(inputSource: InputSource, multiline: boolean): string {
|
||||
switch (inputSource.type) {
|
||||
case 'inline':
|
||||
return `inline data`;
|
||||
|
||||
case 'local':
|
||||
// ToDo: make this official
|
||||
if (nonEmptyArray((inputSource as any).files)) {
|
||||
let lines: string[] = (inputSource as any).files;
|
||||
if (!multiline) lines = lines.slice(0, 1);
|
||||
return joinLinesMax(lines, MAX_LINES);
|
||||
}
|
||||
return `${inputSource.baseDir || '?'}{${inputSource.filter || '?'}}`;
|
||||
|
||||
case 'http':
|
||||
if (nonEmptyArray(inputSource.uris)) {
|
||||
let lines: string[] = inputSource.uris;
|
||||
if (!multiline) lines = lines.slice(0, 1);
|
||||
return joinLinesMax(lines, MAX_LINES);
|
||||
}
|
||||
return '?';
|
||||
|
||||
case 's3':
|
||||
case 'google':
|
||||
case 'azure': {
|
||||
const possibleLines = inputSource.uris || inputSource.prefixes;
|
||||
if (nonEmptyArray(possibleLines)) {
|
||||
let lines: string[] = possibleLines;
|
||||
if (!multiline) lines = lines.slice(0, 1);
|
||||
return joinLinesMax(lines, MAX_LINES);
|
||||
}
|
||||
if (nonEmptyArray(inputSource.objects)) {
|
||||
let lines: string[] = inputSource.objects.map(({ bucket, path }) => `${bucket}:${path}`);
|
||||
if (!multiline) lines = lines.slice(0, 1);
|
||||
return joinLinesMax(lines, MAX_LINES);
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
|
||||
case 'hdfs': {
|
||||
const paths =
|
||||
typeof inputSource.paths === 'string' ? inputSource.paths.split(',') : inputSource.paths;
|
||||
if (nonEmptyArray(paths)) {
|
||||
let lines: string[] = paths;
|
||||
if (!multiline) lines = lines.slice(0, 1);
|
||||
return joinLinesMax(lines, MAX_LINES);
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
|
||||
default:
|
||||
return String(inputSource.type);
|
||||
}
|
||||
}
|
||||
|
||||
export function summarizeInputFormat(inputFormat: InputFormat): string {
|
||||
return String(inputFormat.type);
|
||||
}
|
||||
|
||||
export function summarizeExternalConfig(externalConfig: ExternalConfig): string {
|
||||
return `${summarizeInputSource(externalConfig.inputSource, false)} [${summarizeInputFormat(
|
||||
externalConfig.inputFormat,
|
||||
)}]`;
|
||||
}
|
||||
|
||||
export function externalConfigToTableExpression(config: ExternalConfig): SqlExpression {
|
||||
return SqlExpression.parse(`TABLE(
|
||||
EXTERN(
|
||||
${SqlLiteral.create(JSONBig.stringify(config.inputSource))},
|
||||
${SqlLiteral.create(JSONBig.stringify(config.inputFormat))},
|
||||
${SqlLiteral.create(JSONBig.stringify(config.signature))}
|
||||
)
|
||||
)`);
|
||||
}
|
||||
|
||||
export function externalConfigToInitDimensions(
|
||||
config: ExternalConfig,
|
||||
isArrays: boolean[],
|
||||
timeExpression: SqlExpression | undefined,
|
||||
): SqlExpression[] {
|
||||
return (timeExpression ? [timeExpression.as('__time')] : [])
|
||||
.concat(
|
||||
filterMap(config.signature, ({ name }, i) => {
|
||||
if (timeExpression && timeExpression.containsColumn(name)) return;
|
||||
return SqlRef.column(name).applyIf(
|
||||
isArrays[i],
|
||||
ex => SqlFunction.simple('MV_TO_ARRAY', [ex]).as(name) as any,
|
||||
);
|
||||
}),
|
||||
)
|
||||
.slice(0, MULTI_STAGE_QUERY_MAX_COLUMNS);
|
||||
}
|
||||
|
||||
export function fitExternalConfigPattern(query: SqlQuery): ExternalConfig {
|
||||
if (!(query.getSelectExpressionForIndex(0) instanceof SqlStar)) {
|
||||
throw new Error(`External SELECT must only be a star`);
|
||||
}
|
||||
|
||||
const tableFn = query.fromClause?.expressions?.first();
|
||||
if (!(tableFn instanceof SqlFunction) || tableFn.functionName !== 'TABLE') {
|
||||
throw new Error(`External FROM must be a TABLE function`);
|
||||
}
|
||||
|
||||
const externFn = tableFn.getArg(0);
|
||||
if (!(externFn instanceof SqlFunction) || externFn.functionName !== 'EXTERN') {
|
||||
throw new Error(`Within the TABLE function there must be an extern function`);
|
||||
}
|
||||
|
||||
let inputSource: any;
|
||||
try {
|
||||
const arg0 = externFn.getArg(0);
|
||||
inputSource = JSONBig.parse(arg0 instanceof SqlLiteral ? String(arg0.value) : '#');
|
||||
} catch {
|
||||
throw new Error(`The first argument to the extern function must be a string embedding JSON`);
|
||||
}
|
||||
|
||||
let inputFormat: any;
|
||||
try {
|
||||
const arg1 = externFn.getArg(1);
|
||||
inputFormat = JSONBig.parse(arg1 instanceof SqlLiteral ? String(arg1.value) : '#');
|
||||
} catch {
|
||||
throw new Error(`The second argument to the extern function must be a string embedding JSON`);
|
||||
}
|
||||
|
||||
let signature: any;
|
||||
try {
|
||||
const arg2 = externFn.getArg(2);
|
||||
signature = JSONBig.parse(arg2 instanceof SqlLiteral ? String(arg2.value) : '#');
|
||||
} catch {
|
||||
throw new Error(`The third argument to the extern function must be a string embedding JSON`);
|
||||
}
|
||||
|
||||
return {
|
||||
inputSource,
|
||||
inputFormat,
|
||||
signature,
|
||||
};
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue