-
Notifications
You must be signed in to change notification settings - Fork 916
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds the data source selector and a useIndexPatterns hook
Signed-off-by: Brooke Green <[email protected]>
- Loading branch information
Showing
13 changed files
with
393 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
@import '@elastic/eui/src/global_styling/variables/header'; | ||
@import '@elastic/eui/src/global_styling/variables/form'; | ||
|
||
$osdHeaderOffset: $euiHeaderHeightCompensation * 2; | ||
$osdHeaderOffset: $euiHeaderHeightCompensation * 2; | ||
$osdDropdownNegativePadding: $euiFormControlPadding * -1; | ||
$osdDropdownPadding: $euiFormControlPadding; | ||
$wizSideNavWidth: 470px; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
src/plugins/wizard/public/application/components/data_source_select.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
@import "../variables"; | ||
|
||
.wizDatasourceSelect { | ||
max-width: 469px; | ||
} |
52 changes: 52 additions & 0 deletions
52
src/plugins/wizard/public/application/components/data_source_select.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import React from 'react'; | ||
import { i18n } from '@osd/i18n'; | ||
import { EuiIcon } from '@elastic/eui'; | ||
import { SearchableDropdown, SearchableDropdownOption } from './searchable_dropdown'; | ||
import { useIndexPatterns } from '../utils/use'; | ||
import './data_source_select.scss'; | ||
import indexPatternSvg from '../../assets/index_pattern.svg'; | ||
import { useTypedDispatch } from '../utils/state_management'; | ||
import { setIndexPattern } from '../utils/state_management/visualization_slice'; | ||
import { IndexPattern } from '../../../../data/public'; | ||
|
||
function indexPatternEquality(A?: SearchableDropdownOption, B?: SearchableDropdownOption): boolean { | ||
return !A || !B ? false : A.id === B.id; | ||
} | ||
|
||
function toSearchableDropdownOption(indexPattern: IndexPattern): SearchableDropdownOption { | ||
return { | ||
id: indexPattern.id || '', | ||
label: indexPattern.title, | ||
searchableLabel: indexPattern.title, | ||
prepend: <EuiIcon type={indexPatternSvg} />, | ||
}; | ||
} | ||
|
||
export const DataSourceSelect = () => { | ||
const { indexPatterns, loading, error, selected } = useIndexPatterns(); | ||
const dispatch = useTypedDispatch(); | ||
|
||
return ( | ||
<SearchableDropdown | ||
selected={selected !== undefined ? toSearchableDropdownOption(selected) : undefined} | ||
onChange={(option) => { | ||
const foundOption = indexPatterns.filter((s) => s.id === option.id)[0]; | ||
if (foundOption !== undefined && typeof foundOption.id === 'string') { | ||
dispatch(setIndexPattern(foundOption.id)); | ||
} | ||
}} | ||
prepend={i18n.translate('wizard.nav.dataSource.selector.title', { | ||
defaultMessage: 'Data Source', | ||
})} | ||
error={error} | ||
loading={loading} | ||
options={indexPatterns.map(toSearchableDropdownOption)} | ||
equality={indexPatternEquality} | ||
/> | ||
); | ||
}; |
26 changes: 26 additions & 0 deletions
26
src/plugins/wizard/public/application/components/searchable_dropdown.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
@import "../variables"; | ||
|
||
.searchableDropdown { | ||
overflow: "hidden"; | ||
} | ||
|
||
.searchableDropdown .euiPopover, | ||
.searchableDropdown .euiPopover__anchor { | ||
width: 100%; | ||
} | ||
|
||
.searchableDropdown--fixedWidthChild { | ||
width: calc(#{$wizSideNavWidth} - #{$euiSizeXL} * 2) ; | ||
} | ||
|
||
.searchableDropdown--topDisplay { | ||
padding-right: $euiSizeL; | ||
} | ||
|
||
|
||
.searchableDropdown--selectableWrapper .euiSelectableList { | ||
// When clicking on the selectable content it will "highlight" itself with a box shadow | ||
// This turns that off | ||
box-shadow: none !important; | ||
margin: $osdDropdownNegativePadding - 4; | ||
} |
170 changes: 170 additions & 0 deletions
170
src/plugins/wizard/public/application/components/searchable_dropdown.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import React, { useState, useEffect } from 'react'; | ||
import { | ||
EuiLoadingSpinner, | ||
EuiFormControlLayout, | ||
EuiPopoverTitle, | ||
EuiButtonEmpty, | ||
EuiPopover, | ||
EuiSelectable, | ||
EuiTextColor, | ||
} from '@elastic/eui'; | ||
import './searchable_dropdown.scss'; | ||
|
||
export interface SearchableDropdownOption { | ||
id: string; | ||
label: string; | ||
searchableLabel: string; | ||
prepend: any; | ||
} | ||
|
||
interface SearchableDropdownProps { | ||
selected?: SearchableDropdownOption; | ||
onChange: (selection) => void; | ||
options: SearchableDropdownOption[]; | ||
loading: boolean; | ||
error?: Error; | ||
prepend: string; | ||
// not just the first time! | ||
onOpen?: () => void; | ||
equality: (A, B) => boolean; | ||
} | ||
|
||
type DisplayError = any; | ||
|
||
function displayError(error: DisplayError) { | ||
return typeof error === 'object' ? error.toString() : <>{error}</>; | ||
} | ||
|
||
export const SearchableDropdown = ({ | ||
onChange, | ||
equality, | ||
selected, | ||
options, | ||
error, | ||
loading, | ||
prepend, | ||
onOpen, | ||
}: SearchableDropdownProps) => { | ||
const [localOptions, setLocalOptions] = useState<any[] | undefined>(undefined); | ||
const [isPopoverOpen, setIsPopoverOpen] = useState(false); | ||
const onButtonClick = () => { | ||
if (!isPopoverOpen && typeof onOpen === 'function') { | ||
onOpen(); | ||
} | ||
setIsPopoverOpen(!isPopoverOpen); | ||
}; | ||
const closePopover = () => setIsPopoverOpen(false); | ||
|
||
function selectNewOption(newOptions) { | ||
// alright, the EUI Selectable is pretty ratchet | ||
// this is as smarmy as it is because it needs to be | ||
|
||
// first go through and count all the "checked" options | ||
const selectedCount = newOptions.filter((o) => o.checked === 'on').length; | ||
|
||
// if the count is 0, the user just "unchecked" our selection and we can just do nothing | ||
if (selectedCount === 0) { | ||
setIsPopoverOpen(false); | ||
return; | ||
} | ||
|
||
// then, if there's more than two selections, the Selectable left the previous selection as "checked" | ||
// so we need to go and "uncheck" it | ||
for (let i = 0; i < newOptions.length; i++) { | ||
if (equality(newOptions[i], selected) && selectedCount > 1) { | ||
delete newOptions[i].checked; | ||
} | ||
} | ||
|
||
// finally, we can pick the checked option as the actual selection | ||
const newSelection = newOptions.filter((o) => o.checked === 'on')[0]; | ||
|
||
setLocalOptions(newOptions); | ||
setIsPopoverOpen(false); | ||
onChange(newSelection); | ||
} | ||
|
||
useEffect(() => { | ||
setLocalOptions( | ||
options.map((o) => ({ | ||
...o, | ||
checked: equality(o, selected) ? 'on' : undefined, | ||
})) | ||
); | ||
}, [selected, options, equality]); | ||
|
||
const listDisplay = (list, search) => | ||
loading ? ( | ||
<div style={{ textAlign: 'center' }}> | ||
<EuiLoadingSpinner /> | ||
</div> | ||
) : error !== undefined ? ( | ||
displayError(error) | ||
) : ( | ||
<> | ||
<EuiPopoverTitle paddingSize="s" className="wizPopoverTitle"> | ||
{search} | ||
</EuiPopoverTitle> | ||
{list} | ||
</> | ||
); | ||
|
||
const selectable = ( | ||
<div className="searchableDropdown--selectableWrapper"> | ||
<EuiSelectable | ||
aria-label="Selectable options" | ||
searchable | ||
options={localOptions} | ||
onChange={selectNewOption} | ||
listProps={{ | ||
showIcons: false, | ||
}} | ||
> | ||
{listDisplay} | ||
</EuiSelectable> | ||
</div> | ||
); | ||
|
||
const selectedText = | ||
selected === undefined ? ( | ||
<EuiTextColor color="subdued">{loading ? 'Loading' : 'Select an option'}</EuiTextColor> | ||
) : ( | ||
<> | ||
{selected.prepend} {selected.label} | ||
</> | ||
); | ||
|
||
const selectedView = ( | ||
<EuiButtonEmpty | ||
color="text" | ||
style={{ textAlign: 'left' }} | ||
className="searchableDropdown--topDisplay" | ||
onClick={onButtonClick} | ||
> | ||
{selectedText} | ||
</EuiButtonEmpty> | ||
); | ||
|
||
const formControl = <EuiFormControlLayout | ||
title={selected === undefined ? "Select an option" : selected.label} | ||
isLoading={loading} | ||
fullWidth={true} | ||
style={{ cursor: 'pointer' }} | ||
prepend={prepend} | ||
icon={{ type: 'arrowDown', side: 'right' }} | ||
readOnly={true} | ||
>{selectedView}</EuiFormControlLayout> | ||
|
||
return ( | ||
<div className="searchableDropdown"> | ||
<EuiPopover button={formControl} isOpen={isPopoverOpen} closePopover={closePopover}> | ||
<div className="searchableDropdown--fixedWidthChild">{selectable}</div> | ||
</EuiPopover> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.