Web console: Fixes query cancel NPE and more (#13786)

* add null icon

* empty string table cell

* enable views only if they will work

* make sure method exists

* use SQL compatible nulls for e2e tests
This commit is contained in:
Vadim Ogievetsky 2023-02-15 15:02:50 -08:00 committed by GitHub
parent 460d8b8a2a
commit 1ca0edb8c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 60 additions and 30 deletions

View File

@ -64,6 +64,7 @@ function _build_distribution() {
&& cp "$(_get_code_root)/extensions-core/testing-tools/target/druid-testing-tools-$(_get_druid_version).jar" extensions/druid-testing-tools/ \
&& 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 \
&& echo -e "\n\ndruid.generic.useDefaultValueForNull=false" >> conf/druid/single-server/micro-quickstart/_common/common.runtime.properties \
)
}

View File

@ -65,6 +65,14 @@ exports[`TableCell matches snapshot array short 1`] = `
</div>
`;
exports[`TableCell matches snapshot empty string 1`] = `
<div
class="table-cell empty"
>
empty
</div>
`;
exports[`TableCell matches snapshot null 1`] = `
<div
class="table-cell null"

View File

@ -21,7 +21,8 @@
.table-cell {
padding: $table-cell-v-padding $table-cell-h-padding;
&.null {
&.null,
&.empty {
font-style: italic;
}

View File

@ -29,6 +29,13 @@ describe('TableCell', () => {
expect(container.firstChild).toMatchSnapshot();
});
it('matches snapshot empty string', () => {
const tableCell = <TableCell value="" />;
const { container } = render(tableCell);
expect(container.firstChild).toMatchSnapshot();
});
it('matches snapshot simple', () => {
const tableCell = <TableCell value="Hello World" />;

View File

@ -90,22 +90,22 @@ export const TableCell = React.memo(function TableCell(props: TableCellProps) {
);
}
if (value !== '' && value != null) {
if (value instanceof Date) {
const dateValue = value.valueOf();
return (
<div className="table-cell timestamp" title={String(value.valueOf())}>
{isNaN(dateValue) ? 'Unusable date' : value.toISOString()}
</div>
);
} else if (isSimpleArray(value)) {
return renderTruncated(`[${value.join(', ')}]`);
} else if (typeof value === 'object') {
return renderTruncated(JSONBig.stringify(value));
} else {
return renderTruncated(String(value));
}
} else {
if (value === '') {
return <div className="table-cell empty">empty</div>;
} else if (value == null) {
return <div className="table-cell null">null</div>;
} else if (value instanceof Date) {
const dateValue = value.valueOf();
return (
<div className="table-cell timestamp" title={String(value.valueOf())}>
{isNaN(dateValue) ? 'Unusable date' : value.toISOString()}
</div>
);
} else if (isSimpleArray(value)) {
return renderTruncated(`[${value.join(', ')}]`);
} else if (typeof value === 'object') {
return renderTruncated(JSONBig.stringify(value));
} else {
return renderTruncated(String(value));
}
});

View File

@ -356,7 +356,7 @@ export class ConsoleApplication extends React.PureComponent<
};
render(): JSX.Element {
const { capabilitiesLoading } = this.state;
const { capabilities, capabilitiesLoading } = this.state;
if (capabilitiesLoading) {
return (
@ -371,15 +371,24 @@ export class ConsoleApplication extends React.PureComponent<
<HashRouter hashType="noslash">
<div className="console-application">
<Switch>
<Route path="/data-loader" component={this.wrappedDataLoaderView} />
<Route
path="/streaming-data-loader"
component={this.wrappedStreamingDataLoaderView}
/>
<Route
path="/classic-batch-data-loader"
component={this.wrappedClassicBatchDataLoaderView}
/>
{capabilities.hasCoordinatorAccess() && (
<Route path="/data-loader" component={this.wrappedDataLoaderView} />
)}
{capabilities.hasCoordinatorAccess() && (
<Route
path="/streaming-data-loader"
component={this.wrappedStreamingDataLoaderView}
/>
)}
{capabilities.hasCoordinatorAccess() && (
<Route
path="/classic-batch-data-loader"
component={this.wrappedClassicBatchDataLoaderView}
/>
)}
{capabilities.hasCoordinatorAccess() && capabilities.hasMultiStageQuery() && (
<Route path="/sql-data-loader" component={this.wrappedSqlDataLoaderView} />
)}
<Route path="/ingestion" component={this.wrappedIngestionView} />
<Route path="/datasources" component={this.wrappedDatasourcesView} />
@ -393,9 +402,10 @@ export class ConsoleApplication extends React.PureComponent<
path={['/workbench/:tabId', '/workbench']}
component={this.wrappedWorkbenchView}
/>
<Route path="/sql-data-loader" component={this.wrappedSqlDataLoaderView} />
<Route path="/lookups" component={this.wrappedLookupsView} />
{capabilities.hasCoordinatorAccess() && (
<Route path="/lookups" component={this.wrappedLookupsView} />
)}
<Route component={this.wrappedHomeView} />
</Switch>
</div>

View File

@ -39,7 +39,7 @@ export class Api {
return Promise.reject(new Error(message));
}
if (error.config.method?.toLowerCase() === 'get' && nonEmptyString(responseData)) {
if (error.config?.method?.toLowerCase() === 'get' && nonEmptyString(responseData)) {
return Promise.reject(new Error(responseData));
}

View File

@ -78,6 +78,9 @@ export function dataTypeToIcon(dataType: string): IconName {
case 'COMPLEX<JSON>':
return IconNames.DIAGRAM_TREE;
case 'NULL':
return IconNames.CIRCLE;
default:
if (typeUpper.startsWith('ARRAY')) return IconNames.ARRAY;
if (typeUpper.startsWith('COMPLEX')) return IconNames.ASTERISK;