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 4 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,252 @@
// This file is part of Invenio-RDM-Records
// 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, Icon, Menu } from "semantic-ui-react";
import PropTypes from "prop-types";
import {
SearchConfigurationContext,
SearchAppFacets,
SearchAppResultsPane,
} from "@js/invenio_search_ui/components";
import { GridResponsiveSidebarColumn, InvenioPopup } from "react-invenio-forms";

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

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

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,
overriddenComponents,
config,
userAnonymous,
} = this.props;

const searchApi = new InvenioSearchApi(selectedSearchApi);
console.log(selectedSearchApi);
alejandromumo marked this conversation as resolved.
Show resolved Hide resolved

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 className="m-0 pb-0 centered" relaxed padded>
<Grid.Row className="p-3">
{/* Start burguer menu for mobile and tablet only */}
alejandromumo marked this conversation as resolved.
Show resolved Hide resolved
<Grid.Column only="mobile tablet" mobile={3} tablet={1}>
<Button
basic
size="medium"
icon="sliders"
onClick={() => this.setState({ sidebarVisible: true })}
aria-label={i18next.t("Filter results")}
className="rel-mb-1"
/>
{/* End burguer menu */}
alejandromumo marked this conversation as resolved.
Show resolved Hide resolved
</Grid.Column>
<Grid.Column
mobile={13}
tablet={4}
computer={3}
floated="right"
className="text-align-right-mobile"
>
<Menu role="tablist" className="theme-primary-menu" compact>
<Menu.Item
as="button"
role="tab"
id="all-communities-tab"
aria-selected={selectedAppId === allCommunities.appId}
aria-controls={allCommunities.appId}
name="All"
active={selectedAppId === allCommunities.appId}
onClick={() =>
this.setState({
selectedConfig: allCommunities,
})
}
>
{i18next.t("All")}
</Menu.Item>
<Menu.Item
as="button"
role="tab"
id="my-communities-tab"
aria-selected={selectedAppId === myCommunities.appId}
aria-controls={myCommunities.appId}
Copy link
Member Author

@alejandromumo alejandromumo Oct 23, 2024

Choose a reason for hiding this comment

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

The front-page could reuse this "All / My communities" button. Is it worth moving it to its own component and re-using it? @ptamarit @0einstein0

I've found this to be a bit more complicated since it changes the apiConfig entirely

Copy link
Member

@ptamarit ptamarit Oct 24, 2024

Choose a reason for hiding this comment

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

I think that not showing the buttons to unauthenticated users (as currently done in the front page) would be better. It's more of a convenience than a crucial functionality; if a community owner happens to be logged out, they will still be able to find their community via text search.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good point. Regarding the question "should we make this a reusable component and use it in the frontpage", wdyt?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, right now, this is mostly the same as CommunitySelectionSearch in invenio-rdm-records.
It could go to react-invenio-forms and be used in all 3 places:

  • The community front page
  • The subcommunities search
  • The deposit form community selection

Copy link
Member Author

@alejandromumo alejandromumo Oct 24, 2024

Choose a reason for hiding this comment

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

I don't think this component to filter between "All" or "My communities" should be in react-invenio-forms. It's too specific for communities. To be moved even higher in the modules, it had to be more abstract.

I added a reusable component CommunitiesStatusFilter in invenio_communities/community/searchComponents and used it in the 3 places you mentioned.

name="My communities"
active={selectedAppId === myCommunities.appId}
disabled={userAnonymous}
onClick={() =>
this.setState({
selectedConfig: myCommunities,
})
}
>
{i18next.t("My communities")}
{userAnonymous && (
<InvenioPopup
popupId="disabled-my-communities-popup"
size="small"
trigger={
<Icon
className="mb-5 ml-5"
color="grey"
name="question circle outline"
/>
}
ariaLabel={i18next.t(
"You must be logged in to filter by your communities."
)}
content={i18next.t(
"You must be logged in to filter by your communities."
)}
/>
)}
</Menu.Item>
</Menu>
</Grid.Column>
<Grid.Column
mobile={16}
tablet={11}
computer={9}
verticalAlign="middle"
>
<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,
userAnonymous: 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: {},
userAnonymous: true,
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { CommunitiesSearchLayout } from "./CommunitiesSearchLayout";
export { ResultsGridItemTemplate } from "./ResultsGridItemTemplate";
export { CommunitiesSearchBarElement } from "./CommunitiesSearchBarElement";
export { CommunitiesEmptySearchResults } from "./CommunitiesEmptySearchResults";
export { CommunitySelectionSearch } from "./CommunitySelectionSearch";
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,
};
alejandromumo marked this conversation as resolved.
Show resolved Hide resolved
Empty file.
Loading
Loading