Skip to content

Commit

Permalink
Add quicksight dashboard (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
dbernstein authored Jan 24, 2024
1 parent dccdbdc commit 2f3f3fa
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 1 deletion.
12 changes: 11 additions & 1 deletion src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import {
RightsStatusData,
CatalogServicesData,
SelfTestsData,
PatronData,
DiagnosticsData,
FeatureFlags,
SitewideAnnouncementsData,
StatisticsData,
QuickSightEmbeddedURLData,
} from "./interfaces";
import { CollectionData } from "@thepalaceproject/web-opds-client/lib/interfaces";
import DataFetcher from "@thepalaceproject/web-opds-client/lib/DataFetcher";
Expand Down Expand Up @@ -190,6 +190,7 @@ export default class ActionCreator extends BaseActionCreator {
static readonly RESET_ADOBE_ID = "RESET_ADOBE_ID";

static readonly DIAGNOSTICS = "DIAGNOSTICS";
static readonly QUICKSIGHT_EMBEDDED_URL = "QUICKSIGHT_EMBEDDED_URL";

csrfToken: string;

Expand Down Expand Up @@ -1066,4 +1067,13 @@ export default class ActionCreator extends BaseActionCreator {
value,
};
}

fetchQuicksightEmbedUrl(dashboardId: string, ld: LibrariesData) {
const library_uuids: string = ld.libraries.map((l) => l.uuid).join(",");
const url = `/admin/quicksight_embed/${dashboardId}?libraryUuids=${library_uuids}`;
return this.fetchJSON<QuickSightEmbeddedURLData>(
ActionCreator.QUICKSIGHT_EMBEDDED_URL,
url
).bind(this);
}
}
110 changes: 110 additions & 0 deletions src/components/QuicksightDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import * as React from "react";
import {
QuickSightEmbeddedURLData,
LibrariesData,
LibraryData,
} from "../interfaces";
import { Store } from "redux";
import { RootState } from "../store";
import { connect } from "react-redux";
import ActionCreator from "../actions";

export interface QuicksightDashboardStateProps {
isFetchingLibraries?: boolean;
libraries?: LibraryData[];
}

export interface QuicksightDashboardDispatchProps {
fetchLibraries?: () => Promise<LibrariesData>;
fetchQuicksightEmbedUrl?: (
dashboardId: string,
ld: LibrariesData
) => Promise<QuickSightEmbeddedURLData>;
}

export interface QuicksightDashboardOwnProps {
store?: Store<RootState>;
dashboardId?: string;
}

export interface QuicksightDashboardProps
extends QuicksightDashboardStateProps,
QuicksightDashboardDispatchProps,
QuicksightDashboardOwnProps {}

export interface QuicksightDashboardState {
embedUrl: string;
}

class QuicksightDashboard extends React.Component<
QuicksightDashboardProps,
QuicksightDashboardState
> {
constructor(props) {
super(props);
this.state = { embedUrl: null };
}
componentDidMount() {
if (this.state.embedUrl) {
return;
}
// Every time the component is mounted a fresh embed url must be fetched.
// TODO: For whatever reason, the "this.props.libraries" variable was not being
// set when I tried to put this logic in the render method (nor did it work trying to access it in this method).
// Clearly I am missing something. Also the embed url should be set via the action/reducer pattern,
// but again I wasn't able to get things working following the pattern in Header.tsx so this is where I landed.
// It's ugly but it works.
// Nevertheless it should be brought into alignment before it goes into production.
this.props.fetchLibraries().then((libs) => {
try {
this.props
.fetchQuicksightEmbedUrl(this.props.dashboardId, libs)
.then((data) => this.setState({ embedUrl: data.embedUrl }))
.catch((error) => {
console.error(error);
});
} catch (e) {
console.log(e);
}
});
}

render(): JSX.Element {
return (
<iframe
title="Library Dashboard"
height="1200"
width="100%"
src={this.state.embedUrl}
/>
);
}
}

function mapStateToProps(state, ownProps) {
return {
isFetchingLibraries: state.editor.libraries?.isFetching,
libraries: state.editor.libraries?.data?.libraries,
};
}

function mapDispatchToProps(dispatch) {
const actions = new ActionCreator();
return {
fetchLibraries: () => dispatch(actions.fetchLibraries()),
fetchQuicksightEmbedUrl: (
dashboardId: string,
librariesData: LibrariesData
) => dispatch(actions.fetchQuicksightEmbedUrl(dashboardId, librariesData)),
};
}

const ConnectedQuicksightDashboard = connect<
QuicksightDashboardStateProps,
QuicksightDashboardDispatchProps,
QuicksightDashboardOwnProps
>(
mapStateToProps,
mapDispatchToProps
)(QuicksightDashboard);
export default ConnectedQuicksightDashboard;
57 changes: 57 additions & 0 deletions src/components/QuicksightDashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as React from "react";
import { Store } from "redux";
import { RootState } from "../store";
import * as PropTypes from "prop-types";
import Header from "./Header";
import Footer from "./Footer";
import title from "../utils/title";
import QuicksightDashboard from "./QuicksightDashboard";

export interface QuicksightDashboardPageProps
extends React.Props<QuicksightDashboardPageProps> {
params: {
library?: string;
};
}

export interface QuicksightDashboardPageContext {
editorStore: Store<RootState>;
}

/** Page holds quicksight dashboards. */
export default class QuicksightDashboardPage extends React.Component<
QuicksightDashboardPageProps
> {
context: QuicksightDashboardPageContext;

static contextTypes: React.ValidationMap<QuicksightDashboardPageContext> = {
editorStore: PropTypes.object.isRequired as React.Validator<Store>,
};

static childContextTypes: React.ValidationMap<object> = {
library: PropTypes.func,
};

getChildContext() {
return {
library: () => this.props.params.library,
};
}

render(): JSX.Element {
const { library } = this.props.params;
return (
<div className="quicksight-dashboard">
<Header />
<main className="body">
<QuicksightDashboard dashboardId="library" />
</main>
<Footer />
</div>
);
}

UNSAFE_componentWillMount() {
document.title = title("Quicksight Dashboard");
}
}
5 changes: 5 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import CatalogPage from "./components/CatalogPage";
import CustomListPage from "./components/CustomListPage";
import LanePage from "./components/LanePage";
import DashboardPage from "./components/DashboardPage";
import QuicksightDashboardPage from "./components/QuicksightDashboardPage";
import ConfigPage from "./components/ConfigPage";
import AccountPage from "./components/AccountPage";
import SetupPage from "./components/SetupPage";
Expand Down Expand Up @@ -87,6 +88,10 @@ class CirculationAdmin {
path="/admin/web/dashboard(/:library)"
component={DashboardPage}
/>
<Route
path="/admin/web/quicksight"
component={QuicksightDashboardPage}
/>
<Route
path="/admin/web/config(/:tab)(/:editOrCreate)(/:identifier)"
component={ConfigPage}
Expand Down
4 changes: 4 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,3 +470,7 @@ export interface AdvancedSearchQuery {
export interface AdvancedSearchData {
query: AdvancedSearchQuery;
}

export interface QuickSightEmbeddedURLData {
embedUrl: string;
}
7 changes: 7 additions & 0 deletions src/stylesheets/quicksight-dashboard.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.quicksight-dashboard {
.body {
height: 100%;
margin: 10px;
margin-top: 60px;
}
}
55 changes: 55 additions & 0 deletions tests/jest/components/QuicksightDashboard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from "react";
import { screen, waitFor } from "@testing-library/react";

import QuicksightDashboard from "../../../src/components/QuicksightDashboard";
import { LibrariesData, LibraryData } from "../../../src/interfaces";
import buildStore, { RootState } from "../../../src/store";
import { setupServer } from "msw/node";
import { rest } from "msw";
import renderWithContext from "../testUtils/renderWithContext";

const libraries: LibrariesData = { libraries: [{ uuid: "my-uuid" }] };
const dashboardId = "test";
const embedUrl = "http://embedUrl";
const dashboardUrlData = { embedUrl: embedUrl };

describe("QuicksightDashboard", () => {
const server = setupServer(
rest.get("*/admin/libraries", (req, res, ctx) => res(ctx.json(libraries))),

rest.get(
"*/admin/quicksight_embed/" +
dashboardId +
"?libraryUuids=" +
libraries["libraries"][0]["uuid"],
(req, res, ctx) => res(ctx.json(dashboardUrlData))
)
);

beforeAll(() => {
server.listen();
});

afterAll(() => {
server.close();
});

it("embed url is retrieved and set in iframe", async () => {
const contextProviderProps = {
csrfToken: "",
roles: [{ role: "system" }],
};

renderWithContext(
<QuicksightDashboard dashboardId={dashboardId} store={buildStore()} />,
contextProviderProps
);

await waitFor(() => {
expect(screen.getAllByTitle("Library Dashboard")[0]).toHaveAttribute(
"src",
embedUrl
);
});
});
});

0 comments on commit 2f3f3fa

Please sign in to comment.