Skip to content

Commit 1568d8e

Browse files
authored
Merge pull request #5109 from nulib/deploy/staging
Deploy v10.2.1 to production
2 parents 24ce958 + 305fe0a commit 1568d8e

File tree

115 files changed

+1513
-489
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+1513
-489
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,11 @@ jobs:
114114
run: mix credo
115115
working-directory: app
116116
- name: Elixir Tests
117-
run: mix test || mix test --failed
117+
run: mix test || mix test --seed $(cat ${MEADOW_TEST_SAVE_SEED}) --failed
118118
env:
119119
AWS_LOCALSTACK: true
120120
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
121+
MEADOW_TEST_SAVE_SEED: ${{ runner.temp }}/MEADOW_TEST_SEED
121122
working-directory: app
122123
# - name: Test DB Rollback
123124
# run: mix ecto.rollback --all
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { Button, Notification } from "@nulib/design-system";
2+
import { GET_OBSOLETE_CONTROLLED_TERMS } from "@js/components/Dashboards/dashboards.gql";
3+
import { IconImages } from "@js/components/Icon";
4+
import { useHistory } from "react-router-dom";
5+
import { useQuery } from "@apollo/client";
6+
7+
import React from "react";
8+
9+
const colHeaders = ["Obsolete Term", "Obsolete Label", "Replacement Term"];
10+
11+
export default function DashboardsObsoleteTermsList() {
12+
const history = useHistory();
13+
const [authorities, setAuthorities] = React.useState([]);
14+
const [limit, setLimit] = React.useState(100);
15+
16+
// GraphQL
17+
const { loading, error, data } = useQuery(GET_OBSOLETE_CONTROLLED_TERMS, {
18+
variables: { limit },
19+
pollInterval: 10000,
20+
});
21+
22+
const limitOptions = [25, 50, 100, 500];
23+
24+
function filterValues() {
25+
if (!data) return;
26+
setAuthorities([...data.obsoleteControlledTerms]);
27+
}
28+
29+
React.useEffect(() => {
30+
if (!data) return;
31+
filterValues();
32+
}, [data, limit]);
33+
34+
if (loading) return null;
35+
if (error) return <Notification isDanger>{error.toString()}</Notification>;
36+
37+
const handleViewClick = (value) => {
38+
history.push("/search", {
39+
passedInSearchTerm: `all_controlled_ids:\"${value}\"`,
40+
});
41+
};
42+
43+
return (
44+
<React.Fragment>
45+
<div
46+
className="is-flex is-justify-content-flex-end is-align-items-center mb-5"
47+
data-testid="obsolete-terms-dashboard-table-options"
48+
>
49+
<label className="is-flex is-align-items-center columns is-3">
50+
<span className="column">Item Count</span>
51+
<div className="is-flex is-align-items-center column">
52+
{limitOptions.map((option) => {
53+
return (
54+
<button
55+
key={option}
56+
className={`button ${limit === option ? "is-primary active" : "is-ghost"}`}
57+
onClick={() => setLimit(option)}
58+
>
59+
{option}
60+
</button>
61+
);
62+
})}
63+
</div>
64+
</label>
65+
</div>
66+
67+
<div className="table-container">
68+
<table
69+
className="table is-striped is-fullwidth"
70+
data-testid="obsolete-terms-dashboard-table"
71+
>
72+
<thead>
73+
<tr>
74+
{colHeaders.map((col) => (
75+
<th key={col}>{col}</th>
76+
))}
77+
<th></th>
78+
</tr>
79+
</thead>
80+
<tbody data-testid="obsolete-terms-table-body">
81+
{authorities.map((record) => {
82+
const { id = "", label = "", replacedBy = "" } = record;
83+
84+
return (
85+
<tr key={id} data-testid="obsolete-terms-row">
86+
<td><a href={id} target="_blank" rel="noopener noreferrer">{id}</a></td>
87+
<td>{label}</td>
88+
<td><a href={replacedBy} target="_blank" rel="noopener noreferrer">{replacedBy}</a></td>
89+
90+
<td className="has-text-right is-right mb-0">
91+
<div className="field is-grouped">
92+
<Button
93+
onClick={() => handleViewClick(id)}
94+
isLight
95+
data-testid="button-to-search"
96+
title="View works containing this record"
97+
>
98+
<IconImages />
99+
</Button>
100+
</div>
101+
</td>
102+
</tr>
103+
);
104+
})}
105+
</tbody>
106+
</table>
107+
</div>
108+
</React.Fragment>
109+
);
110+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React from "react";
2+
import { renderWithRouterApollo } from "@js/services/testing-helpers";
3+
import { screen, within } from "@testing-library/react";
4+
import DashboardsObsoleteTermsList from "./List";
5+
import { getObsoleteTermsMock, getObsoleteTermsSetLimitMock } from "@js/components/Dashboards/dashboards.gql.mock";
6+
import userEvent from "@testing-library/user-event";
7+
8+
describe("DashboardsObsoleteTermsList component", () => {
9+
beforeEach(() => {
10+
renderWithRouterApollo(<DashboardsObsoleteTermsList />, {
11+
mocks: [
12+
getObsoleteTermsMock,
13+
getObsoleteTermsSetLimitMock,
14+
],
15+
});
16+
});
17+
18+
it("renders", async () => {
19+
expect(await screen.findByTestId("obsolete-terms-dashboard-table"));
20+
});
21+
22+
it("renders the correct number of obsolete terms rows", async () => {
23+
const rows = await screen.findAllByTestId("obsolete-terms-row");
24+
expect(rows).toHaveLength(2);
25+
});
26+
27+
it("renders correct obsolete terms row details", async () => {
28+
const td = await screen.findByText(
29+
"http://id.authority.org/test/12345",
30+
);
31+
const row = td.closest("tr");
32+
const utils = within(row);
33+
expect(utils.getByText(/Obsolete Term 1/i));
34+
expect(utils.getByText(/http:\/\/id.authority.org\/test\/67890/i));
35+
});
36+
37+
it("renders correct obsolete terms query limit options", async () => {
38+
const options = await screen.findByTestId(
39+
"obsolete-terms-dashboard-table-options",
40+
);
41+
42+
const buttons = within(options).getAllByRole("button");
43+
44+
// expect 4 buttons
45+
expect(buttons).toHaveLength(4);
46+
47+
// expect button text content
48+
expect(buttons[0]).toHaveTextContent("25");
49+
expect(buttons[1]).toHaveTextContent("50");
50+
expect(buttons[2]).toHaveTextContent("100");
51+
expect(buttons[3]).toHaveTextContent("500");
52+
53+
// expect default active button
54+
expect(buttons[2]).toHaveClass("active", "is-primary");
55+
56+
// expect button click and active class change
57+
await userEvent.click(buttons[0]);
58+
expect(await screen.findByText("25")).toHaveClass("active", "is-primary");
59+
});
60+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ActionHeadline, PageTitle } from "@js/components/UI/UI";
2+
3+
import NLogo from "@js/components/northwesternN.svg";
4+
import React from "react";
5+
import UIIconText from "@js/components/UI/IconText";
6+
7+
function DashboardsObsoleteTermsTitleBar() {
8+
return (
9+
<React.Fragment>
10+
<ActionHeadline data-testid="obsolete-terms-title-bar">
11+
<PageTitle data-testid="obsolete-terms-dashboard-title">
12+
<UIIconText icon={<NLogo width="24px" height="24px" />}>
13+
Obsolete Controlled Terms Dashboard
14+
</UIIconText>
15+
</PageTitle>
16+
</ActionHeadline>
17+
</React.Fragment>
18+
);
19+
}
20+
21+
export default DashboardsObsoleteTermsTitleBar;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from "react";
2+
import { screen } from "@testing-library/react";
3+
import { renderWithRouterApollo } from "@js/services/testing-helpers";
4+
import DashboardsObsoleteTermsTitleBar from "./TitleBar";
5+
6+
describe("DashboardsObsoleteTermsTitleBar component", () => {
7+
beforeEach(() => {
8+
renderWithRouterApollo(<DashboardsObsoleteTermsTitleBar />);
9+
});
10+
11+
it("renders component, title and add new button", () => {
12+
expect(screen.getByTestId("obsolete-terms-title-bar"));
13+
expect(screen.getByTestId("obsolete-terms-dashboard-title"));
14+
});
15+
});

app/assets/js/components/Dashboards/dashboards.gql.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,13 @@ export const UPDATE_NUL_AUTHORITY_RECORD = gql`
159159
}
160160
}
161161
`;
162+
163+
export const GET_OBSOLETE_CONTROLLED_TERMS = gql`
164+
query ListObsoleteControlledTerms($limit: Int) {
165+
obsoleteControlledTerms(limit: $limit){
166+
id
167+
label
168+
replacedBy
169+
}
170+
}
171+
`

app/assets/js/components/Dashboards/dashboards.gql.mock.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
GET_NUL_AUTHORITY_RECORDS,
88
UPDATE_NUL_AUTHORITY_RECORD,
99
GET_PRESERVATION_CHECKS,
10+
GET_OBSOLETE_CONTROLLED_TERMS,
1011
} from "@js/components/Dashboards/dashboards.gql";
1112
import { errors as csvMetadataUpdateJobErrors } from "@js/components/Dashboards/Csv/Errors";
1213

@@ -143,6 +144,19 @@ export const mockNulAuthorityRecords = [
143144
},
144145
];
145146

147+
export const mockObsoleteTerms = [
148+
{
149+
id: "http://id.authority.org/test/12345",
150+
label: "Obsolete Term 1",
151+
replacedBy: "http://id.authority.org/test/67890",
152+
},
153+
{
154+
id: "http://id.authority.org/test/54321",
155+
label: "Obsolete Term 2",
156+
replacedBy: "http://id.authority.org/test/09876",
157+
},
158+
];
159+
146160
export const deleteNulAuthorityRecordMock = {
147161
request: {
148162
query: DELETE_NUL_AUTHORITY_RECORD,
@@ -256,3 +270,31 @@ export const updateNulAuthorityRecordMock = {
256270
},
257271
},
258272
};
273+
274+
export const getObsoleteTermsMock = {
275+
request: {
276+
query: GET_OBSOLETE_CONTROLLED_TERMS,
277+
variables: {
278+
limit: 100,
279+
},
280+
},
281+
result: {
282+
data: {
283+
obsoleteControlledTerms: mockObsoleteTerms,
284+
},
285+
},
286+
};
287+
288+
export const getObsoleteTermsSetLimitMock = {
289+
request: {
290+
query: GET_OBSOLETE_CONTROLLED_TERMS,
291+
variables: {
292+
limit: 25,
293+
},
294+
},
295+
result: {
296+
data: {
297+
obsoleteControlledTerms: mockObsoleteTerms,
298+
},
299+
},
300+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import React from "react";
2+
import { FiShare2 } from "react-icons/fi";
3+
4+
export default function IconLinkedData(props) {
5+
return <FiShare2 {...props} />;
6+
}

app/assets/js/components/Icon/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import IconEnter from "@js/components/Icon/Enter";
2626
import IconExternalLink from "@js/components/Icon/ExternalLink";
2727
import IconFile from "@js/components/Icon/File";
2828
import IconImages from "@js/components/Icon/Images";
29+
import IconLinkedData from "@js/components/Icon/LinkedData";
2930
import IconList from "@js/components/Icon/List";
3031
import IconMagic from "@js/components/Icon/Magic";
3132
import IconMinus from "@js/components/Icon/Minus";
@@ -71,6 +72,7 @@ export {
7172
IconExternalLink,
7273
IconFile,
7374
IconImages,
75+
IconLinkedData,
7476
IconList,
7577
IconMagic,
7678
IconMinus,

app/assets/js/components/UI/Layout/NavBar.jsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { GrDocumentCsv, GrMultiple } from "react-icons/gr";
1616
import {
1717
IconChart,
1818
IconCheck,
19+
IconLinkedData,
1920
IconSearch,
2021
IconUser,
2122
} from "@js/components/Icon";
@@ -193,6 +194,13 @@ const UILayoutNavBar = () => {
193194
</IconText>
194195
</Link>
195196
</UILayoutNavDropdownItem>
197+
<UILayoutNavDropdownItem>
198+
<Link to="/dashboards/obsolete-terms">
199+
<IconText icon={<IconLinkedData />}>
200+
Obsolete Controlled Terms
201+
</IconText>
202+
</Link>
203+
</UILayoutNavDropdownItem>
196204
<UILayoutNavDropdownItem>
197205
<Link to="/dashboards/preservation-checks">
198206
<IconText icon={<IconCheck />}>

0 commit comments

Comments
 (0)