Skip to content

Commit

Permalink
feat(sqllab): giving the query history pane a facelift (apache#31316)
Browse files Browse the repository at this point in the history
  • Loading branch information
mistercrunch authored Dec 12, 2024
1 parent 988da2c commit 4ff9aac
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 55 deletions.
4 changes: 1 addition & 3 deletions superset-frontend/src/SqlLab/actions/sqlLab.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,9 +421,7 @@ export function postStopQuery(query) {
})
.then(() => dispatch(stopQuery(query)))
.then(() => dispatch(addSuccessToast(t('Query was stopped.'))))
.catch(() =>
dispatch(addDangerToast(t('Failed at stopping query. %s', query.id))),
);
.catch(() => dispatch(addDangerToast(t('Failed to stop query.'))));
};
}

Expand Down
96 changes: 69 additions & 27 deletions superset-frontend/src/SqlLab/components/QueryTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
import { useMemo } from 'react';
import { useMemo, ReactNode } from 'react';
import moment from 'moment';
import Card from 'src/components/Card';
import ProgressBar from 'src/components/ProgressBar';
import Label from 'src/components/Label';
import { t, useTheme, QueryResponse } from '@superset-ui/core';
import { useDispatch, useSelector } from 'react-redux';

Expand All @@ -35,6 +34,7 @@ import TableView from 'src/components/TableView';
import Button from 'src/components/Button';
import { fDuration } from 'src/utils/dates';
import Icons from 'src/components/Icons';
import Label from 'src/components/Label';
import { Tooltip } from 'src/components/Tooltip';
import { SqlLabRootState } from 'src/SqlLab/types';
import ModalTrigger from 'src/components/ModalTrigger';
Expand All @@ -44,11 +44,16 @@ import HighlightedSql from '../HighlightedSql';
import { StaticPosition, verticalAlign, StyledTooltip } from './styles';

interface QueryTableQuery
extends Omit<QueryResponse, 'state' | 'sql' | 'progress' | 'results'> {
extends Omit<
QueryResponse,
'state' | 'sql' | 'progress' | 'results' | 'duration' | 'started'
> {
state?: Record<string, any>;
sql?: Record<string, any>;
progress?: Record<string, any>;
results?: Record<string, any>;
duration?: ReactNode;
started?: ReactNode;
}

interface QueryTableProps {
Expand Down Expand Up @@ -125,55 +130,95 @@ const QueryTable = ({
const statusAttributes = {
success: {
config: {
icon: <Icons.Check iconColor={theme.colors.success.base} />,
icon: (
<Icons.CheckOutlined
iconColor={theme.colors.success.base}
iconSize="m"
/>
),
// icon: <Icons.Edit iconSize="xl" />,
label: t('Success'),
},
},
failed: {
config: {
icon: <Icons.XSmall iconColor={theme.colors.error.base} />,
icon: (
<Icons.CloseOutlined
iconColor={theme.colors.error.base}
iconSize="m"
/>
),
label: t('Failed'),
},
},
stopped: {
config: {
icon: <Icons.XSmall iconColor={theme.colors.error.base} />,
icon: (
<Icons.CloseOutlined
iconColor={theme.colors.error.base}
iconSize="m"
/>
),
label: t('Failed'),
},
},
running: {
config: {
icon: <Icons.Running iconColor={theme.colors.primary.base} />,
icon: (
<Icons.LoadingOutlined
iconColor={theme.colors.primary.base}
iconSize="m"
/>
),
label: t('Running'),
},
},
fetching: {
config: {
icon: <Icons.Queued iconColor={theme.colors.primary.base} />,
icon: (
<Icons.LoadingOutlined
iconColor={theme.colors.primary.base}
iconSize="m"
/>
),
label: t('Fetching'),
},
},
timed_out: {
config: {
icon: <Icons.Offline iconColor={theme.colors.grayscale.light1} />,
icon: (
<Icons.Clock iconColor={theme.colors.error.base} iconSize="m" />
),
label: t('Offline'),
},
},
scheduled: {
config: {
icon: <Icons.Queued iconColor={theme.colors.grayscale.base} />,
icon: (
<Icons.LoadingOutlined
iconColor={theme.colors.warning.base}
iconSize="m"
/>
),
label: t('Scheduled'),
},
},
pending: {
config: {
icon: <Icons.Queued iconColor={theme.colors.grayscale.base} />,
icon: (
<Icons.LoadingOutlined
iconColor={theme.colors.warning.base}
iconSize="m"
/>
),
label: t('Scheduled'),
},
},
error: {
config: {
icon: <Icons.Error iconColor={theme.colors.error.base} />,
icon: (
<Icons.Error iconColor={theme.colors.error.base} iconSize="m" />
),
label: t('Unknown Status'),
},
},
Expand All @@ -187,16 +232,10 @@ const QueryTable = ({
const status = statusAttributes[state] || statusAttributes.error;

if (q.endDttm) {
q.duration = fDuration(q.startDttm, q.endDttm);
q.duration = (
<Label monospace>{fDuration(q.startDttm, q.endDttm)}</Label>
);
}
const time = moment(q.startDttm).format().split('T');
q.time = (
<div>
<span>
{time[0]} <br /> {time[1]}
</span>
</div>
);
q.user = (
<Button
buttonSize="small"
Expand All @@ -215,7 +254,9 @@ const QueryTable = ({
{q.db}
</Button>
);
q.started = moment(q.startDttm).format('L HH:mm:ss');
q.started = (
<Label monospace>{moment(q.startDttm).format('L HH:mm:ss')}</Label>
);
q.querylink = (
<Button
buttonSize="small"
Expand All @@ -241,9 +282,9 @@ const QueryTable = ({
<ModalTrigger
className="ResultsModal"
triggerNode={
<Label type="info" className="pointer">
<Button buttonSize="xsmall" buttonStyle="tertiary">
{t('View')}
</Label>
</Button>
}
modalTitle={t('Data preview')}
beforeOpen={() => openAsyncResults(query, displayLimit)}
Expand Down Expand Up @@ -275,9 +316,7 @@ const QueryTable = ({
<ProgressBar percent={parseInt(progress.toFixed(0), 10)} striped />
);
q.state = (
<Tooltip title={status.config.label} placement="bottom">
<span>{status.config.icon}</span>
</Tooltip>
<Tooltip title={status.config.label}>{status.config.icon}</Tooltip>
);
q.actions = (
<div>
Expand All @@ -287,20 +326,23 @@ const QueryTable = ({
'Overwrite text in the editor with a query on this table',
)}
placement="top"
className="pointer"
>
<Icons.Edit iconSize="xl" />
</StyledTooltip>
<StyledTooltip
onClick={() => openQueryInNewTab(query)}
tooltip={t('Run query in a new tab')}
placement="top"
className="pointer"
>
<Icons.PlusCircleOutlined iconSize="xl" css={verticalAlign} />
</StyledTooltip>
{q.id !== latestQueryId && (
<StyledTooltip
tooltip={t('Remove query from log')}
onClick={() => dispatch(removeQuery(query))}
className="pointer"
>
<Icons.Trash iconSize="xl" />
</StyledTooltip>
Expand Down
13 changes: 11 additions & 2 deletions superset-frontend/src/components/Icons/Icons.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,19 @@ const IconBlock = styled.div`
flex-direction: column;
align-items: center;
padding: ${({ theme }) => theme.gridUnit * 2}px;
span {
margin-top: ${({ theme }) =>
2 * theme.gridUnit}px; // Add spacing between icon and name
font-size: ${({ theme }) =>
theme.typography.sizes.m}; // Optional: adjust font size for elegance
color: ${({ theme }) =>
theme.colors.grayscale.base}; // Optional: subtle color for the name
}
`;

export const InteractiveIcons = ({
showNames,
showNames = true,
...rest
}: IconType & { showNames: boolean }) => (
<IconSet>
Expand All @@ -56,7 +65,7 @@ export const InteractiveIcons = ({
return (
<IconBlock key={k}>
<IconComponent {...rest} />
{showNames && k}
{showNames && <span>{k}</span>}
</IconBlock>
);
})}
Expand Down
9 changes: 7 additions & 2 deletions superset-frontend/src/components/Label/Label.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ export const LabelGallery = () => (
);

export const InteractiveLabel = (args: any) => {
const { hasOnClick, label, ...rest } = args;
const { hasOnClick, label, monospace, ...rest } = args;
return (
<Label onClick={hasOnClick ? action('clicked') : undefined} {...rest}>
<Label
onClick={hasOnClick ? action('clicked') : undefined}
monospace={monospace}
{...rest}
>
{label}
</Label>
);
Expand All @@ -66,4 +70,5 @@ export const InteractiveLabel = (args: any) => {
InteractiveLabel.args = {
hasOnClick: true,
label: 'Example',
monospace: true,
};
54 changes: 33 additions & 21 deletions superset-frontend/src/components/Label/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,20 @@ export interface LabelProps extends HTMLAttributes<HTMLSpanElement> {
style?: CSSProperties;
children?: ReactNode;
role?: string;
monospace?: boolean;
}

export default function Label(props: LabelProps) {
const theme = useTheme();
const { colors, transitionTiming } = theme;
const { type = 'default', onClick, children, ...rest } = props;
const {
type = 'default',
monospace = false,
style,
onClick,
children,
...rest
} = props;
const {
alert,
primary,
Expand Down Expand Up @@ -89,37 +97,41 @@ export default function Label(props: LabelProps) {
} else {
baseColor = primary;
}

backgroundColor = baseColor.base;
backgroundColorHover = onClick ? baseColor.dark1 : baseColor.base;
borderColor = onClick ? baseColor.dark1 : 'transparent';
borderColorHover = onClick ? baseColor.dark2 : 'transparent';
}
const css = {
transition: `background-color ${transitionTiming}s`,
whiteSpace: 'nowrap',
cursor: onClick ? 'pointer' : 'default',
overflow: 'hidden',
textOverflow: 'ellipsis',
backgroundColor,
borderColor,
borderRadius: 21,
padding: '0.35em 0.8em',
lineHeight: 1,
color,
maxWidth: '100%',
'&:hover': {
backgroundColor: backgroundColorHover,
borderColor: borderColorHover,
opacity: 1,
},
};
if (monospace) {
css['font-family'] = theme.typography.families.monospace;
}

return (
<Tag
onClick={onClick}
role={onClick ? 'button' : undefined}
style={style}
{...rest}
css={{
transition: `background-color ${transitionTiming}s`,
whiteSpace: 'nowrap',
cursor: onClick ? 'pointer' : 'default',
overflow: 'hidden',
textOverflow: 'ellipsis',
backgroundColor,
borderColor,
borderRadius: 21,
padding: '0.35em 0.8em',
lineHeight: 1,
color,
maxWidth: '100%',
'&:hover': {
backgroundColor: backgroundColorHover,
borderColor: borderColorHover,
opacity: 1,
},
}}
css={css}
>
{children}
</Tag>
Expand Down

0 comments on commit 4ff9aac

Please sign in to comment.