mirror of
https://github.com/apache/superset.git
synced 2026-04-28 04:25:07 +00:00
fix: Query History cosmetic issues (#14885)
* easiest fix * Query History Cosmetic Issues * added revisions * beto revisions
This commit is contained in:
318
superset-frontend/src/SqlLab/components/QueryTable/index.jsx
Normal file
318
superset-frontend/src/SqlLab/components/QueryTable/index.jsx
Normal file
@@ -0,0 +1,318 @@
|
||||
/**
|
||||
* 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 React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import Card from 'src/components/Card';
|
||||
import ProgressBar from 'src/components/ProgressBar';
|
||||
import Label from 'src/components/Label';
|
||||
import { t, styled } from '@superset-ui/core';
|
||||
import { useSelector } from 'react-redux';
|
||||
import TableView from 'src/components/TableView';
|
||||
import Button from 'src/components/Button';
|
||||
import { fDuration } from 'src/modules/dates';
|
||||
import Icons from 'src/components/Icons';
|
||||
import Icon from 'src/components/Icon';
|
||||
import { Tooltip } from 'src/components/Tooltip';
|
||||
import ResultSet from '../ResultSet';
|
||||
import ModalTrigger from '../../../components/ModalTrigger';
|
||||
import HighlightedSql from '../HighlightedSql';
|
||||
import { StaticPosition, verticalAlign, StyledTooltip } from './styles';
|
||||
|
||||
const propTypes = {
|
||||
columns: PropTypes.array,
|
||||
actions: PropTypes.object,
|
||||
queries: PropTypes.array,
|
||||
onUserClicked: PropTypes.func,
|
||||
onDbClicked: PropTypes.func,
|
||||
displayLimit: PropTypes.number.isRequired,
|
||||
};
|
||||
const defaultProps = {
|
||||
columns: ['started', 'duration', 'rows'],
|
||||
queries: [],
|
||||
onUserClicked: () => {},
|
||||
onDbClicked: () => {},
|
||||
};
|
||||
|
||||
const openQuery = id => {
|
||||
const url = `/superset/sqllab?queryId=${id}`;
|
||||
window.open(url);
|
||||
};
|
||||
|
||||
const statusAttributes = {
|
||||
success: {
|
||||
color: ({ theme }) => theme.colors.success.base,
|
||||
config: {
|
||||
name: 'check',
|
||||
label: t('Success'),
|
||||
status: 'success',
|
||||
},
|
||||
},
|
||||
failed: {
|
||||
color: ({ theme }) => theme.colors.error.base,
|
||||
config: {
|
||||
name: 'x-small',
|
||||
label: t('Failed'),
|
||||
status: 'failed',
|
||||
},
|
||||
},
|
||||
stopped: {
|
||||
color: ({ theme }) => theme.colors.error.base,
|
||||
config: {
|
||||
name: 'x-small',
|
||||
label: t('Failed'),
|
||||
status: 'failed',
|
||||
},
|
||||
},
|
||||
running: {
|
||||
color: ({ theme }) => theme.colors.primary.base,
|
||||
config: {
|
||||
name: 'running',
|
||||
label: t('Running'),
|
||||
status: 'running',
|
||||
},
|
||||
},
|
||||
timed_out: {
|
||||
color: ({ theme }) => theme.colors.grayscale.light1,
|
||||
config: {
|
||||
name: 'offline',
|
||||
label: t('Offline'),
|
||||
status: 'offline',
|
||||
},
|
||||
},
|
||||
scheduled: {
|
||||
name: 'queued',
|
||||
label: t('Scheduled'),
|
||||
status: 'queued',
|
||||
},
|
||||
pending: {
|
||||
name: 'queued',
|
||||
label: t('Scheduled'),
|
||||
status: 'queued',
|
||||
},
|
||||
};
|
||||
|
||||
const StatusIcon = styled(Icon, {
|
||||
shouldForwardProp: prop => prop !== 'status',
|
||||
})`
|
||||
color: ${({ status, theme }) =>
|
||||
statusAttributes[status]?.color || theme.colors.grayscale.base};
|
||||
`;
|
||||
|
||||
const QueryTable = props => {
|
||||
const setHeaders = column => {
|
||||
if (column === 'sql') {
|
||||
return column.toUpperCase();
|
||||
}
|
||||
return column.charAt(0).toUpperCase().concat(column.slice(1));
|
||||
};
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
props.columns.map(column => ({
|
||||
accessor: column,
|
||||
Header: () => setHeaders(column),
|
||||
disableSortBy: true,
|
||||
})),
|
||||
[props.columns],
|
||||
);
|
||||
|
||||
const user = useSelector(({ sqlLab: { user } }) => user);
|
||||
|
||||
const data = useMemo(() => {
|
||||
const restoreSql = query => {
|
||||
props.actions.queryEditorSetSql({ id: query.sqlEditorId }, query.sql);
|
||||
};
|
||||
|
||||
const openQueryInNewTab = query => {
|
||||
props.actions.cloneQueryToNewTab(query, true);
|
||||
};
|
||||
|
||||
const openAsyncResults = (query, displayLimit) => {
|
||||
props.actions.fetchQueryResults(query, displayLimit);
|
||||
};
|
||||
|
||||
const clearQueryResults = query => {
|
||||
props.actions.clearQueryResults(query);
|
||||
};
|
||||
|
||||
const removeQuery = query => {
|
||||
props.actions.removeQuery(query);
|
||||
};
|
||||
|
||||
return props.queries
|
||||
.map(query => {
|
||||
const q = { ...query };
|
||||
if (q.endDttm) {
|
||||
q.duration = fDuration(q.startDttm, q.endDttm);
|
||||
}
|
||||
const time = moment(q.startDttm).format().split('T');
|
||||
q.time = (
|
||||
<div>
|
||||
<span>
|
||||
{time[0]} <br /> {time[1]}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
q.user = (
|
||||
<Button
|
||||
buttonSize="small"
|
||||
buttonStyle="link"
|
||||
onClick={() => props.onUserClicked(q.userId)}
|
||||
>
|
||||
{q.user}
|
||||
</Button>
|
||||
);
|
||||
q.db = (
|
||||
<Button
|
||||
buttonSize="small"
|
||||
buttonStyle="link"
|
||||
onClick={() => props.onDbClicked(q.dbId)}
|
||||
>
|
||||
{q.db}
|
||||
</Button>
|
||||
);
|
||||
q.started = moment(q.startDttm).format('HH:mm:ss');
|
||||
q.querylink = (
|
||||
<Button
|
||||
buttonSize="small"
|
||||
buttonStyle="link"
|
||||
onClick={() => openQuery(q.queryId)}
|
||||
>
|
||||
<i className="fa fa-external-link m-r-3" />
|
||||
{t('Edit')}
|
||||
</Button>
|
||||
);
|
||||
q.sql = (
|
||||
<Card css={[StaticPosition]}>
|
||||
<HighlightedSql
|
||||
sql={q.sql}
|
||||
rawSql={q.executedSql}
|
||||
shrink
|
||||
maxWidth={60}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
if (q.resultsKey) {
|
||||
q.output = (
|
||||
<ModalTrigger
|
||||
className="ResultsModal"
|
||||
triggerNode={
|
||||
<Label type="info" className="pointer">
|
||||
{t('View results')}
|
||||
</Label>
|
||||
}
|
||||
modalTitle={t('Data preview')}
|
||||
beforeOpen={() => openAsyncResults(query, props.displayLimit)}
|
||||
onExit={() => clearQueryResults(query)}
|
||||
modalBody={
|
||||
<ResultSet
|
||||
showSql
|
||||
user={user}
|
||||
query={query}
|
||||
actions={props.actions}
|
||||
height={400}
|
||||
displayLimit={props.displayLimit}
|
||||
/>
|
||||
}
|
||||
responsive
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// if query was run using ctas and force_ctas_schema was set
|
||||
// tempTable will have the schema
|
||||
const schemaUsed =
|
||||
q.ctas && q.tempTable && q.tempTable.includes('.') ? '' : q.schema;
|
||||
q.output = [schemaUsed, q.tempTable].filter(v => v).join('.');
|
||||
}
|
||||
q.progress =
|
||||
q.state === 'success' ? (
|
||||
<ProgressBar
|
||||
percent={parseInt(q.progress.toFixed(0), 10)}
|
||||
striped
|
||||
showInfo={false}
|
||||
/>
|
||||
) : (
|
||||
<ProgressBar
|
||||
percent={parseInt(q.progress.toFixed(0), 10)}
|
||||
striped
|
||||
/>
|
||||
);
|
||||
q.state = (
|
||||
<Tooltip
|
||||
title={statusAttributes[q.state].config.label}
|
||||
placement="bottom"
|
||||
>
|
||||
<span>
|
||||
<StatusIcon
|
||||
name={statusAttributes[q.state].config.name}
|
||||
status={statusAttributes[q.state].config.status}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
q.actions = (
|
||||
<div>
|
||||
<StyledTooltip
|
||||
onClick={() => restoreSql(query)}
|
||||
tooltip={t(
|
||||
'Overwrite text in the editor with a query on this table',
|
||||
)}
|
||||
placement="top"
|
||||
>
|
||||
<Icons.Edit iconSize="small" />
|
||||
</StyledTooltip>
|
||||
<StyledTooltip
|
||||
onClick={() => openQueryInNewTab(query)}
|
||||
tooltip={t('Run query in a new tab')}
|
||||
placement="top"
|
||||
>
|
||||
<Icons.PlusCircleOutlined
|
||||
iconSize="x-small"
|
||||
css={verticalAlign}
|
||||
/>
|
||||
</StyledTooltip>
|
||||
<StyledTooltip
|
||||
tooltip={t('Remove query from log')}
|
||||
onClick={() => removeQuery(query)}
|
||||
>
|
||||
<Icons.Trash iconSize="x-small" />
|
||||
</StyledTooltip>
|
||||
</div>
|
||||
);
|
||||
return q;
|
||||
})
|
||||
.reverse();
|
||||
}, [props]);
|
||||
|
||||
return (
|
||||
<div className="QueryTable">
|
||||
<TableView
|
||||
columns={columns}
|
||||
data={data}
|
||||
className="table-condensed"
|
||||
pageSize={50}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
QueryTable.propTypes = propTypes;
|
||||
QueryTable.defaultProps = defaultProps;
|
||||
|
||||
export default QueryTable;
|
||||
41
superset-frontend/src/SqlLab/components/QueryTable/styles.ts
Normal file
41
superset-frontend/src/SqlLab/components/QueryTable/styles.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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 { styled, css } from '@superset-ui/core';
|
||||
import { IconTooltip } from '../../../components/IconTooltip';
|
||||
|
||||
export const StaticPosition = css`
|
||||
position: static;
|
||||
`;
|
||||
|
||||
export const verticalAlign = css`
|
||||
vertical-align: 0em;
|
||||
svg {
|
||||
height: 0.9em;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledTooltip = styled(IconTooltip)`
|
||||
padding-right: ${({ theme }) => theme.gridUnit * 2}px;
|
||||
span {
|
||||
color: ${({ theme }) => theme.colors.grayscale.base};
|
||||
&: hover {
|
||||
color: ${({ theme }) => theme.colors.primary.base};
|
||||
}
|
||||
}
|
||||
`;
|
||||
Reference in New Issue
Block a user