Skip to content

Commit

Permalink
dashboard: add list records widget
Browse files Browse the repository at this point in the history
  • Loading branch information
jrcastro2 committed Sep 5, 2022
1 parent 8aefcdc commit b37cc02
Show file tree
Hide file tree
Showing 10 changed files with 3,318 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { APIRoutes } from "./routes";
import { http } from "./config";

const getResource = async (
apiEndpoint,
pid,
) => {
const getResource = async (apiEndpoint, pid) => {
return await http.get(APIRoutes.get(apiEndpoint, pid));
};

const deleteResource = async (
resource,
apiEndpoint,
idKeyPath = "pid"
) => {
return await http.delete(APIRoutes.detailsView(apiEndpoint, resource, idKeyPath));
const searchResource = async (apiEndpoint, query, sort, page, size) => {
return await http.get(APIRoutes.search(apiEndpoint, query, sort, page, size));
};

const editResource = async(apiEndpoint, pid, payload) => {
return await http.put(APIRoutes.get(apiEndpoint,pid), payload);
const deleteResource = async (resource, apiEndpoint, idKeyPath = "pid") => {
return await http.delete(
APIRoutes.detailsView(apiEndpoint, resource, idKeyPath)
);
};

const editResource = async (apiEndpoint, pid, payload) => {
return await http.put(APIRoutes.get(apiEndpoint, pid), payload);
};

const createResource = () => {};
Expand All @@ -30,5 +27,5 @@ export const InvenioAdministrationActionsApi = {
deleteResource: deleteResource,
editResource: editResource,
getResource: getResource,
searchResource: searchResource,
};

Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ const apiConfig = {
withCredentials: true,
xsrfCookieName: "csrftoken",
xsrfHeaderName: "X-CSRFToken",
baseURL: "/",
headers: {
"Accept": "application/json",
Accept: "application/json",
"Content-Type": "application/json",
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import _get from "lodash/get";

const APIRoutesGenerators = {
detailsView: (routePrefix, resource, idKeyPath="pid") => {
detailsView: (routePrefix, resource, idKeyPath = "pid") => {
return `${routePrefix}/${_get(resource, idKeyPath)}`;
},
get: (routePrefix, pid) => {
return `${routePrefix}/${pid}`;
},
}
search: (routePrefix, query, sort, page, size) => {
return `${routePrefix}?q=${query}&sort=${sort}&page=${page}&size=${size}`;
},
};

export const APIRoutes = {
...APIRoutesGenerators
}
...APIRoutesGenerators,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";
import { InvenioAdministrationActionsApi } from "../api/actions";
import { Table, Container, Loader, Header, Message } from "semantic-ui-react";
import _get from "lodash/get";
import _isEmpty from "lodash/isEmpty";
import _upperFirst from "lodash/upperFirst";

class TableListView extends React.Component {
constructor() {
super();
this.state = {
hits: [],
isLoading: false,
sortedFields: {},
};
}
fetchValues = async () => {
const { apiEndpoint, query, sort, page, size } = this.props;
return await InvenioAdministrationActionsApi.searchResource(
apiEndpoint,
query,
sort,
page,
size
);
};

async componentDidMount() {
this.setState({ isLoading: true });
const { fields } = this.props;
const sortFields = (fields) =>
Object.entries(fields).sort((a, b) => a[1].order > b[1].order);
const response = await this.fetchValues();
const sortedFields = sortFields(fields);
console.log("sortedFields", sortedFields);
console.log("fields", fields);
this.setState({
hits: response.data.hits.hits,
isLoading: false,
sortedFields: sortedFields,
});
}

displayNoHits = () => {
return <Message>No results found</Message>;
};

displayTable = () => {
const { hits, sortedFields } = this.state;

return (
<>
<Table>
<Table.Header>
<Table.Row>
{sortedFields.map(([property, { text, order }]) => {
return (
<Table.HeaderCell data-testid={`header-${property}`}>
{text}
</Table.HeaderCell>
);
})}
</Table.Row>
</Table.Header>

<Table.Body>
{hits.map((hit) => {
return (
<Table.Row>
{sortedFields.map(([property, { text, order }]) => {
return <Table.Cell>{_get(hit, property)}</Table.Cell>;
})}
</Table.Row>
);
})}
</Table.Body>
</Table>
</>
);
};

render() {
const { isLoading, hits } = this.state;
const { header } = this.props;

return (
<Container>
<Header>{header}</Header>
{isLoading ? (
<Loader active />
) : !_isEmpty(hits) ? (
this.displayTable()
) : (
this.displayNoHits()
)}
</Container>
);
}
}

TableListView.propTypes = {
apiEndpoint: PropTypes.string.isRequired,
fields: PropTypes.object.isRequired,
header: PropTypes.string.isRequired,
query: PropTypes.string,
sort: PropTypes.string,
page: PropTypes.number,
size: PropTypes.number,
};

TableListView.defaultProps = {
query: "",
sort: "newest",
page: 1,
size: 5,
};

export function createTableListView(rootElement, props) {
return ReactDOM.render(<TableListView {...props} />, rootElement);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { TableListView } from "./TableListView";

let container;

beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});

afterEach(() => {
// cleanup on exiting
unmountComponentAtNode(container);
container.remove();
container = null;
});

it("Can render public access - record without files, public comm, not embargoed", () => {
const testProps = {
header: "Test header",
apiEndpoint: "/api/test",
fields: {
"metadata.title": { text: "Title", order: 1 },
"metadata.subtitle": { text: "Subtitle", order: 2 },
},
};

act(() => {
render(<TableListView {...testProps} />, container);
});

// check restricted button active
expect(
container.querySelector(
'[data-testid="protection-buttons-component-restricted"]'
)
).toHaveClass("active");

// check public button not active
expect(
container.querySelector(
'[data-testid="protection-buttons-component-public"]'
)
).not.toHaveClass("active");

// check embargo checkbox disabled
expect(
container.querySelector('[data-testid="embargo-checkbox-component"]')
).not.toHaveClass("disabled");

// check embargo option disabled
expect(
container.querySelector('[data-testid="option-list-embargo"]')
).not.toHaveClass("disabled");

// check if message informs about restriction
expect(
container.querySelector('[data-testid="access-message"]').textContent
).toContain("Restricted");

// check if files disabled
expect(
container.querySelector('[data-testid="access-files"]').textContent
).toContain("The record has no files.");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { createTableListView } from "./TableListView";

export { createTableListView };

const featuredCommunitiesDomContainer = document.getElementById(
"featured-communities-table-view-widget"
);
const communityDomContainer = document.getElementById(
"community-table-view-widget"
);
const oaipmhDomContainer = document.getElementById("oaipmh-table-view-widget");

const communityProps = {
header: "New communities",
apiEndpoint: "api/communities",
fields: {
"metadata.title": { text: "Title", order: 1 },
"metadata.type.title.en": { text: "Type", order: 2 },
"access.visibility": { text: "Restriction", order: 3 },
},
};

const featuredCommunitiesProps = {
header: "Featured communities",
apiEndpoint: "/api/communities/featured",
fields: {
"metadata.title": { text: "Title", order: 1 },
},
};

const oaipmhProps = {
header: "OAI-PMH Sets",
apiEndpoint: "api/oaipmh/sets",
fields: {
name: { text: "Name", order: 1 },
spec: { text: "Spec", order: 2 },
description: { text: "Description", order: 2 },
},
sort: "created",
};

createTableListView(featuredCommunitiesDomContainer, featuredCommunitiesProps);
createTableListView(communityDomContainer, communityProps);
createTableListView(oaipmhDomContainer, oaipmhProps);
Loading

0 comments on commit b37cc02

Please sign in to comment.