DatasourcePanel.tsx 5.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/**
 * 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';
20
import { styled, t } from '@superset-ui/core';
21 22 23 24
import { Collapse } from 'src/common/components';
import {
  ColumnOption,
  MetricOption,
25 26
  ControlConfig,
  DatasourceMeta,
27
} from '@superset-ui/chart-controls';
28 29
import { debounce } from 'lodash';
import { matchSorter, rankings } from 'match-sorter';
30 31 32
import { ExploreActions } from '../actions/exploreActions';
import Control from './Control';

33 34
interface DatasourceControl extends ControlConfig {
  datasource?: DatasourceMeta;
35 36 37
}

interface Props {
38
  datasource: DatasourceMeta;
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
  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 {
56
    padding: ${({ theme }) => `0 0 ${4 * theme.gridUnit}px`};
57 58 59 60 61 62 63
    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};
  }
64 65
  .form-control.input-md {
    width: calc(100% - ${({ theme }) => theme.gridUnit * 4}px);
66
    margin: ${({ theme }) => theme.gridUnit * 2}px auto;
67 68 69 70 71 72 73 74 75 76 77
  }
  .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;
  }
`;

78 79 80 81 82 83 84 85 86 87 88 89
const LabelContainer = styled.div`
  overflow: hidden;
  text-overflow: ellipsis;

  & > span {
    white-space: nowrap;
  }

  .option-label {
    display: inline;
  }

90 91 92 93 94 95 96 97
  .metric-option {
    & > svg {
      min-width: ${({ theme }) => `${theme.gridUnit * 4}px`};
    }
    & > .option-label {
      overflow: hidden;
      text-overflow: ellipsis;
    }
98 99 100
  }
`;

101
export default function DataSourcePanel({
102 103 104
  datasource,
  controls: { datasource: datasourceControl },
  actions,
105
}: Props) {
106 107 108 109 110
  const { columns, metrics } = datasource;
  const [lists, setList] = useState({
    columns,
    metrics,
  });
111 112

  const search = debounce((value: string) => {
113 114 115 116
    if (value === '') {
      setList({ columns, metrics });
      return;
    }
117
    setList({
118
      columns: matchSorter(columns, value, {
119 120 121 122 123 124 125 126 127 128 129 130 131
        keys: [
          'verbose_name',
          'column_name',
          {
            key: 'description',
            threshold: rankings.CONTAINS,
          },
          {
            key: 'expression',
            threshold: rankings.CONTAINS,
          },
        ],
        keepDiacritics: true,
132 133
      }),
      metrics: matchSorter(metrics, value, {
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
        keys: [
          'verbose_name',
          'metric_name',
          {
            key: 'description',
            threshold: rankings.CONTAINS,
          },
          {
            key: 'expression',
            threshold: rankings.CONTAINS,
          },
        ],
        keepDiacritics: true,
        baseSort: (a, b) =>
          Number(b.item.is_certified) - Number(a.item.is_certified) ||
          String(a.rankedValue).localeCompare(b.rankedValue),
150
      }),
151
    });
152 153
  }, 200);

154 155 156 157 158
  useEffect(() => {
    setList({
      columns,
      metrics,
    });
159
  }, [columns, datasource, metrics]);
160 161 162 163

  const metricSlice = lists.metrics.slice(0, 50);
  const columnSlice = lists.columns.slice(0, 50);

164 165
  const mainBody = (
    <>
166 167
      <input
        type="text"
168 169 170
        onChange={evt => {
          search(evt.target.value);
        }}
171
        className="form-control input-md"
172 173
        placeholder={t('Search Metrics & Columns')}
      />
174 175
      <div className="field-selections">
        <Collapse
176
          bordered
177
          defaultActiveKey={['metrics', 'column']}
178
          expandIconPosition="right"
179
          ghost
180 181
        >
          <Collapse.Panel
182 183
            header={<span className="header">{t('Metrics')}</span>}
            key="metrics"
184 185
          >
            <div className="field-length">
186
              {t(`Showing %s of %s`, metricSlice.length, lists.metrics.length)}
187
            </div>
188
            {metricSlice.map(m => (
189
              <LabelContainer key={m.metric_name} className="column">
190
                <MetricOption metric={m} showType />
191
              </LabelContainer>
192 193 194
            ))}
          </Collapse.Panel>
          <Collapse.Panel
195 196
            header={<span className="header">{t('Columns')}</span>}
            key="column"
197 198
          >
            <div className="field-length">
199
              {t(`Showing %s of %s`, columnSlice.length, lists.columns.length)}
200
            </div>
201
            {columnSlice.map(col => (
202
              <LabelContainer key={col.column_name} className="column">
203
                <ColumnOption column={col} showType />
204
              </LabelContainer>
205 206 207 208
            ))}
          </Collapse.Panel>
        </Collapse>
      </div>
209 210 211 212 213 214 215
    </>
  );

  return (
    <DatasourceContainer>
      <Control {...datasourceControl} name="datasource" actions={actions} />
      {datasource.id != null && mainBody}
216 217
    </DatasourceContainer>
  );
218
}