Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(welcome): add SQL snippets to saved queries card #11678

Merged
merged 12 commits into from Nov 30, 2020
26 changes: 26 additions & 0 deletions superset-frontend/images/empty-query.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions superset-frontend/src/components/ListViewCard/index.tsx
Expand Up @@ -145,7 +145,7 @@ const SkeletonActions = styled(Skeleton.Button)`

const paragraphConfig = { rows: 1, width: 150 };
interface CardProps {
title: React.ReactNode;
title?: React.ReactNode;
url?: string;
imgURL?: string;
imgFallbackURL?: string;
Expand All @@ -155,7 +155,7 @@ interface CardProps {
titleRight?: React.ReactNode;
coverLeft?: React.ReactNode;
coverRight?: React.ReactNode;
actions: React.ReactNode | null;
actions?: React.ReactNode | null;
rows?: number | string;
avatar?: string;
cover?: React.ReactNode | null;
Expand Down
15 changes: 4 additions & 11 deletions superset-frontend/src/views/CRUD/data/query/QueryList.tsx
Expand Up @@ -20,7 +20,7 @@ import React, { useMemo, useState, useCallback } from 'react';
import { SupersetClient, t, styled } from '@superset-ui/core';
import moment from 'moment';

import { createErrorHandler } from 'src/views/CRUD/utils';
import { createErrorHandler, shortenSQL } from 'src/views/CRUD/utils';
import withToasts from 'src/messageToasts/enhancers/withToasts';
import { useListViewResource } from 'src/views/CRUD/hooks';
import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu';
Expand All @@ -38,6 +38,7 @@ import { QueryObject } from 'src/views/CRUD/types';
import QueryPreviewModal from './QueryPreviewModal';

const PAGE_SIZE = 25;
const SQL_PREVIEW_MAX_LINES = 4;

const TopAlignedListView = styled(ListView)<ListViewProps<QueryObject>>`
table .table-cell {
Expand All @@ -52,15 +53,7 @@ const StyledSyntaxHighlighter = styled(SyntaxHighlighter)`
text-overflow: ellipsis;
white-space: nowrap;
`;
const SQL_PREVIEW_MAX_LINES = 4;
function shortenSQL(sql: string) {
let lines: string[] = sql.split('\n');
if (lines.length >= SQL_PREVIEW_MAX_LINES) {
lines = lines.slice(0, SQL_PREVIEW_MAX_LINES);
lines.push('...');
}
return lines.join('\n');
}

interface QueryListProps {
addDangerToast: (msg: string, config?: any) => any;
addSuccessToast: (msg: string, config?: any) => any;
Expand Down Expand Up @@ -298,7 +291,7 @@ function QueryList({ addDangerToast, addSuccessToast }: QueryListProps) {
onClick={() => setQueryCurrentlyPreviewing(original)}
>
<StyledSyntaxHighlighter language="sql" style={github}>
{shortenSQL(original.sql)}
{shortenSQL(original.sql, SQL_PREVIEW_MAX_LINES)}
</StyledSyntaxHighlighter>
</div>
);
Expand Down
17 changes: 13 additions & 4 deletions superset-frontend/src/views/CRUD/utils.tsx
Expand Up @@ -261,22 +261,31 @@ export function handleDashboardDelete(
);
}

export function shortenSQL(sql: string, maxLines: number) {
let lines: string[] = sql.split('\n');
if (lines.length >= maxLines) {
lines = lines.slice(0, maxLines);
lines.push('...');
pkdotson marked this conversation as resolved.
Show resolved Hide resolved
}
return lines.join('\n');
}

const breakpoints = [576, 768, 992, 1200];
export const mq = breakpoints.map(bp => `@media (max-width: ${bp}px)`);

export const CardContainer = styled.div`
display: grid;
grid-template-columns: repeat(auto-fit, minmax(31%, max-content));
grid-template-columns: repeat(auto-fit, minmax(31%, 31%));
${[mq[3]]} {
grid-template-columns: repeat(auto-fit, minmax(31%, max-content));
grid-template-columns: repeat(auto-fit, minmax(31%, 31%));
}

${[mq[2]]} {
grid-template-columns: repeat(auto-fit, minmax(48%, max-content));
grid-template-columns: repeat(auto-fit, minmax(48%, 48%));
}

${[mq[1]]} {
grid-template-columns: repeat(auto-fit, minmax(50%, max-content));
grid-template-columns: repeat(auto-fit, minmax(50%, 80%));
}
grid-gap: ${({ theme }) => theme.gridUnit * 8}px;
justify-content: left;
Expand Down
120 changes: 87 additions & 33 deletions superset-frontend/src/views/CRUD/welcome/SavedQueries.tsx
Expand Up @@ -18,6 +18,9 @@
*/
import React, { useState } from 'react';
import { t, SupersetClient, styled } from '@superset-ui/core';
import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/light';
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql';
import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github';
import withToasts from 'src/messageToasts/enhancers/withToasts';
import { Dropdown, Menu } from 'src/common/components';
import { useListViewResource, copyQueryLink } from 'src/views/CRUD/hooks';
Expand All @@ -30,9 +33,11 @@ import {
IconContainer,
CardContainer,
createErrorHandler,
CardStyles,
shortenSQL,
} from '../utils';

SyntaxHighlighter.registerLanguage('sql', sql);

const PAGE_SIZE = 3;

interface Query {
Expand All @@ -45,6 +50,8 @@ interface Query {
description?: string;
end_time?: string;
label?: string;
changed_on_delta_humanized?: string;
sql?: string | null;
}

interface SavedQueriesProps {
Expand All @@ -57,19 +64,51 @@ interface SavedQueriesProps {
mine: Array<Query>;
}

export const CardStyles = styled.div`
cursor: pointer;
a {
text-decoration: none;
}
.ant-card-cover {
border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
& > div {
height: 171px;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be an experiment for another PR, but if this card is basically a text box, we may be able to base its height on the number of lines set elsewhere in the JS. That is, if line-height is pulled from the Theme variables, the height could be numLines * lineHeight. Anyway, something to fiddle with to make this a little more dynamic.

}
}
.gradient-container > div {
background-size: contain;
background-repeat: no-repeat;
background-position: center;
background-color: #d8dbe4;
pkdotson marked this conversation as resolved.
Show resolved Hide resolved
display: inline-block;
width: 100%;
height: 179px;
background-repeat: no-repeat;
vertical-align: middle;
}
`;

const QueryData = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-start;
border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
.title {
font-weight: ${({ theme }) => theme.typography.weights.normal};
color: ${({ theme }) => theme.colors.grayscale.light1};
svg {
margin-left: ${({ theme }) => theme.gridUnit * 10}px;
}
.holder {
margin: ${({ theme }) => theme.gridUnit * 2}px;
.query-title {
padding: ${({ theme }) => theme.gridUnit * 2 + 2}px;
font-size: ${({ theme }) => theme.typography.sizes.l}px;
}
`;

const QueryContainer = styled.div`
pre {
height: ${({ theme }) => theme.gridUnit * 40}px;
border: none !important;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping me when you have a chance, we can pair up and see if there's a better selector to remove the need for !important here.

background-color: ${({ theme }) =>
theme.colors.grayscale.light5} !important;
overflow: hidden;
padding: ${({ theme }) => theme.gridUnit * 4}px !important;
}
`;

const SavedQueries = ({
user,
addDangerToast,
Expand Down Expand Up @@ -259,35 +298,50 @@ const SavedQueries = ({
key={q.id}
>
<ListViewCard
imgFallbackURL=""
imgURL=""
url={`/superset/sqllab?savedQueryId=${q.id}`}
title={q.label}
rows={q.rows}
description={t('Last run ', q.end_time)}
imgFallbackURL="/static/assets/images/empty-query.svg"
description={t('Last run %s', q.changed_on_delta_humanized)}
cover={
<QueryData>
<div className="holder">
<div className="title">{t('Tables')}</div>
<div>{q?.sql_tables?.length}</div>
</div>
<div className="holder">
<div className="title">{t('Datasource Name')}</div>
<div>{q?.sql_tables && q.sql_tables[0]?.table}</div>
</div>
</QueryData>
q?.sql?.length ? (
<QueryContainer>
<SyntaxHighlighter
language="sql"
lineProps={{
style: {
color: 'black',
wordBreak: 'break-all',
whiteSpace: 'pre-wrap',
},
}}
style={github}
wrapLines
lineNumberStyle={{
display: 'none',
}}
showLineNumbers={false}
>
{shortenSQL(q.sql, 25)}
</SyntaxHighlighter>
</QueryContainer>
) : (
false
)
}
actions={
<ListViewCard.Actions
onClick={e => {
e.stopPropagation();
e.preventDefault();
}}
>
<Dropdown overlay={renderMenu(q)}>
<Icon name="more-horiz" />
</Dropdown>
</ListViewCard.Actions>
<QueryData>
<ListViewCard.Actions
onClick={e => {
e.stopPropagation();
e.preventDefault();
}}
>
<Dropdown overlay={renderMenu(q)}>
<Icon name="more-horiz" />
</Dropdown>
</ListViewCard.Actions>
</QueryData>
}
/>
</CardStyles>
Expand Down