| /** |
| * 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, { useEffect, useState } from 'react'; |
| import { styled, t } from '@superset-ui/core'; |
| import Collapse from 'src/common/components/Collapse'; |
| import { |
| ColumnOption, |
| MetricOption, |
| ControlConfig, |
| DatasourceMeta, |
| } from '@superset-ui/chart-controls'; |
| import { debounce } from 'lodash'; |
| import { matchSorter, rankings } from 'match-sorter'; |
| import { FAST_DEBOUNCE } from 'src/constants'; |
| import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; |
| import { ExploreActions } from 'src/explore/actions/exploreActions'; |
| import Control from 'src/explore/components/Control'; |
| import DatasourcePanelDragWrapper from './DatasourcePanelDragWrapper'; |
| import { DatasourcePanelDndType } from './types'; |
| |
| interface DatasourceControl extends ControlConfig { |
| datasource?: DatasourceMeta; |
| } |
| |
| interface Props { |
| datasource: DatasourceMeta; |
| controls: { |
| datasource: DatasourceControl; |
| }; |
| actions: Partial<ExploreActions> & Pick<ExploreActions, 'setControlValue'>; |
| } |
| |
| const DatasourceContainer = styled.div` |
| background-color: ${({ theme }) => theme.colors.grayscale.light4}; |
| position: relative; |
| height: 100%; |
| display: flex; |
| flex-direction: column; |
| max-height: 100%; |
| .ant-collapse { |
| height: auto; |
| } |
| .field-selections { |
| padding: ${({ theme }) => `0 0 ${4 * theme.gridUnit}px`}; |
| overflow: auto; |
| } |
| .field-length { |
| margin-bottom: ${({ theme }) => theme.gridUnit * 2}px; |
| font-size: ${({ theme }) => theme.typography.sizes.s}px; |
| color: ${({ theme }) => theme.colors.grayscale.light1}; |
| } |
| .form-control.input-md { |
| width: calc(100% - ${({ theme }) => theme.gridUnit * 4}px); |
| margin: ${({ theme }) => theme.gridUnit * 2}px auto; |
| } |
| .type-label { |
| font-weight: ${({ theme }) => theme.typography.weights.light}; |
| font-size: ${({ theme }) => theme.typography.sizes.s}px; |
| color: ${({ theme }) => theme.colors.grayscale.base}; |
| } |
| .Control { |
| padding-bottom: 0; |
| } |
| `; |
| |
| const LabelContainer = styled.div` |
| overflow: hidden; |
| text-overflow: ellipsis; |
| |
| & > span { |
| white-space: nowrap; |
| } |
| |
| .option-label { |
| display: inline; |
| } |
| |
| .metric-option { |
| & > svg { |
| min-width: ${({ theme }) => `${theme.gridUnit * 4}px`}; |
| } |
| & > .option-label { |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| } |
| `; |
| |
| const enableExploreDnd = isFeatureEnabled( |
| FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP, |
| ); |
| |
| export default function DataSourcePanel({ |
| datasource, |
| controls: { datasource: datasourceControl }, |
| actions, |
| }: Props) { |
| const { columns, metrics } = datasource; |
| const [inputValue, setInputValue] = useState(''); |
| const [lists, setList] = useState({ |
| columns, |
| metrics, |
| }); |
| |
| const search = debounce((value: string) => { |
| if (value === '') { |
| setList({ columns, metrics }); |
| return; |
| } |
| setList({ |
| columns: matchSorter(columns, value, { |
| keys: [ |
| { |
| key: 'verbose_name', |
| threshold: rankings.CONTAINS, |
| }, |
| { |
| key: 'column_name', |
| threshold: rankings.CONTAINS, |
| }, |
| { |
| key: item => |
| [item.description, item.expression].map( |
| x => x?.replace(/[_\n\s]+/g, ' ') || '', |
| ), |
| threshold: rankings.CONTAINS, |
| maxRanking: rankings.CONTAINS, |
| }, |
| ], |
| keepDiacritics: true, |
| }), |
| metrics: matchSorter(metrics, value, { |
| keys: [ |
| { |
| key: 'verbose_name', |
| threshold: rankings.CONTAINS, |
| }, |
| { |
| key: 'metric_name', |
| threshold: rankings.CONTAINS, |
| }, |
| { |
| key: item => |
| [item.description, item.expression].map( |
| x => x?.replace(/[_\n\s]+/g, ' ') || '', |
| ), |
| threshold: rankings.CONTAINS, |
| maxRanking: rankings.CONTAINS, |
| }, |
| ], |
| keepDiacritics: true, |
| baseSort: (a, b) => |
| Number(b.item.is_certified) - Number(a.item.is_certified) || |
| String(a.rankedValue).localeCompare(b.rankedValue), |
| }), |
| }); |
| }, FAST_DEBOUNCE); |
| |
| useEffect(() => { |
| setList({ |
| columns, |
| metrics, |
| }); |
| setInputValue(''); |
| }, [columns, datasource, metrics]); |
| |
| const metricSlice = lists.metrics.slice(0, 50); |
| const columnSlice = lists.columns.slice(0, 50); |
| |
| const mainBody = ( |
| <> |
| <input |
| type="text" |
| onChange={evt => { |
| setInputValue(evt.target.value); |
| search(evt.target.value); |
| }} |
| value={inputValue} |
| className="form-control input-md" |
| placeholder={t('Search Metrics & Columns')} |
| /> |
| <div className="field-selections"> |
| <Collapse |
| bordered |
| defaultActiveKey={['metrics', 'column']} |
| expandIconPosition="right" |
| ghost |
| > |
| <Collapse.Panel |
| header={<span className="header">{t('Metrics')}</span>} |
| key="metrics" |
| > |
| <div className="field-length"> |
| {t(`Showing %s of %s`, metricSlice.length, lists.metrics.length)} |
| </div> |
| {metricSlice.map(m => ( |
| <LabelContainer key={m.metric_name} className="column"> |
| {enableExploreDnd ? ( |
| <DatasourcePanelDragWrapper |
| metricOrColumnName={m.metric_name} |
| type={DatasourcePanelDndType.METRIC} |
| > |
| <MetricOption metric={m} showType /> |
| </DatasourcePanelDragWrapper> |
| ) : ( |
| <MetricOption metric={m} showType /> |
| )} |
| </LabelContainer> |
| ))} |
| </Collapse.Panel> |
| <Collapse.Panel |
| header={<span className="header">{t('Columns')}</span>} |
| key="column" |
| > |
| <div className="field-length"> |
| {t(`Showing %s of %s`, columnSlice.length, lists.columns.length)} |
| </div> |
| {columnSlice.map(col => ( |
| <LabelContainer key={col.column_name} className="column"> |
| {enableExploreDnd ? ( |
| <DatasourcePanelDragWrapper |
| metricOrColumnName={col.column_name} |
| type={DatasourcePanelDndType.COLUMN} |
| > |
| <ColumnOption column={col} showType /> |
| </DatasourcePanelDragWrapper> |
| ) : ( |
| <ColumnOption column={col} showType /> |
| )} |
| </LabelContainer> |
| ))} |
| </Collapse.Panel> |
| </Collapse> |
| </div> |
| </> |
| ); |
| |
| return ( |
| <DatasourceContainer> |
| <Control {...datasourceControl} name="datasource" actions={actions} /> |
| {datasource.id != null && mainBody} |
| </DatasourceContainer> |
| ); |
| } |