From 7401c83007571ac6c32b525051d0540e5875317b Mon Sep 17 00:00:00 2001 From: GeorgySk Date: Fri, 17 May 2024 16:02:39 +0200 Subject: [PATCH 01/29] Add metadata filter interface --- src/components/@Explore/ExploreTab.tsx | 97 ++++++++++++++++- .../@Explore/MetadataFilterItem.tsx | 103 ++++++++++++++++++ 2 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 src/components/@Explore/MetadataFilterItem.tsx diff --git a/src/components/@Explore/ExploreTab.tsx b/src/components/@Explore/ExploreTab.tsx index 95142e8..5bd837b 100644 --- a/src/components/@Explore/ExploreTab.tsx +++ b/src/components/@Explore/ExploreTab.tsx @@ -23,6 +23,7 @@ import { withRequestAPI, IWithRequestAPIProps } from '../../utils/Actions'; import { DIDSearchType, IDIDSearchResult } from '../../types'; import { InlineDropdown } from '../components/../@Explore/InlineDropdown'; import { ListScopesPopover } from '../components/../@Explore/ListScopesPopover'; +import { MetadataFilter, MetadataFilterItem } from '../components/../@Explore/MetadataFilterItem'; const useStyles = createUseStyles({ mainContainer: { @@ -74,6 +75,31 @@ const useStyles = createUseStyles({ cursor: 'pointer', marginLeft: '4px' }, + addMetadataFilterButton: { + padding: '0 16px 0 16px', + fontSize: '9pt', + cursor: 'pointer', + opacity: 0.5, + '&:hover': { + opacity: 1 + } + }, + deleteFiltersButton: { + fontSize: '9pt', + cursor: 'pointer', + opacity: 0.5, + '&:hover': { + opacity: 1 + } + }, + deleteFiltersIcon: { + fontSize: '9pt', + cursor: 'pointer', + opacity: 0.5, + '&:hover': { + opacity: 1 + } + }, loading: { padding: '16px' }, @@ -102,10 +128,9 @@ const _Explore: React.FunctionComponent = props => { const [searchQuery, setSearchQuery] = useState(''); const [searchType, setSearchType] = useState('all'); - const [searchResult, setSearchResult] = useState(); - const [didExpanded, setDidExpanded] = useState<{ [index: number]: boolean }>( - {} - ); + const [searchResult, setSearchResult] = useState(); + const [didExpanded, setDidExpanded] = useState<{ [index: number]: boolean }>({}); + const [metadataFilters, setMetadataFilters] = React.useState([]); const [error, setError] = useState(); const [lastQuery, setLastQuery] = useState(''); const [loading, setLoading] = useState(false); @@ -222,6 +247,35 @@ const _Explore: React.FunctionComponent = props => { ); }; + const handleAddMetadataFilter = () => { + setMetadataFilters([ + ...metadataFilters, + { logic: "And", key: "", operator: "=", value: "" }, + ]); + }; + + const handleDeleteMetadataFilter = (indexToDelete: number) => { + setMetadataFilters((prev) => { + const newFilters = prev.filter((_, index) => index !== indexToDelete); + if (indexToDelete === 0 && newFilters.length > 0) { + newFilters[0] = { ...newFilters[0], logic: "And" }; + } + return newFilters; + }); + }; + + const handleDeleteAllMetadataFilters = () => { + setMetadataFilters([]); + }; + + const handleFilterChange = (index: number, updatedFilter: MetadataFilter) => { + setMetadataFilters((prev) => { + const newFilters = [...prev]; + newFilters[index] = updatedFilter; + return newFilters; + }); + }; + return (
@@ -245,6 +299,41 @@ const _Explore: React.FunctionComponent = props => { optionWidth="180px" />
+
+ {!metadataFilters.length && ( +
+ + Add metadata filter +
+ )} + {metadataFilters.map((filter, index) => ( + handleDeleteMetadataFilter(index)} + onChange={(updatedFilter) => + handleFilterChange(index, updatedFilter) + } + /> + ))} + {!!metadataFilters.length && ( +
+ + Add filter rule +
+ )} + {!!metadataFilters.length && ( +
+ delete + Delete filter +
+ )} +
{loading && (
diff --git a/src/components/@Explore/MetadataFilterItem.tsx b/src/components/@Explore/MetadataFilterItem.tsx new file mode 100644 index 0000000..5cad841 --- /dev/null +++ b/src/components/@Explore/MetadataFilterItem.tsx @@ -0,0 +1,103 @@ +import React, { useState } from 'react'; +import { createUseStyles } from 'react-jss'; +import { TextField } from '../../components/TextField'; + +const useStyles = createUseStyles({ + control: { + display: "flex", + flexDirection: "row", + alignItems: "stretch", + position: "relative", + marginBottom: "8px", + }, + deleteButton: { + fontSize: '9pt', + cursor: 'pointer', + opacity: 0.5, + '&:hover': { + opacity: 1 + } + }, + deleteIcon: { + fontSize: '9pt', + cursor: 'pointer', + opacity: 0.5, + '&:hover': { + opacity: 1 + } + }, +}) + +const operators = ["=", "≠", "<", ">", "≤", "≥"]; + +export interface MetadataFilter { + logic: string; + key: string; + operator: string; + value: string; +} + +export interface MetadataFilterItemProps { + filter: MetadataFilter; + showBoolOperatorDropdown: boolean; + onDelete: () => any; + onChange: (updatedFilter: MetadataFilter) => any; +} + +export const MetadataFilterItem: React.FC = ({ + filter, + showBoolOperatorDropdown, + onDelete, + onChange, +}) => { + const classes = useStyles(); + + const handleLogicChange = (e: React.ChangeEvent) => { + onChange({ ...filter, logic: e.target.value }); + }; + + const handleKeyChange = (e: React.ChangeEvent) => { + onChange({ ...filter, key: e.target.value }); + }; + + const handleOperatorChange = (e: React.ChangeEvent) => { + onChange({ ...filter, operator: e.target.value }); + }; + + const handleValueChange = (e: React.ChangeEvent) => { + onChange({ ...filter, value: e.target.value }); + }; + + return ( +
+ {showBoolOperatorDropdown ? ( +
Where
+ ) : ( + + )} + + + +
+ delete +
+
+ ); +}; From c7e0499aa651b5e7b8885ddf27d97f86a8d22dec Mon Sep 17 00:00:00 2001 From: GeorgySk Date: Fri, 17 May 2024 16:57:26 +0200 Subject: [PATCH 02/29] Fix invalid style references --- src/components/@Explore/ExploreTab.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/@Explore/ExploreTab.tsx b/src/components/@Explore/ExploreTab.tsx index 5bd837b..2f6a7df 100644 --- a/src/components/@Explore/ExploreTab.tsx +++ b/src/components/@Explore/ExploreTab.tsx @@ -302,7 +302,7 @@ const _Explore: React.FunctionComponent = props => {
{!metadataFilters.length && (
+ Add metadata filter @@ -321,7 +321,7 @@ const _Explore: React.FunctionComponent = props => { ))} {!!metadataFilters.length && (
+ Add filter rule From f38f1651587710a401a1fd3bfc242016d3d5d591 Mon Sep 17 00:00:00 2001 From: GeorgySk Date: Wed, 29 May 2024 16:27:17 +0200 Subject: [PATCH 03/29] Fix invalid reference --- src/components/@Explore/ExploreTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/@Explore/ExploreTab.tsx b/src/components/@Explore/ExploreTab.tsx index 2f6a7df..5bbd80d 100644 --- a/src/components/@Explore/ExploreTab.tsx +++ b/src/components/@Explore/ExploreTab.tsx @@ -128,7 +128,7 @@ const _Explore: React.FunctionComponent = props => { const [searchQuery, setSearchQuery] = useState(''); const [searchType, setSearchType] = useState('all'); - const [searchResult, setSearchResult] = useState(); + const [searchResult, setSearchResult] = useState(); const [didExpanded, setDidExpanded] = useState<{ [index: number]: boolean }>({}); const [metadataFilters, setMetadataFilters] = React.useState([]); const [error, setError] = useState(); From a6fa76615592131bc09f5c91295dadbd7e7b6601 Mon Sep 17 00:00:00 2001 From: GeorgySk Date: Tue, 23 Jul 2024 16:48:08 +0200 Subject: [PATCH 04/29] Add filtering functionality --- rucio_jupyterlab/handlers/did_search.py | 13 +- rucio_jupyterlab/rucio/rucio.py | 136 +++++++++++++++++- src/components/@Explore/ExploreTab.tsx | 23 ++- .../@Explore/MetadataFilterItem.tsx | 4 + src/utils/Actions.tsx | 5 +- 5 files changed, 167 insertions(+), 14 deletions(-) diff --git a/rucio_jupyterlab/handlers/did_search.py b/rucio_jupyterlab/handlers/did_search.py index 351361b..9ac22f8 100644 --- a/rucio_jupyterlab/handlers/did_search.py +++ b/rucio_jupyterlab/handlers/did_search.py @@ -28,14 +28,20 @@ def __init__(self, namespace, rucio): self.rucio = rucio self.db = get_db() # pylint: disable=invalid-name - def search_did(self, scope, name, search_type, limit): + def search_did(self, scope, name, search_type, filters, limit): wildcard_enabled = self.rucio.instance_config.get('wildcard_enabled', False) if ('*' in name or '%' in name) and not wildcard_enabled: raise WildcardDisallowedException() - dids = self.rucio.search_did(scope, name, search_type, limit) + dids = self.rucio.search_did(scope, name, search_type, filters, limit) + for did in dids: + if did['did_type'] is None: # JSON plugin was used lacking data + metadata = self.rucio.get_metadata(scope, did['name'])[0] + did['did_type'] = f"DIDType.{metadata['did_type']}" + did['bytes'] = metadata['bytes'] + did['length'] = metadata['length'] def mapper(entry, _): return { @@ -54,13 +60,14 @@ def get(self): namespace = self.get_query_argument('namespace') search_type = self.get_query_argument('type', 'collection') did = self.get_query_argument('did') + filters = self.get_query_argument('filters', default=None) rucio = self.rucio.for_instance(namespace) (scope, name) = did.split(':') handler = DIDSearchHandlerImpl(namespace, rucio) try: - dids = handler.search_did(scope, name, search_type, ROW_LIMIT) + dids = handler.search_did(scope, name, search_type, filters, ROW_LIMIT) self.finish(json.dumps(dids)) except RucioAuthenticationException: self.set_status(401) diff --git a/rucio_jupyterlab/rucio/rucio.py b/rucio_jupyterlab/rucio/rucio.py index bb5801b..07a4b4a 100644 --- a/rucio_jupyterlab/rucio/rucio.py +++ b/rucio_jupyterlab/rucio/rucio.py @@ -8,6 +8,7 @@ # - Muhammad Aditya Hilmy, , 2020 import logging +import re import time import json from urllib.parse import urlencode, quote @@ -16,6 +17,114 @@ from .authenticators import RucioAuthenticationException, authenticate_userpass, authenticate_x509, authenticate_oidc +def parse_did_filter_from_string_fe(input_string, name='*', type='collection', omit_name=False): + """ + Parse DID filter string for the filter engine (fe). + + Should adhere to the following conventions: + - ';' represents the logical OR operator + - ',' represents the logical AND operator + - all operators belong to set of (<=, >=, ==, !=, >, <, =) + - there should be no duplicate key+operator criteria. + + One sided and compound inequalities are supported. + + Sanity checking of input is left to the filter engine. + + :param input_string: String containing the filter options. + :param name: DID name. + :param type: The type of the did: all(container, dataset, file), collection(dataset or container), dataset, container. + :param omit_name: omit addition of name to filters. + :return: list of dictionaries with each dictionary as a separate OR expression. + """ + # lookup table unifying all comprehended operators to a nominal suffix. + # note that the order matters as the regex engine is eager, e.g. don't want to evaluate '<=' as '<' and '='. + operators_suffix_LUT = dict({ + '≤': 'lte', + '≥': 'gte', + '==': '', + '≠': 'ne', + '>': 'gt', + '<': 'lt', + '=': '' + }) + + # lookup table mapping operator opposites, used to reverse compound inequalities. + operator_opposites_LUT = { + 'lt': 'gt', + 'lte': 'gte' + } + operator_opposites_LUT.update({op2: op1 for op1, op2 in operator_opposites_LUT.items()}) + + filters = [] + if input_string: + or_groups = list(filter(None, input_string.split(';'))) # split into OR clauses + for or_group in or_groups: + or_group = or_group.strip() + and_groups = list(filter(None, or_group.split(','))) # split into AND clauses + and_group_filters = {} + for and_group in and_groups: + and_group = and_group.strip() + # tokenise this AND clause using operators as delimiters. + tokenisation_regex = "({})".format('|'.join(operators_suffix_LUT.keys())) + and_group_split_by_operator = list(filter(None, re.split(tokenisation_regex, and_group))) + if len(and_group_split_by_operator) == 3: # this is a one-sided inequality or expression + key, operator, value = [token.strip() for token in and_group_split_by_operator] + + # substitute input operator with the nominal operator defined by the LUT, . + operator_mapped = operators_suffix_LUT.get(operator) + + filter_key_full = key + if operator_mapped is not None: + if operator_mapped: + filter_key_full = "{}.{}".format(key, operator_mapped) + else: + raise ValueError("{} operator not understood.".format(operator_mapped)) + + if filter_key_full in and_group_filters: + raise ValueError(filter_key_full) + else: + and_group_filters[filter_key_full] = value + elif len(and_group_split_by_operator) == 5: # this is a compound inequality + value1, operator1, key, operator2, value2 = [token.strip() for token in and_group_split_by_operator] + + # substitute input operator with the nominal operator defined by the LUT, . + operator1_mapped = operator_opposites_LUT.get(operators_suffix_LUT.get(operator1)) + operator2_mapped = operators_suffix_LUT.get(operator2) + + filter_key1_full = filter_key2_full = key + if operator1_mapped is not None and operator2_mapped is not None: + if operator1_mapped: # ignore '' operator (maps from equals) + filter_key1_full = "{}.{}".format(key, operator1_mapped) + if operator2_mapped: # ignore '' operator (maps from equals) + filter_key2_full = "{}.{}".format(key, operator2_mapped) + else: + raise ValueError("{} operator not understood.".format(operator_mapped)) + + if filter_key1_full in and_group_filters: + raise ValueError(filter_key1_full) + else: + and_group_filters[filter_key1_full] = value1 + if filter_key2_full in and_group_filters: + raise ValueError(filter_key2_full) + else: + and_group_filters[filter_key2_full] = value2 + else: + raise ValueError(and_group) + + # add name key to each AND clause if it hasn't already been populated from the filter and not set. + if not omit_name and 'name' not in and_group_filters: + and_group_filters['name'] = name + + filters.append(and_group_filters) + else: + if not omit_name: + filters.append({ + 'name': name + }) + return filters, type + + class RucioAPI: rucio_auth_token_cache = dict() @@ -55,16 +164,20 @@ def get_rses(self, rse_expression=None): return results - def search_did(self, scope, name, search_type='collection', limit=None): + def search_did(self, scope, name, search_type='collection', filters=None, limit=None): token = self._get_auth_token() headers = {'X-Rucio-Auth-Token': token} scope = quote(scope) - urlencoded_params = urlencode({ + params = { 'type': search_type, 'long': '1', 'name': name - }) + } + if filters: + filters, _ = parse_did_filter_from_string_fe(filters) + params['filters'] = json.dumps(filters) + urlencoded_params = urlencode(params) response = requests.get(url=f'{self.base_url}/dids/{scope}/dids/search?{urlencoded_params}', headers=headers, verify=self.rucio_ca_cert) @@ -80,6 +193,23 @@ def search_did(self, scope, name, search_type='collection', limit=None): return results + def get_metadata(self, scope, name): + token = self._get_auth_token() + headers = {'X-Rucio-Auth-Token': token} + + scope = quote(scope) + name = quote(name) + + response = requests.get(url=f'{self.base_url}/dids/{scope}/{name}/meta', headers=headers, verify=self.rucio_ca_cert) + + if response.text == '': + return [] + + lines = response.text.rstrip('\n').splitlines() + results = [json.loads(l) for l in lines] + + return results + def get_files(self, scope, name): token = self._get_auth_token() headers = {'X-Rucio-Auth-Token': token} diff --git a/src/components/@Explore/ExploreTab.tsx b/src/components/@Explore/ExploreTab.tsx index 5bbd80d..62a1c11 100644 --- a/src/components/@Explore/ExploreTab.tsx +++ b/src/components/@Explore/ExploreTab.tsx @@ -132,12 +132,21 @@ const _Explore: React.FunctionComponent = props => { const [didExpanded, setDidExpanded] = useState<{ [index: number]: boolean }>({}); const [metadataFilters, setMetadataFilters] = React.useState([]); const [error, setError] = useState(); - const [lastQuery, setLastQuery] = useState(''); const [loading, setLoading] = useState(false); + const [searchTrigger, setSearchTrigger] = useState(0); const activeInstance = useStoreState(UIStore, s => s.activeInstance); + const buildMetadataFilterString = () => { + return metadataFilters + .map((filter, index) => { + const logic = index === 0 ? '' : filter.logic === 'And' ? ',' : ';'; + return `${logic}${filter.key}${filter.operator}${filter.value}`; + }) + .join(''); + }; + const doSearch = () => { - setLastQuery(searchQuery); + setSearchTrigger(prev => prev + 1); // Increment the counter to trigger the search }; const itemsSortFunction = ( @@ -160,7 +169,7 @@ const _Explore: React.FunctionComponent = props => { }; useEffect(() => { - if (!lastQuery || !activeInstance) { + if (!searchQuery || !activeInstance) { return; } @@ -168,8 +177,9 @@ const _Explore: React.FunctionComponent = props => { setSearchResult(undefined); setDidExpanded({}); setError(undefined); + const filterString = buildMetadataFilterString(); actions - .searchDID(activeInstance.name, searchQuery, searchType) + .searchDID(activeInstance.name, searchQuery, searchType, filterString) .then(items => items.sort(itemsSortFunction)) .then(result => setSearchResult(result)) .catch(e => { @@ -186,7 +196,7 @@ const _Explore: React.FunctionComponent = props => { } }) .finally(() => setLoading(false)); - }, [lastQuery, searchType]); + }, [searchTrigger, searchType]); const searchBoxRef = useRef(null); const onScopeClicked = (scope: string) => { @@ -205,7 +215,7 @@ const _Explore: React.FunctionComponent = props => { const searchButton = (
setLastQuery(searchQuery)} + onClick={doSearch} > search
@@ -317,6 +327,7 @@ const _Explore: React.FunctionComponent = props => { onChange={(updatedFilter) => handleFilterChange(index, updatedFilter) } + onKeyPress={handleKeyPress} /> ))} {!!metadataFilters.length && ( diff --git a/src/components/@Explore/MetadataFilterItem.tsx b/src/components/@Explore/MetadataFilterItem.tsx index 5cad841..100f23e 100644 --- a/src/components/@Explore/MetadataFilterItem.tsx +++ b/src/components/@Explore/MetadataFilterItem.tsx @@ -42,6 +42,7 @@ export interface MetadataFilterItemProps { showBoolOperatorDropdown: boolean; onDelete: () => any; onChange: (updatedFilter: MetadataFilter) => any; + onKeyPress: (e: React.KeyboardEvent) => any; } export const MetadataFilterItem: React.FC = ({ @@ -49,6 +50,7 @@ export const MetadataFilterItem: React.FC = ({ showBoolOperatorDropdown, onDelete, onChange, + onKeyPress }) => { const classes = useStyles(); @@ -82,6 +84,7 @@ export const MetadataFilterItem: React.FC = ({ placeholder="Key" value={filter.key} onChange={handleKeyChange} + onKeyPress={onKeyPress} /> - - - +
+ +
)} - - - +
+ +
+
+ +
+
+ +
delete
From a8a26b4155ee811881d10a97347bf3e2994540c4 Mon Sep 17 00:00:00 2001 From: Francesc Torradeflot Date: Wed, 21 Aug 2024 09:24:44 +0200 Subject: [PATCH 06/29] Fix version in python code --- rucio_jupyterlab/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rucio_jupyterlab/_version.py b/rucio_jupyterlab/_version.py index bfbd17f..8c50f31 100644 --- a/rucio_jupyterlab/_version.py +++ b/rucio_jupyterlab/_version.py @@ -1,4 +1,4 @@ # This file is auto-generated by Hatchling. As such, do not: # - modify # - track in version control e.g. be sure to add to .gitignore -__version__ = VERSION = '0.10.0' +__version__ = VERSION = '1.0.0' From f93bb0ade1f0344c55e896fd51110a3066461ce5 Mon Sep 17 00:00:00 2001 From: Francesc Torradeflot Date: Wed, 21 Aug 2024 09:32:19 +0200 Subject: [PATCH 07/29] Fix ESLint errors --- src/components/@Explore/ExploreTab.tsx | 46 +++++++++++-------- .../@Explore/MetadataFilterItem.tsx | 30 ++++++------ 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/components/@Explore/ExploreTab.tsx b/src/components/@Explore/ExploreTab.tsx index d11bd86..2cb77ed 100644 --- a/src/components/@Explore/ExploreTab.tsx +++ b/src/components/@Explore/ExploreTab.tsx @@ -23,7 +23,10 @@ import { withRequestAPI, IWithRequestAPIProps } from '../../utils/Actions'; import { DIDSearchType, IDIDSearchResult } from '../../types'; import { InlineDropdown } from '../components/../@Explore/InlineDropdown'; import { ListScopesPopover } from '../components/../@Explore/ListScopesPopover'; -import { MetadataFilter, MetadataFilterItem } from '../components/../@Explore/MetadataFilterItem'; +import { + IMetadataFilter, + MetadataFilterItem +} from '../components/../@Explore/MetadataFilterItem'; const useStyles = createUseStyles({ mainContainer: { @@ -138,8 +141,12 @@ const _Explore: React.FunctionComponent = props => { const [searchQuery, setSearchQuery] = useState(''); const [searchType, setSearchType] = useState('all'); const [searchResult, setSearchResult] = useState(); - const [didExpanded, setDidExpanded] = useState<{ [index: number]: boolean }>({}); - const [metadataFilters, setMetadataFilters] = React.useState([]); + const [didExpanded, setDidExpanded] = useState<{ [index: number]: boolean }>( + {} + ); + const [metadataFilters, setMetadataFilters] = React.useState< + IMetadataFilter[] + >([]); const [error, setError] = useState(); const [loading, setLoading] = useState(false); const [searchTrigger, setSearchTrigger] = useState(0); @@ -205,7 +212,7 @@ const _Explore: React.FunctionComponent = props => { } }) .finally(() => setLoading(false)); - }, [searchTrigger, searchType]); + }, [searchTrigger, searchType]); const searchBoxRef = useRef(null); const onScopeClicked = (scope: string) => { @@ -222,10 +229,7 @@ const _Explore: React.FunctionComponent = props => { ); const searchButton = ( -
+
search
); @@ -269,15 +273,15 @@ const _Explore: React.FunctionComponent = props => { const handleAddMetadataFilter = () => { setMetadataFilters([ ...metadataFilters, - { logic: "And", key: "", operator: "=", value: "" }, + { logic: 'And', key: '', operator: '=', value: '' } ]); }; const handleDeleteMetadataFilter = (indexToDelete: number) => { - setMetadataFilters((prev) => { + setMetadataFilters(prev => { const newFilters = prev.filter((_, index) => index !== indexToDelete); if (indexToDelete === 0 && newFilters.length > 0) { - newFilters[0] = { ...newFilters[0], logic: "And" }; + newFilters[0] = { ...newFilters[0], logic: 'And' }; } return newFilters; }); @@ -287,8 +291,11 @@ const _Explore: React.FunctionComponent = props => { setMetadataFilters([]); }; - const handleFilterChange = (index: number, updatedFilter: MetadataFilter) => { - setMetadataFilters((prev) => { + const handleFilterChange = ( + index: number, + updatedFilter: IMetadataFilter + ) => { + setMetadataFilters(prev => { const newFilters = [...prev]; newFilters[index] = updatedFilter; return newFilters; @@ -333,9 +340,7 @@ const _Explore: React.FunctionComponent = props => { filter={filter} showBoolOperatorDropdown={index === 0} onDelete={() => handleDeleteMetadataFilter(index)} - onChange={(updatedFilter) => - handleFilterChange(index, updatedFilter) - } + onChange={updatedFilter => handleFilterChange(index, updatedFilter)} onKeyPress={handleKeyPress} /> ))} @@ -348,8 +353,13 @@ const _Explore: React.FunctionComponent = props => {
)} {!!metadataFilters.length && ( -
- delete +
+ + delete + Delete filter
)} diff --git a/src/components/@Explore/MetadataFilterItem.tsx b/src/components/@Explore/MetadataFilterItem.tsx index 3b6eed4..65f5a1e 100644 --- a/src/components/@Explore/MetadataFilterItem.tsx +++ b/src/components/@Explore/MetadataFilterItem.tsx @@ -1,14 +1,14 @@ -import React, { useState } from 'react'; +import React from 'react'; import { createUseStyles } from 'react-jss'; import { TextField } from '../../components/TextField'; const useStyles = createUseStyles({ control: { - display: "flex", - flexWrap: "nowrap", - flexDirection: "row", - alignItems: "center", - position: "relative", + display: 'flex', + flexWrap: 'nowrap', + flexDirection: 'row', + alignItems: 'center', + position: 'relative', padding: '4px 16px 0 16px' }, where: { @@ -46,27 +46,27 @@ const useStyles = createUseStyles({ '&:hover': { opacity: 1 } - }, -}) + } +}); -const operators = ["=", "≠", "<", ">", "≤", "≥"]; +const operators = ['=', '≠', '<', '>', '≤', '≥']; -export interface MetadataFilter { +export interface IMetadataFilter { logic: string; key: string; operator: string; value: string; } -export interface MetadataFilterItemProps { - filter: MetadataFilter; +export interface IMetadataFilterItemProps { + filter: IMetadataFilter; showBoolOperatorDropdown: boolean; onDelete: () => any; - onChange: (updatedFilter: MetadataFilter) => any; + onChange: (updatedFilter: IMetadataFilter) => any; onKeyPress: (e: React.KeyboardEvent) => any; } -export const MetadataFilterItem: React.FC = ({ +export const MetadataFilterItem: React.FC = ({ filter, showBoolOperatorDropdown, onDelete, @@ -115,7 +115,7 @@ export const MetadataFilterItem: React.FC = ({
- - - -
+ )}
= ({ onKeyPress={onKeyPress} />
-
- -
+
Date: Mon, 16 Dec 2024 19:32:40 +0100 Subject: [PATCH 23/29] Remove comma, ES lint check --- src/components/@Explore/MetadataFilterItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/@Explore/MetadataFilterItem.tsx b/src/components/@Explore/MetadataFilterItem.tsx index ae2c17f..26c3cb3 100644 --- a/src/components/@Explore/MetadataFilterItem.tsx +++ b/src/components/@Explore/MetadataFilterItem.tsx @@ -18,7 +18,7 @@ const useStyles = createUseStyles({ display: 'flex', alignSelf: 'stretch', color: 'var(--jp-ui-font-color1)', - background: 'var(--jp-layout-color1)', + background: 'var(--jp-layout-color1)' }, key: { flex: 1, From 389df01cb39e878efa31fe572da077090e11c70d Mon Sep 17 00:00:00 2001 From: Francesc Torradeflot Date: Tue, 17 Dec 2024 14:28:50 +0100 Subject: [PATCH 24/29] inherit jupyterlab ci/cd actions --- .github/actions/setup/action.yml | 29 ----------------------------- .github/workflows/test.yml | 26 +++++++++++++++++++------- 2 files changed, 19 insertions(+), 36 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index b3771c2..f250516 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -2,35 +2,6 @@ name: "Setup" runs: using: "composite" steps: - - name: Install node - uses: actions/setup-node@v4 - with: - node-version: '18.x' - - name: Install Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - architecture: 'x64' - - name: Setup pip cache - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: pip-3.11-${{ hashFiles('package.json') }} - restore-keys: | - pip-3.11- - pip- - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - shell: bash - - name: Setup yarn cache - uses: actions/cache@v2 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - yarn- - name: Install Python dependencies run: python -m pip install -r requirements.txt shell: bash diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7549ed7..f331a76 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,12 +8,24 @@ on: workflow_dispatch: jobs: - build: - runs-on: ubuntu-latest + Test: + matrix: + python-version: ["3.9", "3.11", "3.12"] + node-version: ["18.x", "20.x"] + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 - - uses: ./.github/actions/setup - - uses: ./.github/actions/test - - uses: ./.github/actions/build-ext - - uses: ./.github/actions/post-test + - name: Checkout + uses: actions/checkout@v4 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Test + uses: ./.github/actions/test + + - name: Build + uses: ./.github/actions/build-ext + + - name: Post Test + uses: ./.github/actions/post-test From 721d24de7775341d7d57fa80d7b12f6761799b67 Mon Sep 17 00:00:00 2001 From: Francesc Torradeflot Date: Tue, 17 Dec 2024 14:33:24 +0100 Subject: [PATCH 25/29] Fix test workflow --- .github/workflows/test.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f331a76..fbf2659 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,9 +9,11 @@ on: jobs: Test: - matrix: - python-version: ["3.9", "3.11", "3.12"] - node-version: ["18.x", "20.x"] + name: Test + strategy: + matrix: + python-version: ["3.9", "3.11", "3.12"] + node-version: ["18.x", "20.x"] runs-on: ubuntu-22.04 steps: - name: Checkout From 72a3e45ad62ef780b275919479d2ee1b0cb09e3b Mon Sep 17 00:00:00 2001 From: Francesc Torradeflot Date: Tue, 17 Dec 2024 14:35:59 +0100 Subject: [PATCH 26/29] Add back custom setup step --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fbf2659..f5c0b94 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,9 @@ jobs: - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Custom Setup + uses: ./.github/actions/setup + - name: Test uses: ./.github/actions/test From 4097970d32f902fd1e6764116f768b9edd19d536 Mon Sep 17 00:00:00 2001 From: Francesc Torradeflot Date: Tue, 17 Dec 2024 14:51:08 +0100 Subject: [PATCH 27/29] Add firefox setup step --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f5c0b94..7956ed8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,9 @@ jobs: - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Setup firefox + uses: browser-actions/setup-firefox@latest + - name: Custom Setup uses: ./.github/actions/setup From fe40d403238ac8e62599902a3b01f84840f14ae2 Mon Sep 17 00:00:00 2001 From: Francesc Torradeflot Date: Tue, 17 Dec 2024 15:04:59 +0100 Subject: [PATCH 28/29] Force browser to firefox --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7956ed8..126b557 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,5 +35,7 @@ jobs: uses: ./.github/actions/build-ext - name: Post Test + env: + JLAB_BROWSER_TYPE: firefox uses: ./.github/actions/post-test From ced8b56fe683e843679e8a2a46993009af97be21 Mon Sep 17 00:00:00 2001 From: Francesc Torradeflot Date: Tue, 17 Dec 2024 15:38:15 +0100 Subject: [PATCH 29/29] Adapt build & publish workflow --- .../workflows/build-and-publish-tagged.yml | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-and-publish-tagged.yml b/.github/workflows/build-and-publish-tagged.yml index 007e019..445b9fe 100644 --- a/.github/workflows/build-and-publish-tagged.yml +++ b/.github/workflows/build-and-publish-tagged.yml @@ -5,24 +5,28 @@ on: tags: 'v*' jobs: - build: - name: Build and Publish to PyPI + + Test: + uses: ./.github/workflows/test.yml + + Build: + needs: [Test] runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/rucio-jupyterlab - permissions: - id-token: write steps: - - uses: actions/checkout@v3 - - uses: ./.github/actions/setup - - uses: ./.github/actions/test - - uses: ./.github/actions/build-ext - - uses: ./.github/actions/post-test + - name: Checkout + uses: actions/checkout@v4 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Custom Setup + uses: ./.github/actions/setup + - name: Build sdist run: | pip install build python -m build --sdist + - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: