| /** |
| * 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, { 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'; |
| import ListViewCard from 'src/components/ListViewCard'; |
| import DeleteModal from 'src/components/DeleteModal'; |
| import Icon from 'src/components/Icon'; |
| import SubMenu from 'src/components/Menu/SubMenu'; |
| import EmptyState from './EmptyState'; |
| import { CardContainer, createErrorHandler, shortenSQL } from '../utils'; |
| |
| SyntaxHighlighter.registerLanguage('sql', sql); |
| |
| const PAGE_SIZE = 3; |
| |
| interface Query { |
| id?: number; |
| sql_tables?: Array<any>; |
| database?: { |
| database_name: string; |
| }; |
| rows?: string; |
| description?: string; |
| end_time?: string; |
| label?: string; |
| changed_on_delta_humanized?: string; |
| sql?: string | null; |
| } |
| |
| interface SavedQueriesProps { |
| user: { |
| userId: string | number; |
| }; |
| queryFilter: string; |
| addDangerToast: (arg0: string) => void; |
| addSuccessToast: (arg0: string) => void; |
| 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; |
| } |
| } |
| .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 = ({ |
| user, |
| addDangerToast, |
| addSuccessToast, |
| mine, |
| }: SavedQueriesProps) => { |
| const { |
| state: { resourceCollection: queries }, |
| hasPerm, |
| fetchData, |
| refreshData, |
| } = useListViewResource<Query>( |
| 'saved_query', |
| t('query'), |
| addDangerToast, |
| true, |
| mine, |
| ); |
| const [queryFilter, setQueryFilter] = useState('Mine'); |
| const [queryDeleteModal, setQueryDeleteModal] = useState(false); |
| const [currentlyEdited, setCurrentlyEdited] = useState<Query>({}); |
| const [ifMine, setMine] = useState(true); |
| const canEdit = hasPerm('can_edit'); |
| const canDelete = hasPerm('can_delete'); |
| |
| const handleQueryDelete = ({ id, label }: Query) => { |
| SupersetClient.delete({ |
| endpoint: `/api/v1/saved_query/${id}`, |
| }).then( |
| () => { |
| const queryParams = { |
| filters: [ |
| { |
| id: 'created_by', |
| operator: 'rel_o_m', |
| value: `${user?.userId}`, |
| }, |
| ], |
| pageSize: PAGE_SIZE, |
| sortBy: [ |
| { |
| id: 'changed_on_delta_humanized', |
| desc: true, |
| }, |
| ], |
| pageIndex: 0, |
| }; |
| // if mine is default there refresh data with current filters |
| const filter = ifMine ? queryParams : undefined; |
| refreshData(filter); |
| setMine(false); |
| setQueryDeleteModal(false); |
| addSuccessToast(t('Deleted: %s', label)); |
| }, |
| createErrorHandler(errMsg => |
| addDangerToast(t('There was an issue deleting %s: %s', label, errMsg)), |
| ), |
| ); |
| }; |
| |
| const getFilters = (filterName: string) => { |
| const filters = []; |
| if (filterName === 'Mine') { |
| filters.push({ |
| id: 'created_by', |
| operator: 'rel_o_m', |
| value: `${user?.userId}`, |
| }); |
| } else { |
| filters.push({ |
| id: 'id', |
| operator: 'saved_query_is_fav', |
| value: true, |
| }); |
| } |
| return filters; |
| }; |
| |
| const getData = (filter: string) => |
| fetchData({ |
| pageIndex: 0, |
| pageSize: PAGE_SIZE, |
| sortBy: [ |
| { |
| id: 'changed_on_delta_humanized', |
| desc: true, |
| }, |
| ], |
| filters: getFilters(filter), |
| }); |
| |
| const renderMenu = (query: Query) => ( |
| <Menu> |
| {canEdit && ( |
| <Menu.Item |
| onClick={() => { |
| window.location.href = `/superset/sqllab?savedQueryId=${query.id}`; |
| }} |
| > |
| {t('Edit')} |
| </Menu.Item> |
| )} |
| <Menu.Item |
| onClick={() => { |
| if (query.id) { |
| copyQueryLink(query.id, addDangerToast, addSuccessToast); |
| } |
| }} |
| > |
| {t('Share')} |
| </Menu.Item> |
| {canDelete && ( |
| <Menu.Item |
| onClick={() => { |
| setQueryDeleteModal(true); |
| setCurrentlyEdited(query); |
| }} |
| > |
| {t('Delete')} |
| </Menu.Item> |
| )} |
| </Menu> |
| ); |
| return ( |
| <> |
| {queryDeleteModal && ( |
| <DeleteModal |
| description={t( |
| 'This action will permanently delete the saved query.', |
| )} |
| onConfirm={() => { |
| if (queryDeleteModal) { |
| handleQueryDelete(currentlyEdited); |
| } |
| }} |
| onHide={() => { |
| setQueryDeleteModal(false); |
| }} |
| open |
| title={t('Delete Query?')} |
| /> |
| )} |
| <SubMenu |
| activeChild={queryFilter} |
| tabs={[ |
| /* @TODO uncomment when fav functionality is implemented |
| { |
| name: 'Favorite', |
| label: t('Favorite'), |
| onClick: () => { |
| getData('Favorite').then(() => setQueryFilter('Favorite')); |
| }, |
| }, |
| */ |
| { |
| name: 'Mine', |
| label: t('Mine'), |
| onClick: () => getData('Mine').then(() => setQueryFilter('Mine')), |
| }, |
| ]} |
| buttons={[ |
| { |
| name: ( |
| <div> |
| <i className="fa fa-plus" /> |
| SQL Query{' '} |
| </div> |
| ), |
| buttonStyle: 'tertiary', |
| onClick: () => { |
| window.location.href = '/superset/sqllab'; |
| }, |
| }, |
| { |
| name: 'View All ยป', |
| buttonStyle: 'link', |
| onClick: () => { |
| window.location.href = '/savedqueryview/list'; |
| }, |
| }, |
| ]} |
| /> |
| {queries.length > 0 ? ( |
| <CardContainer> |
| {queries.map(q => ( |
| <CardStyles |
| onClick={() => { |
| window.location.href = `/superset/sqllab?savedQueryId=${q.id}`; |
| }} |
| key={q.id} |
| > |
| <ListViewCard |
| imgURL="" |
| url={`/superset/sqllab?savedQueryId=${q.id}`} |
| title={q.label} |
| imgFallbackURL="/static/assets/images/empty-query.svg" |
| description={t('Last run %s', q.changed_on_delta_humanized)} |
| cover={ |
| 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={ |
| <QueryData> |
| <ListViewCard.Actions |
| onClick={e => { |
| e.stopPropagation(); |
| e.preventDefault(); |
| }} |
| > |
| <Dropdown overlay={renderMenu(q)}> |
| <Icon name="more-horiz" /> |
| </Dropdown> |
| </ListViewCard.Actions> |
| </QueryData> |
| } |
| /> |
| </CardStyles> |
| ))} |
| </CardContainer> |
| ) : ( |
| <EmptyState tableName="SAVED_QUERIES" tab={queryFilter} /> |
| )} |
| </> |
| ); |
| }; |
| |
| export default withToasts(SavedQueries); |