Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

subcommunities: improve search page #1242

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { i18next } from "@translations/invenio_rdm_records/i18next";

import React, { Component } from "react";

import { Menu } from "semantic-ui-react";
import PropTypes from "prop-types";

export class CommunitiesStatusFilter extends Component {
render() {
const {
myCommunitiesOnClick,
allCommunitiesOnClick,
appID,
allCommunitiesSelected,
} = this.props;

return (
<Menu role="tablist" className="theme-primary-menu" compact>
<Menu.Item
as="button"
role="tab"
id="all-communities-tab"
aria-selected={allCommunitiesSelected}
aria-controls={appID}
name="All"
active={allCommunitiesSelected}
onClick={allCommunitiesOnClick}
>
{i18next.t("All")}
</Menu.Item>
<Menu.Item
as="button"
role="tab"
id="my-communities-tab"
aria-selected={!allCommunitiesSelected}
aria-controls={appID}
name="My communities"
active={!allCommunitiesSelected}
onClick={myCommunitiesOnClick}
>
{i18next.t("My communities")}
</Menu.Item>
</Menu>
);
}
}

CommunitiesStatusFilter.propTypes = {
allCommunitiesOnClick: PropTypes.func.isRequired,
myCommunitiesOnClick: PropTypes.func.isRequired,
allCommunitiesSelected: PropTypes.bool,
appID: PropTypes.string.isRequired,
};

CommunitiesStatusFilter.defaultProps = {
allCommunitiesSelected: true,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// This file is part of Invenio-RDM
// Copyright (C) 2024 CERN.
//
// Invenio-communities is free software; you can redistribute it and/or modify it
// under the terms of the MIT License; see LICENSE file for more details.

import { i18next } from "@translations/invenio_rdm_records/i18next";
import React, { Component } from "react";
import { OverridableContext } from "react-overridable";
import { InvenioSearchApi, ReactSearchKit, SearchBar, buildUID } from "react-searchkit";
import { Button, Grid } from "semantic-ui-react";
import PropTypes from "prop-types";
import {
SearchConfigurationContext,
SearchAppFacets,
SearchAppResultsPane,
} from "@js/invenio_search_ui/components";
import { GridResponsiveSidebarColumn } from "react-invenio-forms";
import { CommunitiesStatusFilter } from "./CommunitiesStatusFilter";

export class CommunitySelectionSearch extends Component {
constructor(props) {
super(props);
const {
apiConfigs: { allCommunities },
} = this.props;

this.state = {
selectedConfig: allCommunities,
sidebarVisible: false,
};
}

/**
* Namespaces each component object with the specified appID.
* This is needed since the `CommunitySelectionSearch` component
* uses two search applications with distinct configs (e.g. to search in "All" or "My" communities)
*/
prefixAppID(components, appID) {
alejandromumo marked this conversation as resolved.
Show resolved Hide resolved
// iterate components and prefix them with ".appID"
return Object.fromEntries(
Object.entries(components).map(([key, value]) => [`${appID}.${key}`, value])
);
}

render() {
const {
selectedConfig: {
searchApi: selectedSearchApi,
appId: selectedAppId,
initialQueryState: selectedInitialQueryState,
toggleText,
},
sidebarVisible,
} = this.state;

const {
apiConfigs: { allCommunities, myCommunities },
autofocus,
communitiesStatusFilterEnabled,
config,
overriddenComponents,
} = this.props;

const searchApi = new InvenioSearchApi(selectedSearchApi);

const validatedComponents = this.prefixAppID(overriddenComponents, selectedAppId);

const layoutOptions = {
listView: true,
gridView: false,
};

const context = {
selectedAppId,
buildUID: (element) => buildUID(element, "", selectedAppId),
...config,
};

return (
<OverridableContext.Provider value={validatedComponents}>
<SearchConfigurationContext.Provider value={context}>
<ReactSearchKit
appName={selectedAppId}
urlHandlerApi={{ enabled: false }}
searchApi={searchApi}
key={selectedAppId}
initialQueryState={selectedInitialQueryState}
defaultSortingOnEmptyQueryString={{ sortBy: "bestmatch" }}
>
<Grid padded>
<Grid.Row>
{/* Start burger menu for mobile and tablet only */}
<Grid.Column only="mobile tablet" mobile={4} tablet={1}>
<Button
basic
size="medium"
icon="sliders"
onClick={() => this.setState({ sidebarVisible: true })}
aria-label={i18next.t("Filter results")}
className="rel-mb-1"
/>
{/* End burger menu */}
</Grid.Column>
{config.aggs && <Grid.Column computer={4} />}
{communitiesStatusFilterEnabled && (
<Grid.Column
mobile={8}
tablet={4}
computer={4}
floated="right"
className="text-align-right-mobile"
>
<CommunitiesStatusFilter
myCommunitiesOnClick={() => {
this.setState({
selectedConfig: myCommunities,
});
}}
allCommunitiesOnClick={() => {
this.setState({
selectedConfig: allCommunities,
});
}}
appID={selectedAppId}
allCommunitiesSelected={selectedAppId === allCommunities.appId}
/>
</Grid.Column>
)}
<Grid.Column
mobile={16}
tablet={11}
computer={communitiesStatusFilterEnabled ? 8 : 12}
verticalAlign="middle"
floated="right"
>
<SearchBar
placeholder={toggleText}
autofocus={autofocus}
actionProps={{
"icon": "search",
"content": null,
"className": "search",
"aria-label": i18next.t("Search"),
}}
className="middle aligned"
/>
</Grid.Column>
</Grid.Row>

<Grid.Row className="community-list-results pt-2">
<GridResponsiveSidebarColumn
width={4}
open={sidebarVisible}
onHideClick={() => this.setState({ sidebarVisible: false })}
>
<SearchAppFacets aggs={config.aggs} appName={selectedAppId} />
</GridResponsiveSidebarColumn>
<Grid.Column mobile={16} tablet={16} computer={12}>
<SearchAppResultsPane
appName={selectedAppId}
layoutOptions={layoutOptions}
/>
</Grid.Column>
</Grid.Row>
</Grid>
</ReactSearchKit>
</SearchConfigurationContext.Provider>
</OverridableContext.Provider>
);
}
}

CommunitySelectionSearch.propTypes = {
apiConfigs: PropTypes.shape({
allCommunities: PropTypes.shape({
appId: PropTypes.string.isRequired,
initialQueryState: PropTypes.object.isRequired,
searchApi: PropTypes.object.isRequired,
}),
myCommunities: PropTypes.shape({
appId: PropTypes.string.isRequired,
initialQueryState: PropTypes.object.isRequired,
searchApi: PropTypes.object.isRequired,
}),
}),
autofocus: PropTypes.bool,
overriddenComponents: PropTypes.object,
config: PropTypes.object.isRequired,
communitiesStatusFilterEnabled: PropTypes.bool,
};

CommunitySelectionSearch.defaultProps = {
autofocus: true,
apiConfigs: {
allCommunities: {
initialQueryState: { size: 5, page: 1, sortBy: "bestmatch" },
searchApi: {
axios: {
url: "/api/communities",
headers: { Accept: "application/vnd.inveniordm.v1+json" },
},
},
appId: "ReactInvenioDeposit.CommunitySelectionSearch.AllCommunities",
toggleText: "Search in all communities",
},
myCommunities: {
initialQueryState: { size: 5, page: 1, sortBy: "bestmatch" },
searchApi: {
axios: {
url: "/api/user/communities",
headers: { Accept: "application/vnd.inveniordm.v1+json" },
},
},
appId: "ReactInvenioDeposit.CommunitySelectionSearch.MyCommunities",
toggleText: "Search in my communities",
},
},
overriddenComponents: {},
communitiesStatusFilterEnabled: true,
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export { CommunitiesSearchLayout } from "./CommunitiesSearchLayout";
export { ResultsGridItemTemplate } from "./ResultsGridItemTemplate";
export { CommunitiesSearchBarElement } from "./CommunitiesSearchBarElement";
export { CommunitiesEmptySearchResults } from "./CommunitiesEmptySearchResults";
export { CommunitySelectionSearch } from "./CommunitySelectionSearch";
export { CommunitiesStatusFilter } from "./CommunitiesStatusFilter";
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ContribBucketAggregationElement,
ContribBucketAggregationValuesElement,
ContribSearchAppFacets,
SearchResultsBox,
} from "@js/invenio_search_ui/components";
import { overrideStore, parametrize } from "react-overridable";
import {
Expand All @@ -21,7 +22,6 @@ import {
import {
RequestsSearchLayout,
RequestsEmptyResultsWithState,
RequestsResults,
} from "@js/invenio_requests/search";

const domContainer = document.getElementById("communities-request-search-root");
Expand All @@ -48,7 +48,7 @@ const defaultComponents = {
[`${appName}.ResultsList.item`]: RequestsResultsItemTemplateWithCommunity,
[`${appName}.ResultsGrid.item`]: () => null,
[`${appName}.SearchApp.layout`]: RequestsSearchLayoutWAppName,
[`${appName}.SearchApp.results`]: RequestsResults,
[`${appName}.SearchApp.results`]: SearchResultsBox,
[`${appName}.SearchBar.element`]: RecordSearchBarElement,
[`${appName}.EmptyResults.element`]: RequestsEmptyResultsWithState,
...defaultContribComponents,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// This file is part of Invenio
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is almost the same as https://github.com/inveniosoftware/invenio-requests/blob/master/invenio_requests/assets/semantic-ui/js/invenio_requests/search/RequestsResults.js (just renamed it). That makes me think this can be moved to a higher module so both requests and communities can reuse it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also in react-invenio-forms?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a search aware component, we should move it to invenio-search-ui?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a search aware component, we should move it to invenio-search-ui?

Exactly, I was just messaging Pablo about this. +1 on moving it to invenio-search-ui

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the discussion, I moved it to a reusable component named invenio_search_ui/components/SearchResultsBox. Not the best name but I'm open to changing it to anything else 😄

However, let's align IRL whether this is the right place / way or not . Ping @ntarocco @ptamarit @kpsherva

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was to create a base package, similar to invenio-base for Python code, that serves as a catalog of components. This package should remain stateless, without any specific features or logic, making it a simple dependency that can be included in other modules.

While I understand the rationale behind associating a component that displays search results with invenio-search-ui, I believe that it doesn't align with the intended purpose of the base package.

The base package is meant to be a neutral catalog, and incorporating such a component into invenio-search-ui would go against these criteria.

I am sure that we will find a case where we will need that component in a place where we don't depend on invenio-search-ui.

Happy to hear/read counterarguments.

// Copyright (C) 2024 CERN.
//
// Invenio is free software; you can redistribute it and/or modify it
// under the terms of the MIT License; see LICENSE file for more details.

import { InvenioSearchPagination } from "@js/invenio_search_ui/components";
import { i18next } from "@translations/invenio_requests/i18next";
import PropTypes from "prop-types";
import React from "react";
import { Count, ResultsList, Sort } from "react-searchkit";
import { Grid, Segment } from "semantic-ui-react";

export const SubcommunityResults = ({
alejandromumo marked this conversation as resolved.
Show resolved Hide resolved
sortOptions,
paginationOptions,
currentResultsState,
}) => {
const { total } = currentResultsState.data;
return (
total && (
<Grid>
<Grid.Row>
<Grid.Column width={16}>
<Segment>
<Grid>
<Grid.Row
verticalAlign="middle"
className="small pt-5 pb-5 highlight-background"
>
<Grid.Column width={4}>
<Count
label={() => (
<>
{i18next.t("{{count}} results found", {
count: total,
})}
</>
)}
/>
</Grid.Column>
<Grid.Column width={12} textAlign="right">
{sortOptions && (
<Sort
values={sortOptions}
label={(cmp) => (
<>
<label className="mr-10">{i18next.t("Sort by")}</label>
{cmp}
</>
)}
/>
)}
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column>
<ResultsList />
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
</Grid.Column>
</Grid.Row>
<InvenioSearchPagination paginationOptions={paginationOptions} />
</Grid>
)
);
};

SubcommunityResults.propTypes = {
sortOptions: PropTypes.object.isRequired,
paginationOptions: PropTypes.object.isRequired,
currentResultsState: PropTypes.object.isRequired,
};
Loading
Loading