Skip to content

Commit

Permalink
administration: add restore and delete community
Browse files Browse the repository at this point in the history
  • Loading branch information
kpsherva committed Sep 23, 2023
1 parent 4856698 commit 54a0778
Show file tree
Hide file tree
Showing 21 changed files with 876 additions and 37 deletions.
49 changes: 39 additions & 10 deletions invenio_communities/administration/communities.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
# details.

"""Invenio administration OAI-PMH view module."""
from flask import app, current_app, has_app_context
from functools import partial

from flask import current_app
from invenio_administration.views.base import (
AdminResourceDetailView,
AdminResourceListView,
)
from invenio_search_ui.searchconfig import search_app_config

from invenio_communities.communities.schema import CommunityFeaturedSchema

Expand All @@ -36,28 +39,54 @@ class CommunityListView(AdminResourceListView):
display_edit = False

item_field_list = {
"slug": {
"text": "Slug",
"order": 1,
},
"metadata.title": {"text": "Title", "order": 2},
"slug": {"text": "Slug", "order": 1, "width": 1},
"metadata.title": {"text": "Title", "order": 2, "width": 4},
# This field is for display only, it won't work on forms
"ui.type.title_l10n": {"text": "Type", "order": 3},
"featured": {"text": "Featured", "order": 4},
"created": {"text": "Created", "order": 5},
"ui.type.title_l10n": {"text": "Type", "order": 3, "width": 2},
"featured": {"text": "Featured", "order": 4, "width": 1},
"created": {"text": "Created", "order": 5, "width": 2},
}

actions = {
"featured": {
"text": "Feature",
"payload_schema": CommunityFeaturedSchema,
"order": 1,
}
},
# custom components in the UI
"delete": {
"text": "Delete",
"payload_schema": None,
"order": 2,
},
# custom components in the UI
"restore": {
"text": "Restore",
"payload_schema": None,
"order": 2,
},
}
search_config_name = "COMMUNITIES_SEARCH"
search_facets_config_name = "COMMUNITIES_FACETS"
search_sort_config_name = "COMMUNITIES_SORT_OPTIONS"

def init_search_config(self):
"""Build search view config."""
return partial(
search_app_config,
config_name=self.get_search_app_name(),
available_facets=current_app.config.get(self.search_facets_config_name),
sort_options=current_app.config[self.search_sort_config_name],
endpoint=self.get_api_endpoint(),
headers=self.get_search_request_headers(),
initial_filters=[["status", "P"]],
hidden_params=[
["include_deleted", "1"],
],
page=1,
size=30,
)

@staticmethod
def disabled():
"""Disable the view on demand."""
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { APIRoutes } from "./routes";
import { http } from "react-invenio-forms";

const getFeatured = async (apiEndpoint) => {
return await http.get(apiEndpoint);
};

const deleteCommunity = async (community, payload) => {
const reason = payload["removal_reason"];
payload["removal_reason"] = { id: reason };
// WARNING: Axios does not accept payload without data key
return await http.delete(APIRoutes.delete(community), { data: { ...payload } });
};

const restore = async (record) => {
return await http.post(APIRoutes.restore(record));
};

export const InvenioAdministrationCommunitiesApi = {
getFeatured: getFeatured,
delete: deleteCommunity,
restore: restore,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { InvenioAdministrationCommunitiesApi } from "./api";
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import _get from "lodash/get";

const APIRoutesGenerators = {
delete: (record, idKeyPath = "id") => {
return `/api/communities/${_get(record, idKeyPath)}`;
},
restore: (record, idKeyPath = "id") => {
return `/api/communities/${_get(record, idKeyPath)}/restore`;
},
};

export const APIRoutes = {
...APIRoutesGenerators,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* // This file is part of Invenio-App-Rdm
* // Copyright (C) 2023 CERN.
* //
* // Invenio-App-Rdm 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 { RestoreConfirmation } from "./RestoreConfirmation";
import TombstoneForm from "./TombstoneForm";
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Button, Modal, Icon } from "semantic-ui-react";
import { ActionModal, ActionForm } from "@js/invenio_administration";
import _isEmpty from "lodash/isEmpty";
import { i18next } from "@translations/invenio_app_rdm/i18next";

export class RecordResourceActions extends Component {
constructor(props) {
super(props);
this.state = {
modalOpen: false,
modalHeader: undefined,
modalBody: undefined,
};
}

onModalTriggerClick = (e, { payloadSchema, dataName, dataActionKey }) => {
const { resource } = this.props;

if (dataActionKey === "delete") {
this.setState({
modalOpen: true,
modalHeader: i18next.t("Delete community"),
modalBody: (
<TombstoneForm
actionSuccessCallback={this.handleSuccess}
actionCancelCallback={this.closeModal}
resource={resource}
/>
),
});
} else if (dataActionKey === "restore") {
this.setState({
modalOpen: true,
modalHeader: i18next.t("Restore community"),
modalBody: (
<RestoreConfirmation
actionSuccessCallback={this.handleSuccess}
actionCancelCallback={this.closeModal}
resource={resource}
/>
),
});
} else {
this.setState({
modalOpen: true,
modalHeader: dataName,
modalBody: (
<ActionForm
actionKey={dataActionKey}
actionSchema={payloadSchema}
actionSuccessCallback={this.handleSuccess}
actionCancelCallback={this.closeModal}
resource={resource}
/>
),
});
}
};

closeModal = () => {
this.setState({
modalOpen: false,
modalHeader: undefined,
modalBody: undefined,
});
};

handleSuccess = () => {
const { successCallback } = this.props;
this.setState({
modalOpen: false,
modalHeader: undefined,
modalBody: undefined,
});
successCallback();
};

render() {
const { actions, Element, resource } = this.props;
const { modalOpen, modalHeader, modalBody } = this.state;
return (
<>
{Object.entries(actions).map(([actionKey, actionConfig]) => {

Check warning on line 95 in invenio_communities/assets/semantic-ui/js/invenio_communities/administration/components/RecordResourceActions.js

View workflow job for this annotation

GitHub Actions / Tests (3.8, pypi, postgresql13, opensearch2)

Array.prototype.map() expects a value to be returned at the end of arrow function

Check warning on line 95 in invenio_communities/assets/semantic-ui/js/invenio_communities/administration/components/RecordResourceActions.js

View workflow job for this annotation

GitHub Actions / Tests (3.8, pypi, postgresql13, elasticsearch7)

Array.prototype.map() expects a value to be returned at the end of arrow function

Check warning on line 95 in invenio_communities/assets/semantic-ui/js/invenio_communities/administration/components/RecordResourceActions.js

View workflow job for this annotation

GitHub Actions / Tests (3.9, pypi, postgresql13, opensearch2)

Array.prototype.map() expects a value to be returned at the end of arrow function

Check warning on line 95 in invenio_communities/assets/semantic-ui/js/invenio_communities/administration/components/RecordResourceActions.js

View workflow job for this annotation

GitHub Actions / Tests (3.9, pypi, postgresql13, elasticsearch7)

Array.prototype.map() expects a value to be returned at the end of arrow function
if (actionKey === "delete" && !resource.deletion_status.is_deleted) {
return (
<Element
key={actionKey}
onClick={this.onModalTriggerClick}
payloadSchema={actionConfig.payload_schema}
dataName={actionConfig.text}
dataActionKey={actionKey}
icon
fluid
labelPosition="left"
>
<Icon name="trash alternate" />
{actionConfig.text}
</Element>
);
} else if (actionKey === "restore" && resource.deletion_status.is_deleted) {
return (
<Element
key={actionKey}
onClick={this.onModalTriggerClick}
payloadSchema={actionConfig.payload_schema}
dataName={actionConfig.text}
dataActionKey={actionKey}
icon
fluid
labelPosition="left"
>
<Icon name="undo" />
{actionConfig.text}
</Element>
);
} else if (!resource.deletion_status.is_deleted && actionKey !== "restore") {
return (
<Element
key={actionKey}
onClick={this.onModalTriggerClick}
payloadSchema={actionConfig.payload_schema}
dataName={actionConfig.text}
dataActionKey={actionKey}
>
{actionConfig.text}
</Element>
);
}
})}
<ActionModal modalOpen={modalOpen} resource={resource}>
{modalHeader && <Modal.Header>{modalHeader}</Modal.Header>}
{!_isEmpty(modalBody) && modalBody}
</ActionModal>
</>
);
}
}

RecordResourceActions.propTypes = {
resource: PropTypes.object.isRequired,
successCallback: PropTypes.func.isRequired,
actions: PropTypes.shape({
text: PropTypes.string.isRequired,
payload_schema: PropTypes.object.isRequired,
order: PropTypes.number.isRequired,
}),
Element: PropTypes.node,
};

RecordResourceActions.defaultProps = {
Element: Button,
actions: undefined,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* // This file is part of Invenio-App-Rdm
* // Copyright (C) 2023 CERN.
* //
* // Invenio-App-Rdm 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 React, { Component } from "react";
import PropTypes from "prop-types";
import { Dropdown, http, withCancel } from "react-invenio-forms";
import { Dropdown as SUIDropdown } from "semantic-ui-react";
import { i18next } from "@translations/invenio_app_rdm/i18next";

export default class RemovalReasonsSelect extends Component {
constructor(props) {
super(props);
this.state = {
options: undefined,
loading: false,
defaultOpt: undefined,
};
}

componentDidMount() {
this.fetchOptions();
}

componentWillUnmount() {
this.cancellableAction && this.cancellableAction.cancel();
}

fetchOptions = async () => {
const { setFieldValue } = this.props;
this.setState({ loading: true });
const url = "/api/vocabularies/removalreasons";
this.cancellableAction = withCancel(
http.get(url, {
headers: {
Accept: "application/vnd.inveniordm.v1+json",
},
})
);
try {
const response = await this.cancellableAction.promise;
const options = response.data.hits.hits;

const defaultOpt = options
? { text: options[0].title_l10n, value: options[0].id, key: options[0].id }
: {};
this.setState({
options: options,
loading: false,
defaultOpt: defaultOpt,
});
setFieldValue("removal_reason", defaultOpt.value);
} catch (e) {
this.setState({ loading: false });
console.error(e);
}
};

render() {
const { loading, options, defaultOpt } = this.state;

if (loading) {
return <SUIDropdown loading={loading} />;
}

return (
<Dropdown
required
label={i18next.t("Unavailability statement")}
options={options}
fieldPath="removal_reason"
defaultValue={defaultOpt}
clearable={false}
/>
);
}
}

RemovalReasonsSelect.propTypes = {
setFieldValue: PropTypes.func.isRequired,
};
Loading

0 comments on commit 54a0778

Please sign in to comment.