mirror of
https://github.com/apache/superset.git
synced 2024-09-17 11:09:47 -04:00
feat(welcome): add SQL snippets to saved queries card (#11678)
* update savedqueries card to new layout * update card * update card to latest mock * update empty state * remove fallback * fix query statement * update card styles * remove double import * use fallbackurl prop for emptystate * update line lenth * Update superset-frontend/src/views/CRUD/welcome/SavedQueries.tsx Co-authored-by: Evan Rusackas <evan@preset.io> * update styles and svg Co-authored-by: Evan Rusackas <evan@preset.io>
This commit is contained in:
parent
a4f44255aa
commit
0e9898cb96
25
superset-frontend/images/empty-query.svg
Normal file
25
superset-frontend/images/empty-query.svg
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<svg width="458" height="146" viewBox="0 0 458 146" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17 21C17 18.7909 18.7909 17 21 17H125C127.209 17 129 18.7909 129 21V29C129 31.2091 127.209 33 125 33H21C18.7909 33 17 31.2091 17 29V21Z" fill="#ECEEF2"/>
|
||||||
|
<path d="M17 45C17 42.7909 18.7909 41 21 41H223C225.209 41 227 42.7909 227 45V53C227 55.2091 225.209 57 223 57H21C18.7909 57 17 55.2091 17 53V45Z" fill="#ECEEF2"/>
|
||||||
|
<path d="M21 65C18.7909 65 17 66.7909 17 69V77C17 79.2091 18.7909 81 21 81H172C174.209 81 176 79.2091 176 77V69C176 66.7909 174.209 65 172 65H21Z" fill="#ECEEF2"/>
|
||||||
|
<path d="M17 93C17 90.7909 18.7909 89 21 89H286C288.209 89 290 90.7909 290 93V101C290 103.209 288.209 105 286 105H21C18.7909 105 17 103.209 17 101V93Z" fill="#ECEEF2"/>
|
||||||
|
<path d="M21 113C18.7909 113 17 114.791 17 117V125C17 127.209 18.7909 129 21 129H73C75.2091 129 77 127.209 77 125V117C77 114.791 75.2091 113 73 113H21Z" fill="#ECEEF2"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -145,7 +145,7 @@ const SkeletonActions = styled(Skeleton.Button)`
|
|||||||
|
|
||||||
const paragraphConfig = { rows: 1, width: 150 };
|
const paragraphConfig = { rows: 1, width: 150 };
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
title: React.ReactNode;
|
title?: React.ReactNode;
|
||||||
url?: string;
|
url?: string;
|
||||||
imgURL?: string;
|
imgURL?: string;
|
||||||
imgFallbackURL?: string;
|
imgFallbackURL?: string;
|
||||||
@ -155,7 +155,7 @@ interface CardProps {
|
|||||||
titleRight?: React.ReactNode;
|
titleRight?: React.ReactNode;
|
||||||
coverLeft?: React.ReactNode;
|
coverLeft?: React.ReactNode;
|
||||||
coverRight?: React.ReactNode;
|
coverRight?: React.ReactNode;
|
||||||
actions: React.ReactNode | null;
|
actions?: React.ReactNode | null;
|
||||||
rows?: number | string;
|
rows?: number | string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
cover?: React.ReactNode | null;
|
cover?: React.ReactNode | null;
|
||||||
|
@ -20,7 +20,7 @@ import React, { useMemo, useState, useCallback } from 'react';
|
|||||||
import { SupersetClient, t, styled } from '@superset-ui/core';
|
import { SupersetClient, t, styled } from '@superset-ui/core';
|
||||||
import moment from 'moment';
|
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 withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||||
import { useListViewResource } from 'src/views/CRUD/hooks';
|
import { useListViewResource } from 'src/views/CRUD/hooks';
|
||||||
import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu';
|
import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu';
|
||||||
@ -38,6 +38,7 @@ import { QueryObject } from 'src/views/CRUD/types';
|
|||||||
import QueryPreviewModal from './QueryPreviewModal';
|
import QueryPreviewModal from './QueryPreviewModal';
|
||||||
|
|
||||||
const PAGE_SIZE = 25;
|
const PAGE_SIZE = 25;
|
||||||
|
const SQL_PREVIEW_MAX_LINES = 4;
|
||||||
|
|
||||||
const TopAlignedListView = styled(ListView)<ListViewProps<QueryObject>>`
|
const TopAlignedListView = styled(ListView)<ListViewProps<QueryObject>>`
|
||||||
table .table-cell {
|
table .table-cell {
|
||||||
@ -52,15 +53,7 @@ const StyledSyntaxHighlighter = styled(SyntaxHighlighter)`
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
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 {
|
interface QueryListProps {
|
||||||
addDangerToast: (msg: string, config?: any) => any;
|
addDangerToast: (msg: string, config?: any) => any;
|
||||||
addSuccessToast: (msg: string, config?: any) => any;
|
addSuccessToast: (msg: string, config?: any) => any;
|
||||||
@ -298,7 +291,7 @@ function QueryList({ addDangerToast, addSuccessToast }: QueryListProps) {
|
|||||||
onClick={() => setQueryCurrentlyPreviewing(original)}
|
onClick={() => setQueryCurrentlyPreviewing(original)}
|
||||||
>
|
>
|
||||||
<StyledSyntaxHighlighter language="sql" style={github}>
|
<StyledSyntaxHighlighter language="sql" style={github}>
|
||||||
{shortenSQL(original.sql)}
|
{shortenSQL(original.sql, SQL_PREVIEW_MAX_LINES)}
|
||||||
</StyledSyntaxHighlighter>
|
</StyledSyntaxHighlighter>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -264,22 +264,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('...');
|
||||||
|
}
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
const breakpoints = [576, 768, 992, 1200];
|
const breakpoints = [576, 768, 992, 1200];
|
||||||
export const mq = breakpoints.map(bp => `@media (max-width: ${bp}px)`);
|
export const mq = breakpoints.map(bp => `@media (max-width: ${bp}px)`);
|
||||||
|
|
||||||
export const CardContainer = styled.div`
|
export const CardContainer = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(31%, max-content));
|
grid-template-columns: repeat(auto-fit, minmax(31%, 31%));
|
||||||
${[mq[3]]} {
|
${[mq[3]]} {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(31%, max-content));
|
grid-template-columns: repeat(auto-fit, minmax(31%, 31%));
|
||||||
}
|
}
|
||||||
|
|
||||||
${[mq[2]]} {
|
${[mq[2]]} {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(48%, max-content));
|
grid-template-columns: repeat(auto-fit, minmax(48%, 48%));
|
||||||
}
|
}
|
||||||
|
|
||||||
${[mq[1]]} {
|
${[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;
|
grid-gap: ${({ theme }) => theme.gridUnit * 8}px;
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { t, SupersetClient, styled } from '@superset-ui/core';
|
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 withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||||
import { Dropdown, Menu } from 'src/common/components';
|
import { Dropdown, Menu } from 'src/common/components';
|
||||||
import { useListViewResource, copyQueryLink } from 'src/views/CRUD/hooks';
|
import { useListViewResource, copyQueryLink } from 'src/views/CRUD/hooks';
|
||||||
@ -30,9 +33,11 @@ import {
|
|||||||
IconContainer,
|
IconContainer,
|
||||||
CardContainer,
|
CardContainer,
|
||||||
createErrorHandler,
|
createErrorHandler,
|
||||||
CardStyles,
|
shortenSQL,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
|
||||||
|
SyntaxHighlighter.registerLanguage('sql', sql);
|
||||||
|
|
||||||
const PAGE_SIZE = 3;
|
const PAGE_SIZE = 3;
|
||||||
|
|
||||||
interface Query {
|
interface Query {
|
||||||
@ -45,6 +50,8 @@ interface Query {
|
|||||||
description?: string;
|
description?: string;
|
||||||
end_time?: string;
|
end_time?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
changed_on_delta_humanized?: string;
|
||||||
|
sql?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SavedQueriesProps {
|
interface SavedQueriesProps {
|
||||||
@ -57,19 +64,51 @@ interface SavedQueriesProps {
|
|||||||
mine: Array<Query>;
|
mine: Array<Query>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QueryData = styled.div`
|
export const CardStyles = styled.div`
|
||||||
display: flex;
|
cursor: pointer;
|
||||||
flex-direction: row;
|
a {
|
||||||
justify-content: flex-start;
|
text-decoration: none;
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
|
|
||||||
.title {
|
|
||||||
font-weight: ${({ theme }) => theme.typography.weights.normal};
|
|
||||||
color: ${({ theme }) => theme.colors.grayscale.light1};
|
|
||||||
}
|
}
|
||||||
.holder {
|
.ant-card-cover {
|
||||||
margin: ${({ theme }) => theme.gridUnit * 2}px;
|
border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
|
||||||
|
& > div {
|
||||||
|
height: 171px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.gradient-container > div {
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-color: ${({ theme }) => theme.colors.secondary.light3};
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
height: 179px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const QueryData = styled.div`
|
||||||
|
svg {
|
||||||
|
margin-left: ${({ theme }) => theme.gridUnit * 10}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;
|
||||||
|
background-color: ${({ theme }) =>
|
||||||
|
theme.colors.grayscale.light5} !important;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: ${({ theme }) => theme.gridUnit * 4}px !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const SavedQueries = ({
|
const SavedQueries = ({
|
||||||
user,
|
user,
|
||||||
addDangerToast,
|
addDangerToast,
|
||||||
@ -259,25 +298,39 @@ const SavedQueries = ({
|
|||||||
key={q.id}
|
key={q.id}
|
||||||
>
|
>
|
||||||
<ListViewCard
|
<ListViewCard
|
||||||
imgFallbackURL=""
|
|
||||||
imgURL=""
|
imgURL=""
|
||||||
url={`/superset/sqllab?savedQueryId=${q.id}`}
|
url={`/superset/sqllab?savedQueryId=${q.id}`}
|
||||||
title={q.label}
|
title={q.label}
|
||||||
rows={q.rows}
|
imgFallbackURL="/static/assets/images/empty-query.svg"
|
||||||
description={t('Last run ', q.end_time)}
|
description={t('Last run %s', q.changed_on_delta_humanized)}
|
||||||
cover={
|
cover={
|
||||||
<QueryData>
|
q?.sql?.length ? (
|
||||||
<div className="holder">
|
<QueryContainer>
|
||||||
<div className="title">{t('Tables')}</div>
|
<SyntaxHighlighter
|
||||||
<div>{q?.sql_tables?.length}</div>
|
language="sql"
|
||||||
</div>
|
lineProps={{
|
||||||
<div className="holder">
|
style: {
|
||||||
<div className="title">{t('Datasource Name')}</div>
|
color: 'black',
|
||||||
<div>{q?.sql_tables && q.sql_tables[0]?.table}</div>
|
wordBreak: 'break-all',
|
||||||
</div>
|
whiteSpace: 'pre-wrap',
|
||||||
</QueryData>
|
},
|
||||||
|
}}
|
||||||
|
style={github}
|
||||||
|
wrapLines
|
||||||
|
lineNumberStyle={{
|
||||||
|
display: 'none',
|
||||||
|
}}
|
||||||
|
showLineNumbers={false}
|
||||||
|
>
|
||||||
|
{shortenSQL(q.sql, 25)}
|
||||||
|
</SyntaxHighlighter>
|
||||||
|
</QueryContainer>
|
||||||
|
) : (
|
||||||
|
false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
|
<QueryData>
|
||||||
<ListViewCard.Actions
|
<ListViewCard.Actions
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -288,6 +341,7 @@ const SavedQueries = ({
|
|||||||
<Icon name="more-horiz" />
|
<Icon name="more-horiz" />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</ListViewCard.Actions>
|
</ListViewCard.Actions>
|
||||||
|
</QueryData>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</CardStyles>
|
</CardStyles>
|
||||||
|
Loading…
Reference in New Issue
Block a user