Superset 0.34 Filter Box实现级联

本文介绍了一种名为FilterBox的组件实现,该组件能够根据用户的选择进行级联操作,适用于复杂的数据筛选场景。文章详细阐述了FilterBox组件的设计思路、属性配置以及如何与日期过滤、数据源过滤等特性结合使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Filter Box实现级联

根据选择实现级联操作
在这里插入图片描述

在这里插入图片描述
Filter Box.jsx

/**
* 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 from 'react';
import PropTypes from 'prop-types';
import VirtualizedSelect from 'react-virtualized-select';
import {Creatable} from 'react-select';
import {Button} from 'react-bootstrap';
import {t} from '@superset-ui/translation';

import DateFilterControl from '../../explore/components/controls/DateFilterControl';
import ControlRow from '../../explore/components/ControlRow';
import Control from '../../explore/components/Control';
import controls from '../../explore/controls';
import OnPasteSelect from '../../components/OnPasteSelect';
import VirtualizedRendererWrap from '../../components/VirtualizedRendererWrap';
import './FilterBox.css';

// maps control names to their key in extra_filters
const TIME_FILTER_MAP = {
   time_range: '__time_range',
   granularity_sqla: '__time_col',
   time_grain_sqla: '__time_grain',
   druid_time_origin: '__time_origin',
   granularity: '__granularity',
};

const TIME_RANGE = '__time_range';

const propTypes = {
   origSelectedValues: PropTypes.object,
   datasource: PropTypes.object.isRequired,
   instantFiltering: PropTypes.bool,
   filtersFields: PropTypes.arrayOf(PropTypes.shape({
       field: PropTypes.string,
       label: PropTypes.string,
   })),
   filtersChoices: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.shape({
       id: PropTypes.string,
       text: PropTypes.string,
       filter: PropTypes.string,
       metric: PropTypes.number,
   }))),
   onChange: PropTypes.func,
   showDateFilter: PropTypes.bool,
   showSqlaTimeGrain: PropTypes.bool,
   showSqlaTimeColumn: PropTypes.bool,
   showDruidTimeGrain: PropTypes.bool,
   showDruidTimeOrigin: PropTypes.bool,
};
const defaultProps = {
   origSelectedValues: {},
   onChange: () => {
   },
   showDateFilter: false,
   showSqlaTimeGrain: false,
   showSqlaTimeColumn: false,
   showDruidTimeGrain: false,
   showDruidTimeOrigin: false,
   instantFiltering: true,
};

class FilterBox extends React.Component {

   constructor(props) {
       super(props);
       this.state = {
           selectedValues: props.origSelectedValues,
           hasChanged: false,
       };

       this.changeFilter = this.changeFilter.bind(this);
   }

   getControlData(controlName) {
       const {selectedValues} = this.state;
       const control = Object.assign({}, controls[controlName], {
           name: controlName,
           key: `control-${controlName}`,
           value: selectedValues[TIME_FILTER_MAP[controlName]],
           actions: {setControlValue: this.changeFilter},
       });
       const mapFunc = control.mapStateToProps;
       return mapFunc
           ? Object.assign({}, control, mapFunc(this.props))
           : control;
   }

   clickApply() {
       const {selectedValues} = this.state;
       Object.keys(selectedValues).forEach((fltr, i, arr) => {
           let refresh = false;
           if (i === arr.length - 1) {
               refresh = true;
           }
           this.props.onChange(fltr, selectedValues[fltr], false, refresh);
       });
       this.setState({hasChanged: false});
   }

   changeFilter(filter, options) {
       const fltr = TIME_FILTER_MAP[filter] || filter;
       let vals = null;
       if (options !== null) {
           if (Array.isArray(options)) {
               vals = options.map(opt => opt.value);
           } else if (options.value) {
               vals = options.value;
           } else {
               vals = options;
           }
       }


       const selectedValues = Object.assign({}, this.state.selectedValues);
       selectedValues[fltr] = vals;


       this.setState({selectedValues, hasChanged: true});


       if (this.props.instantFiltering) {
           this.props.onChange(fltr, vals, false, true);
       }
   }

   renderDateFilter() {
       const {showDateFilter} = this.props;
       if (showDateFilter) {
           return (
               <div className="row space-1">
                   <div className="col-lg-12 col-xs-12">
                       <DateFilterControl
                           name={TIME_RANGE}
                           label={t('Time range')}
                           description={t('Select start and end date')}
                           onChange={(...args) => {
                               this.changeFilter(TIME_RANGE, ...args);
                           }}
                           value={this.state.selectedValues[TIME_RANGE] || 'No filter'}
                       />
                   </div>
               </div>
           );
       }
       return null;
   }

   renderDatasourceFilters() {
       const {
           showSqlaTimeGrain,
           showSqlaTimeColumn,
           showDruidTimeGrain,
           showDruidTimeOrigin,
       } = this.props;
       const datasourceFilters = [];
       const sqlaFilters = [];
       const druidFilters = [];
       if (showSqlaTimeGrain) sqlaFilters.push('time_grain_sqla');
       if (showSqlaTimeColumn) sqlaFilters.push('granularity_sqla');
       if (showDruidTimeGrain) druidFilters.push('granularity');
       if (showDruidTimeOrigin) druidFilters.push('druid_time_origin');
       if (sqlaFilters.length) {
           datasourceFilters.push(
               <ControlRow
                   key="sqla-filters"
                   className="control-row"
                   controls={sqlaFilters.map(control => (
                       <Control {...this.getControlData(control)} />
                   ))}
               />,
           );
       }
       if (druidFilters.length) {
           datasourceFilters.push(
               <ControlRow
                   key="druid-filters"
                   className="control-row"
                   controls={druidFilters.map(control => (
                       <Control {...this.getControlData(control)} />
                   ))}
               />,
           );
       }
       return datasourceFilters;
   }

   renderSelect(filterConfig) {
       const {filtersChoices} = this.props;
       const {selectedValues} = this.state;


       // Add created options to filtersChoices, even though it doesn't exist,
       // or these options will exist in query sql but invisible to end user.
       Object.keys(selectedValues)
           .filter(key => selectedValues.hasOwnProperty(key) && (key in filtersChoices))
           .forEach((key) => {
               const choices = filtersChoices[key] || [];
               const choiceIds = new Set(choices.map(f => f.id));
               const selectedValuesForKey = Array.isArray(selectedValues[key])
                   ? selectedValues[key]
                   : [selectedValues[key]];
               selectedValuesForKey
                   .filter(value => !choiceIds.has(value))
                   .forEach((value) => {
                       choices.unshift({
                           filter: key,
                           id: value,
                           text: value,
                           metric: 0,
                       });
                   });
           });

       const {key, label} = filterConfig;
       const data = this.props.filtersChoices[key];

       
       let datax = [];
       let midKey = 'xx';
       let flag = true;

       if (JSON.stringify(selectedValues) === '{}') {
           datax = data;
       } else {
           for (var xkey in selectedValues) {
               if (xkey == key) {
                   break;
               }
               midKey = selectedValues[xkey];
               flag = false;
           }
       }



       let setx = new Set();
       if (flag) {
           datax = data;
       } else {
           data.map(objx => {
               if(JSON.stringify(midKey).indexOf('[') != -1){
                   midKey.map(opt => {
                       if (objx['dep'] == opt) {
                           setx.add(objx['id']);
                       }
                   });
               }else{
                   if (objx['dep'] == midKey) {
                       setx.add(objx['id']);
                   }
               }
           });

           for(let xvalue of setx){
               datax.push({'id': xvalue,'text': xvalue});
           }

       }



       const max = Math.max(...data.map(d => d.metric));
       let value = selectedValues[key] || null;

       // Assign default value if required
       if (!value && filterConfig.defaultValue) {
           if (filterConfig.multiple) {
               // Support for semicolon-delimited multiple values
               value = filterConfig.defaultValue.split(';');
           } else {
               value = filterConfig.defaultValue;
           }
       }

       return (
           <OnPasteSelect
               placeholder={t('Select [%s]', label)}
               key={key}
               multi={filterConfig.multiple}
               clearable={filterConfig.clearable}
               value={value}
               options={datax.map((opt) => {
                   const perc = Math.round((opt.metric / max) * 100);
                   const backgroundImage = (
                       'linear-gradient(to right, lightgrey, ' +
                       `lightgrey ${perc}%, rgba(0,0,0,0) ${perc}%`
                   );
                   const style = {
                       backgroundImage,
                       padding: '2px 5px',
                   };
                   return {value: opt.id, label: opt.id, style};
               })}
               onChange={(...args) => {
                   this.changeFilter(key, ...args);
               }}
               selectComponent={Creatable}
               selectWrap={VirtualizedSelect}
               optionRenderer={VirtualizedRendererWrap(opt => opt.label)}
               noResultsText={t('No results found')}
           />);
   }

   renderFilters() {

       const {filtersFields} = this.props;

       return filtersFields.map((filterConfig) => {
           const {label, key} = filterConfig;
           return (
               <div key={key} className="m-b-5">
                   {label}
                   {this.renderSelect(filterConfig)}
               </div>
           );
       });
   }

   render() {
       const {instantFiltering} = this.props;

       return (
           <div className="scrollbar-container">
               <div className="scrollbar-content">
                   {this.renderDateFilter()}
                   {this.renderDatasourceFilters()}
                   {this.renderFilters()}
                   {!instantFiltering &&
                   <Button
                       bsSize="small"
                       bsStyle="primary"
                       onClick={this.clickApply.bind(this)}
                       disabled={!this.state.hasChanged}
                   >
                       {t('Apply')}
                   </Button>
                   }
               </div>
           </div>
       );
   }
}

FilterBox.propTypes = propTypes;
FilterBox.defaultProps = defaultProps;

export default FilterBox;

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值