diff --git a/app/public/locales/en/common.yml b/app/public/locales/en/common.yml
index 0f8ba305..e5b293d6 100644
--- a/app/public/locales/en/common.yml
+++ b/app/public/locales/en/common.yml
@@ -676,7 +676,12 @@ filters:
root_relative_squared_error: "Root relative squared error"
c_index: "C-index"
f_measure: "F-measure"
-
+ dependencies: "Libraries"
+ sklearn: "sklearn {{version}}"
+ torch: "Torch {{version}}"
+ Weka: "Weka {{version}}"
+ mlr: "MLR {{version}}"
+ Moa: "Moa {{version}}"
diff --git a/app/src/components/navbar/NavbarSearch.js b/app/src/components/navbar/NavbarSearch.js
index f14c7f98..7c63eae8 100644
--- a/app/src/components/navbar/NavbarSearch.js
+++ b/app/src/components/navbar/NavbarSearch.js
@@ -18,6 +18,7 @@ import { faSearch } from "@fortawesome/free-solid-svg-icons";
import { Box, InputBase, MenuItem, Select } from "@mui/material";
import { SearchProvider, SearchBox } from "@elastic/react-search-ui";
+import { useTheme } from "@mui/system";
const SearchWrapper = styled(Box)`
border-radius: 2px;
@@ -150,7 +151,8 @@ const SearchBar = memo(() => {
PaperProps: {
sx: {
boxShadow: 2,
- border: "1px solid #d3d4d5",
+ border: `1px solid rgba(0, 0, 0, 0.12);
+ `,
},
},
}}
diff --git a/app/src/components/search/CoreFilter.js b/app/src/components/search/CoreFilter.js
index b0fb5f8a..f51a2a40 100644
--- a/app/src/components/search/CoreFilter.js
+++ b/app/src/components/search/CoreFilter.js
@@ -1,4 +1,4 @@
-import React, { useEffect } from "react";
+import { useEffect } from "react";
import { withSearch } from "@elastic/react-search-ui";
const CoreFilter = ({ addFilter, removeFilter, setSort }) => {
diff --git a/app/src/components/search/Filter.js b/app/src/components/search/Filter.js
index bd206c2d..6ffb38dc 100644
--- a/app/src/components/search/Filter.js
+++ b/app/src/components/search/Filter.js
@@ -1,24 +1,63 @@
-import React from "react";
-
+import { Chip } from "@mui/material";
import styled from "@emotion/styled";
-import { Chip as MuiChip } from "@mui/material";
+import { i18n } from "next-i18next";
+import React from "react";
-const FilterChip = styled(MuiChip)`
- margin-left: 10px;
+const FilterChip = styled(Chip)`
+ margin-right: 10px;
margin-top: 10px;
margin-bottom: 10px;
-`;
-const FilterPanel = styled.div`
- border-right: 1px solid rgba(0, 0, 0, 0.12);
- border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+ border-radius: 50px;
`;
+// Handles special cases in the filter options
+const processOption = (option) => {
+ // Homogenize notation for library reporting and versioning
+ const libraries = [
+ "sklearn",
+ "torch",
+ "Weka",
+ "tensorflow",
+ "keras",
+ "mlr",
+ "Moa",
+ ];
+ if (libraries.some((library) => option.includes(library))) {
+ const segments = option.split(/,|\n/);
+ let lib = segments.find((segment) =>
+ libraries.some((library) => segment.includes(library)),
+ );
+ if (lib.startsWith(" ")) {
+ lib = lib.replace(" ", "");
+ }
+ if (
+ lib.startsWith("Weka_") ||
+ lib.startsWith("R_") ||
+ lib.startsWith("Moa_") ||
+ lib.startsWith("mlr_")
+ ) {
+ lib = lib.replace("_", "==");
+ }
+ // If the option has versioning, return it separately
+ const values = lib.split("==");
+ if (values.length > 1) {
+ return [`filters.${values[0]}`, { version: values[1] }];
+ } else {
+ return [`filters.${lib}`];
+ }
+ } else {
+ return [`filters.${option}`];
+ }
+};
+
const Filter = ({ label, options, values, onRemove, onSelect }) => {
return (
-
+
{options.map((option) => (
@@ -28,7 +67,7 @@ const Filter = ({ label, options, values, onRemove, onSelect }) => {
variant={option.selected ? "default" : "outlined"}
/>
))}
-
+
);
};
diff --git a/app/src/components/search/ResultCard.js b/app/src/components/search/ResultCard.js
index ed7fe2c9..e4b217b8 100644
--- a/app/src/components/search/ResultCard.js
+++ b/app/src/components/search/ResultCard.js
@@ -20,6 +20,11 @@ import {
Description as TaskDescription,
stats as taskStats,
} from "../../pages/t/taskCard";
+import {
+ Title as FlowTitle,
+ Description as FlowDescription,
+ stats as flowStats,
+} from "../../pages/f/flowCard";
import { faHashtag, faHistory } from "@fortawesome/free-solid-svg-icons";
@@ -106,18 +111,21 @@ const abbreviateNumber = (value) => {
const titles = {
data: DataTitle,
task: TaskTitle,
+ flow: FlowTitle,
// Add other mappings as needed
};
const descriptions = {
data: DataDescription,
task: TaskDescription,
+ flow: FlowDescription,
// Add other mappings as needed
};
const statistics = {
data: dataStats,
task: taskStats,
+ flow: flowStats,
// Add other mappings as needed
};
diff --git a/app/src/components/search/SearchContainer.js b/app/src/components/search/SearchContainer.js
index 82663ed2..e08e09e7 100644
--- a/app/src/components/search/SearchContainer.js
+++ b/app/src/components/search/SearchContainer.js
@@ -10,7 +10,6 @@ import {
FormControl,
InputLabel,
Tab,
- Chip,
Tabs,
Card,
} from "@mui/material";
@@ -40,6 +39,7 @@ import {
import Wrapper from "../Wrapper";
import { i18n } from "next-i18next";
import ResultsTable from "./ResultTable";
+import Filter from "./Filter";
import TagFilter from "./TagFilter";
const SearchResults = styled(Results)`
@@ -178,61 +178,9 @@ const PagingView = ({ current, totalPages, onChange }) => (
/>
);
-// Allows overriding the filter option text
-const facet_aliases = {
- Status: {
- active: "verified",
- in_preparation: "in preparation",
- deactivated: "deprecated",
- },
-};
-
-const FilterChip = styled(Chip)`
- margin-right: 10px;
- margin-top: 10px;
- margin-bottom: 10px;
- border-radius: 50px;
-`;
-
-const FilterPanel = styled.div``;
-
-const Filter = ({ label, options, values, onRemove, onSelect }) => {
- return (
-
- {options.map((option) => (
-
- option.selected ? onRemove(option.value) : onSelect(option.value)
- }
- color={option.selected ? "primary" : "default"}
- variant={option.selected ? "default" : "outlined"}
- />
- ))}
-
- );
-};
-
// This is the Search UI component. The config contains the search state and actions.
const SearchContainer = memo(
- ({
- config,
- sort_options,
- search_facets,
- columns,
- facet_aliases,
- title,
- type,
- }) => {
+ ({ config, sort_options, search_facets, columns }) => {
const [filter, setFilter] = React.useState("hide");
const handleFilterChange = (event, newFilter) => {
console.log(filter, newFilter);
@@ -287,6 +235,7 @@ const SearchContainer = memo(
view={Filter}
value={filter}
index={index}
+ show={25}
/>
))}
diff --git a/app/src/pages/d/search.js b/app/src/pages/d/search.js
index 7eb7c56a..8953e5c2 100644
--- a/app/src/pages/d/search.js
+++ b/app/src/pages/d/search.js
@@ -13,7 +13,6 @@ import {
renderChips,
} from "../../components/search/ResultTable";
-import Chip from "@mui/material/Chip";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCheck,
@@ -260,8 +259,6 @@ function DataSearchContainer() {
sort_options={sort_options}
search_facets={search_facets}
columns={columns}
- title="Datasets"
- type="Dataset"
/>
);
}
diff --git a/app/src/pages/d/searchConfig.js b/app/src/pages/d/searchConfig.js
index eb718b6d..9ad9447f 100644
--- a/app/src/pages/d/searchConfig.js
+++ b/app/src/pages/d/searchConfig.js
@@ -70,7 +70,7 @@ const searchConfig = {
facets: {
"status.keyword": { type: "value" },
"name.keyword": { type: "value" },
- "licence.keyword": { type: "value" },
+ "licence.keyword": { type: "value", size: 10 },
"qualities.NumberOfInstances": {
type: "range",
ranges: [
diff --git a/app/src/pages/f/flowCard.js b/app/src/pages/f/flowCard.js
new file mode 100644
index 00000000..5ce78444
--- /dev/null
+++ b/app/src/pages/f/flowCard.js
@@ -0,0 +1,67 @@
+import { Box } from "@mui/material";
+import React from "react";
+import { blue, green, orange, purple, red } from "@mui/material/colors";
+import {
+ faCheck,
+ faCloudDownloadAlt,
+ faFlask,
+ faHeart,
+ faTimes,
+ faWrench,
+} from "@fortawesome/free-solid-svg-icons";
+import Teaser from "../../components/search/Teaser";
+
+const status = {
+ active: {
+ title: "verified",
+ icon: faCheck,
+ color: green[500],
+ },
+ deactivated: {
+ title: "deactivated",
+ icon: faTimes,
+ color: red[500],
+ },
+ in_preparation: {
+ title: "unverified",
+ icon: faWrench,
+ color: orange[500],
+ },
+};
+
+export const Title = ({ result }) => {
+ return (
+
+ {result.name.raw}
+
+ );
+};
+
+export const stats = [
+ { param: "runs.raw", unit: "runs", color: red[500], icon: faFlask },
+ {
+ param: "nr_of_likes.raw",
+ unit: "likes",
+ color: purple[500],
+ icon: faHeart,
+ },
+ {
+ param: "nr_of_downloads.raw",
+ unit: "downloads",
+ color: blue[500],
+ icon: faCloudDownloadAlt,
+ },
+];
+
+export const Description = ({ result }) => {
+ return (
+
+ );
+};
diff --git a/app/src/pages/f/search.js b/app/src/pages/f/search.js
new file mode 100644
index 00000000..10769c17
--- /dev/null
+++ b/app/src/pages/f/search.js
@@ -0,0 +1,189 @@
+import React from "react";
+import { useNextRouting } from "../../utils/useNextRouting";
+
+import DashboardLayout from "../../layouts/Dashboard";
+import SearchContainer from "../../components/search/SearchContainer";
+import {
+ renderCell,
+ valueGetter,
+ renderDescription,
+ renderDate,
+ renderTags,
+ renderChips,
+} from "../../components/search/ResultTable";
+
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+import searchConfig from "../f/searchConfig";
+import {
+ faCreativeCommonsBy,
+ faCreativeCommonsPd,
+ faCreativeCommonsZero,
+} from "@fortawesome/free-brands-svg-icons";
+
+// Server-side translation
+import { serverSideTranslations } from "next-i18next/serverSideTranslations";
+export async function getStaticProps(context) {
+ // extract the locale identifier from the URL
+ const { locale } = context;
+ return {
+ props: {
+ // pass the translation props to the page component
+ ...(await serverSideTranslations(locale)),
+ },
+ };
+}
+
+// Defines chips in table view
+const getChipProps = (value) => {
+ switch (value) {
+ // Dataset licence
+ case "public":
+ case "Public":
+ return {
+ label: "Public",
+ icon: ,
+ color: "success",
+ };
+ case "CC0":
+ case "CCZero":
+ case "CC0: Public Domain":
+ case "Public Domain (CC0)":
+ return {
+ label: "CC0",
+ icon: ,
+ color: "success",
+ };
+ case "CC BY 4.0":
+ case "CC BY-NC-ND":
+ case "CC BY-NC 4.0":
+ case "CC BY-SA":
+ case "CC BY":
+ case "Creative Commons Attribution":
+ return {
+ label: value,
+ icon: ,
+ color: "primary",
+ };
+ case "Open Database License (ODbL)":
+ return {
+ label: "ODbL",
+ icon: ,
+ color: "success",
+ };
+ default:
+ return {
+ label: value,
+ };
+ }
+};
+
+const sort_options = [
+ {
+ name: "search.relevance",
+ value: [],
+ },
+ {
+ name: "search.most_runs",
+ value: [{ field: "runs", direction: "desc" }],
+ },
+ {
+ name: "search.most_likes",
+ value: [{ field: "nr_of_likes", direction: "desc" }],
+ },
+ {
+ name: "search.most_downloads",
+ value: [{ field: "nr_of_downloads", direction: "desc" }],
+ },
+ {
+ name: "search.most_recent",
+ value: [{ field: "date", direction: "desc" }],
+ },
+];
+
+const search_facets = [
+ {
+ label: "filters.dependencies",
+ field: "dependencies.keyword",
+ },
+];
+
+// Controls how columns are rendered and manipulated in the table view
+const columns = [
+ {
+ field: "flow_id",
+ headerName: "Flow_id",
+ valueGetter: valueGetter("data_id"),
+ renderCell: renderCell,
+ width: 70,
+ },
+ {
+ field: "name",
+ headerName: "Name",
+ valueGetter: valueGetter("name"),
+ renderCell: renderCell,
+ width: 230,
+ },
+ {
+ field: "version",
+ headerName: "Version",
+ valueGetter: valueGetter("version"),
+ renderCell: renderCell,
+ width: 60,
+ },
+ {
+ field: "description",
+ headerName: "Description",
+ valueGetter: valueGetter("description"),
+ renderCell: renderDescription,
+ width: 360,
+ },
+ {
+ field: "date",
+ headerName: "Date",
+ valueGetter: valueGetter("date"),
+ renderCell: renderDate,
+ },
+ {
+ field: "licence",
+ headerName: "Licence",
+ valueGetter: valueGetter("licence"),
+ renderCell: renderChips(getChipProps),
+ width: 110,
+ },
+ {
+ field: "creator",
+ headerName: "Creator",
+ valueGetter: valueGetter("creator"),
+ renderCell: renderCell,
+ width: 150,
+ },
+ {
+ field: "tags",
+ headerName: "Tags",
+ valueGetter: valueGetter("tags"),
+ renderCell: renderTags,
+ width: 400,
+ },
+];
+
+function DataSearchContainer() {
+ const combinedConfig = useNextRouting(searchConfig, "");
+
+ return (
+
+ );
+}
+
+DataSearchContainer.getLayout = function getLayout(page) {
+ return {page};
+};
+
+DataSearchContainer.displayName = "DataSearchContainer";
+
+export default DataSearchContainer;
diff --git a/app/src/pages/f/searchConfig.js b/app/src/pages/f/searchConfig.js
new file mode 100644
index 00000000..49dee85d
--- /dev/null
+++ b/app/src/pages/f/searchConfig.js
@@ -0,0 +1,91 @@
+import Connector from "../../services/SearchAPIConnector";
+const apiConnector = new Connector("flow");
+
+const searchConfig = {
+ apiConnector: apiConnector,
+ alwaysSearchOnInitialLoad: true,
+ searchQuery: {
+ resultsPerPage: 100,
+ search_fields: {
+ name: { weight: 3 },
+ exact_name: { weight: 3 },
+ description: { weight: 3 },
+ full_description: { weight: 3 },
+ "tags.tag": { weight: 3 },
+ "parameters.full_name": { weight: 3 },
+ "parameters.description": { weight: 3 },
+ uploader: { weight: 2 },
+ installation_notes: { weight: 1 },
+ dependencies: { weight: 1 },
+ licence: { weight: 1 },
+ },
+ result_fields: {
+ contributor: { raw: {} },
+ creator: { raw: {} },
+ flow_id: { raw: {} },
+ date: { raw: {} },
+ name: {
+ snippet: {
+ size: 100,
+ fallback: true,
+ },
+ },
+ description: {
+ snippet: {
+ size: 100,
+ fallback: true,
+ },
+ },
+ full_description: {
+ snippet: {
+ size: 100,
+ fallback: true,
+ },
+ },
+ installation_notes: {
+ snippet: {
+ size: 100,
+ fallback: true,
+ },
+ },
+ licence: { raw: {} },
+ nr_of_likes: { raw: {} },
+ nr_of_downloads: { raw: {} },
+ runs: { raw: {} },
+ status: { raw: {} },
+ tags: { raw: {} },
+ total_downloads: { raw: {} },
+ uploader: { raw: {} },
+ uploader_id: { raw: {} },
+ url: { raw: {} },
+ visibility: { raw: {} },
+ dependencies: { raw: {} },
+ version: { raw: {} },
+ suggest: { raw: {} },
+ },
+ disjunctiveFacets: ["dependencies"],
+ facets: {
+ "dependencies.keyword": { type: "value", size: 50 },
+ },
+ group: {
+ //This doesn't work yet. TODO: figure out how to group.
+ field: { name: { raw: {} } },
+ },
+ },
+ autocompleteQuery: {
+ results: {
+ resultsPerPage: 100,
+ result_fields: {
+ // specify the fields you want from the index to display the results
+ name: { snippet: { size: 100, fallback: true } },
+ url: { raw: {} },
+ },
+ search_fields: {
+ // specify the fields you want to search on
+ name: {},
+ },
+ },
+ },
+};
+
+export default searchConfig;
diff --git a/app/src/pages/t/search.js b/app/src/pages/t/search.js
index 04e1e0aa..8038cdc2 100644
--- a/app/src/pages/t/search.js
+++ b/app/src/pages/t/search.js
@@ -78,8 +78,6 @@ function TaskSearchContainer() {
sort_options={sort_options}
search_facets={search_facets}
columns={columns}
- title="Tasks"
- type="Task"
/>
);
}
diff --git a/app/src/services/SearchAPIConnector.js b/app/src/services/SearchAPIConnector.js
index 65680905..fd6da433 100644
--- a/app/src/services/SearchAPIConnector.js
+++ b/app/src/services/SearchAPIConnector.js
@@ -26,6 +26,7 @@ class SearchAPIConnector {
};
//console.log(request.body);
const response = await fetch("/api/search", request);
+ //console.log(response);
return response.json();
}