@truedat/se 6.1.4 → 6.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/se",
3
- "version": "6.1.4",
3
+ "version": "6.1.5",
4
4
  "description": "Truedat Web Search Engine",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -34,7 +34,7 @@
34
34
  "@testing-library/jest-dom": "^5.16.5",
35
35
  "@testing-library/react": "^12.0.0",
36
36
  "@testing-library/user-event": "^13.2.1",
37
- "@truedat/test": "6.1.4",
37
+ "@truedat/test": "6.1.5",
38
38
  "babel-jest": "^28.1.0",
39
39
  "babel-plugin-dynamic-import-node": "^2.3.3",
40
40
  "babel-plugin-lodash": "^3.3.4",
@@ -86,7 +86,7 @@
86
86
  ]
87
87
  },
88
88
  "dependencies": {
89
- "@truedat/core": "6.1.4",
89
+ "@truedat/core": "6.1.5",
90
90
  "path-to-regexp": "^1.7.0",
91
91
  "prop-types": "^15.8.1",
92
92
  "react-intl": "^5.20.10",
@@ -103,5 +103,5 @@
103
103
  "react-dom": ">= 16.8.6 < 17",
104
104
  "semantic-ui-react": ">= 2.0.3 < 2.2"
105
105
  },
106
- "gitHead": "c0ef00d553186b7601e136f3e38fa0d0d7287fd9"
106
+ "gitHead": "3cbadffa2227cac1cce3c21d8442245082324ac1"
107
107
  }
package/src/api.js CHANGED
@@ -1,3 +1,3 @@
1
- const API_GLOBAL_SEARCH = "/api/global_search";
2
-
3
- export { API_GLOBAL_SEARCH };
1
+ export const API_GLOBAL_SEARCH = "/api/global_search";
2
+ export const API_ELASTIC_INDEXES = "/api/elastic_indexes";
3
+ export const API_ELASTIC_INDEX = "/api/elastic_indexes/:index_name";
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import { useIntl } from "react-intl";
4
+ import { Button } from "semantic-ui-react";
5
+ import { ConfirmModal } from "@truedat/core/components";
6
+ import { useElasticIndexDelete } from "@truedat/se/hooks/useElasticIndexes";
7
+
8
+ export default function DeleteIndexButton({ indexName }) {
9
+ const { formatMessage } = useIntl();
10
+ const { trigger: deleteIndex, isMutating: loading } =
11
+ useElasticIndexDelete(indexName);
12
+ return (
13
+ <ConfirmModal
14
+ trigger={
15
+ <Button
16
+ disabled={loading}
17
+ content={formatMessage({
18
+ id: "elastic_index.delete",
19
+ })}
20
+ />
21
+ }
22
+ header={formatMessage({
23
+ id: "elastic_index.delete.confirmation.header",
24
+ })}
25
+ content={formatMessage(
26
+ { id: "elastic_index.delete.confirmation.content" },
27
+ { index_name: indexName }
28
+ )}
29
+ onConfirm={deleteIndex}
30
+ />
31
+ );
32
+ }
33
+ DeleteIndexButton.propTypes = {
34
+ indexName: PropTypes.string,
35
+ };
@@ -0,0 +1,69 @@
1
+ import React from "react";
2
+ import { FormattedMessage } from "react-intl";
3
+ import {
4
+ Header,
5
+ Icon,
6
+ Table,
7
+ Segment,
8
+ Dimmer,
9
+ Loader,
10
+ } from "semantic-ui-react";
11
+ import { useElasticIndexes } from "../hooks/useElasticIndexes";
12
+ import DeleteIndexButton from "./DeleteIndexButton";
13
+
14
+ export default function ElasticIndexes() {
15
+ const { indexes, loading } = useElasticIndexes();
16
+
17
+ return (
18
+ <Segment>
19
+ <Header as="h2">
20
+ <Icon circular name="check" />
21
+ <Header.Content>
22
+ <FormattedMessage id="elastic_index.header" />
23
+ <Header.Subheader>
24
+ <FormattedMessage id="elastic_index.subheader" />
25
+ </Header.Subheader>
26
+ </Header.Content>
27
+ </Header>
28
+ <Dimmer.Dimmable dimmed={loading}>
29
+ <Dimmer active={loading} inverted>
30
+ <Loader />
31
+ </Dimmer>
32
+ {indexes ? (
33
+ <Table>
34
+ <Table.Header>
35
+ <Table.Row>
36
+ <Table.HeaderCell
37
+ content={<FormattedMessage id={`elastic_index.key`} />}
38
+ />
39
+ <Table.HeaderCell
40
+ content={<FormattedMessage id={`elastic_index.alias`} />}
41
+ />
42
+ <Table.HeaderCell
43
+ content={<FormattedMessage id={`elastic_index.size`} />}
44
+ />
45
+ <Table.HeaderCell
46
+ content={<FormattedMessage id={`elastic_index.documents`} />}
47
+ />
48
+ <Table.HeaderCell
49
+ content={<FormattedMessage id={`elastic_index.action`} />}
50
+ />
51
+ </Table.Row>
52
+ </Table.Header>
53
+ <Table.Body>
54
+ {indexes.map(({ key, alias, size, documents }) => (
55
+ <Table.Row key={key}>
56
+ <Table.Cell content={key} />
57
+ <Table.Cell content={alias} />
58
+ <Table.Cell content={size} />
59
+ <Table.Cell content={documents} />
60
+ <Table.Cell content={<DeleteIndexButton indexName={key} />} />
61
+ </Table.Row>
62
+ ))}
63
+ </Table.Body>
64
+ </Table>
65
+ ) : null}
66
+ </Dimmer.Dimmable>
67
+ </Segment>
68
+ );
69
+ }
@@ -5,62 +5,74 @@ import {
5
5
  SEARCH_CONCEPTS,
6
6
  SEARCH_INGESTS,
7
7
  SEARCH_RESULTS,
8
- SEARCH_STRUCTURES
8
+ SEARCH_STRUCTURES,
9
+ ELASTICINDEXES,
9
10
  } from "@truedat/core/routes";
11
+ import { Unauthorized } from "@truedat/core/components";
12
+ import { useAuthorized } from "@truedat/core/hooks";
10
13
  import SearchLoader from "./SearchLoader";
11
14
  import SearchHome from "./SearchHome";
12
15
  import Search from "./Search";
16
+ import ElasticIndexes from "./ElasticIndexes";
13
17
 
14
- export const SearchRoutes = () => (
15
- <Route
16
- path={SEARCH}
17
- render={() => (
18
- <>
19
- <Route path={SEARCH} component={SearchLoader} exact />
20
- <Route path={SEARCH} component={SearchHome} exact />
21
- <Route
22
- path={SEARCH_RESULTS}
23
- exact
24
- render={() => (
25
- <>
26
- <SearchLoader />
27
- <Search />
28
- </>
29
- )}
30
- />
31
- <Route
32
- path={SEARCH_CONCEPTS}
33
- exact
34
- render={() => (
35
- <>
36
- <SearchLoader />
37
- <Search />
38
- </>
39
- )}
40
- />
41
- <Route
42
- path={SEARCH_INGESTS}
43
- exact
44
- render={() => (
45
- <>
46
- <SearchLoader />
47
- <Search />
48
- </>
49
- )}
50
- />
51
- <Route
52
- path={SEARCH_STRUCTURES}
53
- exact
54
- render={() => (
55
- <>
56
- <SearchLoader />
57
- <Search />
58
- </>
59
- )}
60
- />
61
- </>
62
- )}
63
- />
64
- );
18
+ export const SearchRoutes = () => {
19
+ const authorized = useAuthorized();
20
+ return (
21
+ <Route
22
+ path={SEARCH}
23
+ render={() => (
24
+ <>
25
+ <Route path={SEARCH} component={SearchLoader} exact />
26
+ <Route path={SEARCH} component={SearchHome} exact />
27
+ <Route
28
+ path={SEARCH_RESULTS}
29
+ exact
30
+ render={() => (
31
+ <>
32
+ <SearchLoader />
33
+ <Search />
34
+ </>
35
+ )}
36
+ />
37
+ <Route
38
+ path={SEARCH_CONCEPTS}
39
+ exact
40
+ render={() => (
41
+ <>
42
+ <SearchLoader />
43
+ <Search />
44
+ </>
45
+ )}
46
+ />
47
+ <Route
48
+ path={SEARCH_INGESTS}
49
+ exact
50
+ render={() => (
51
+ <>
52
+ <SearchLoader />
53
+ <Search />
54
+ </>
55
+ )}
56
+ />
57
+ <Route
58
+ path={SEARCH_STRUCTURES}
59
+ exact
60
+ render={() => (
61
+ <>
62
+ <SearchLoader />
63
+ <Search />
64
+ </>
65
+ )}
66
+ />
67
+ <Route
68
+ path={ELASTICINDEXES}
69
+ exact
70
+ render={() => (authorized ? <ElasticIndexes /> : <Unauthorized />)}
71
+ />
72
+ </>
73
+ )}
74
+ />
75
+ );
76
+ };
65
77
 
66
78
  export default SearchRoutes;
@@ -0,0 +1,75 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { useElasticIndexDelete } from "@truedat/se/hooks/useElasticIndexes";
5
+ import DeleteIndexButton from "../DeleteIndexButton";
6
+ import en from "../../messages/en";
7
+
8
+ jest.mock("@truedat/se/hooks/useElasticIndexes", () => {
9
+ const originalModule = jest.requireActual(
10
+ "@truedat/se/hooks/useElasticIndexes"
11
+ );
12
+ const trigger = jest.fn();
13
+ return {
14
+ __esModule: true,
15
+ ...originalModule,
16
+ useElasticIndexDelete: jest.fn(() => ({
17
+ trigger,
18
+ isMutating: false,
19
+ })),
20
+ };
21
+ });
22
+
23
+ const renderOpts = {
24
+ messages: {
25
+ en: {
26
+ ...en,
27
+ "confirmation.yes": "yes",
28
+ "confirmation.no": "no",
29
+ "elastic_index.delete": "elastic_index.delete",
30
+ "elastic_index.delete.confirmation.header":
31
+ "elastic_index.delete.confirmation.header",
32
+ "elastic_index.delete.confirmation.content":
33
+ "elastic_index.delete.confirmation.content",
34
+ },
35
+ },
36
+ };
37
+
38
+ describe("<DeleteIndexButton />", () => {
39
+ const indexName = "test_index";
40
+ it("matches the last snapshot", () => {
41
+ const { container } = render(
42
+ <DeleteIndexButton indexName={indexName} />,
43
+ renderOpts
44
+ );
45
+ expect(container).toMatchSnapshot();
46
+ });
47
+
48
+ it("triggers useElasticIndexDelete hook on confirm", () => {
49
+ const { getByRole } = render(
50
+ <DeleteIndexButton indexName={indexName} />,
51
+ renderOpts
52
+ );
53
+ userEvent.click(getByRole("button", { name: /delete/i }));
54
+ userEvent.click(getByRole("button", { name: "modal-affirmative-action" }));
55
+
56
+ expect(useElasticIndexDelete).toHaveBeenCalledWith(indexName);
57
+ const { trigger } = useElasticIndexDelete();
58
+
59
+ expect(trigger).toHaveBeenCalled();
60
+ });
61
+
62
+ it("do not triggers useElasticIndexDelete hook on cancel", () => {
63
+ jest.clearAllMocks();
64
+ const { getByRole } = render(
65
+ <DeleteIndexButton indexName={indexName} />,
66
+ renderOpts
67
+ );
68
+ userEvent.click(getByRole("button", { name: /delete/i }));
69
+ userEvent.click(getByRole("button", { name: "modal-negative-action" }));
70
+
71
+ expect(useElasticIndexDelete).toHaveBeenCalledWith(indexName);
72
+ const { trigger } = useElasticIndexDelete();
73
+ expect(trigger).toHaveBeenCalledTimes(0);
74
+ });
75
+ });
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import ElasticIndexes from "../ElasticIndexes";
4
+ import en from "../../messages/en";
5
+
6
+ jest.mock("@truedat/se/hooks/useElasticIndexes", () => {
7
+ const originalModule = jest.requireActual(
8
+ "@truedat/se/hooks/useElasticIndexes"
9
+ );
10
+ return {
11
+ __esModule: true,
12
+ ...originalModule,
13
+ useElasticIndexes: jest.fn(() => ({
14
+ indexes: [
15
+ { key: "index_1", alias: "alias_1", size: 123456, documents: 5 },
16
+ { key: "index_2", alias: "alias_2", size: 987654321, documents: 42 },
17
+ ],
18
+ })),
19
+ };
20
+ });
21
+
22
+ const renderOpts = {
23
+ messages: {
24
+ en: {
25
+ ...en,
26
+ "elastic_index.action": "elastic_index.action",
27
+ "elastic_index.documents": "elastic_index.documents",
28
+ "elastic_index.size": "elastic_index.size",
29
+ "elastic_index.alias": "elastic_index.alias",
30
+ "elastic_index.key": "elastic_index.key",
31
+ "elastic_index.delete": "elastic_index.delete",
32
+ "elastic_index.header": "elastic_index.header",
33
+ "elastic_index.subheader": "elastic_index.subheader",
34
+ "elastic_index.delete.confirmation.header":
35
+ "elastic_index.delete.confirmation.header",
36
+ "elastic_index.delete.confirmation.content":
37
+ "elastic_index.delete.confirmation.content",
38
+ },
39
+ },
40
+ };
41
+
42
+ describe("<ElasticIndexes />", () => {
43
+ it("matches the last snapshot", () => {
44
+ const { container } = render(<ElasticIndexes />, renderOpts);
45
+ expect(container).toMatchSnapshot();
46
+ });
47
+ });
@@ -2,6 +2,10 @@ import React from "react";
2
2
  import { shallow } from "enzyme";
3
3
  import { SearchRoutes } from "../SearchRoutes";
4
4
 
5
+ jest.mock("@truedat/core/hooks", () => ({
6
+ useAuthorized: jest.fn(() => true),
7
+ }));
8
+
5
9
  describe("<SearchRoutes />", () => {
6
10
  it("matches the latest snapshot", () => {
7
11
  const wrapper = shallow(<SearchRoutes />);
@@ -0,0 +1,11 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<DeleteIndexButton /> matches the last snapshot 1`] = `
4
+ <div>
5
+ <button
6
+ class="ui button"
7
+ >
8
+ elastic_index.delete
9
+ </button>
10
+ </div>
11
+ `;
@@ -0,0 +1,150 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ElasticIndexes /> matches the last snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui segment"
7
+ >
8
+ <h2
9
+ class="ui header"
10
+ >
11
+ <i
12
+ aria-hidden="true"
13
+ class="check circular icon"
14
+ />
15
+ <div
16
+ class="content"
17
+ >
18
+ elastic_index.header
19
+ <div
20
+ class="sub header"
21
+ >
22
+ elastic_index.subheader
23
+ </div>
24
+ </div>
25
+ </h2>
26
+ <div
27
+ class="dimmable"
28
+ >
29
+ <div
30
+ class="ui inverted dimmer"
31
+ >
32
+ <div
33
+ class="content"
34
+ >
35
+ <div
36
+ class="ui loader"
37
+ />
38
+ </div>
39
+ </div>
40
+ <table
41
+ class="ui table"
42
+ >
43
+ <thead
44
+ class=""
45
+ >
46
+ <tr
47
+ class=""
48
+ >
49
+ <th
50
+ class=""
51
+ >
52
+ elastic_index.key
53
+ </th>
54
+ <th
55
+ class=""
56
+ >
57
+ elastic_index.alias
58
+ </th>
59
+ <th
60
+ class=""
61
+ >
62
+ elastic_index.size
63
+ </th>
64
+ <th
65
+ class=""
66
+ >
67
+ elastic_index.documents
68
+ </th>
69
+ <th
70
+ class=""
71
+ >
72
+ elastic_index.action
73
+ </th>
74
+ </tr>
75
+ </thead>
76
+ <tbody
77
+ class=""
78
+ >
79
+ <tr
80
+ class=""
81
+ >
82
+ <td
83
+ class=""
84
+ >
85
+ index_1
86
+ </td>
87
+ <td
88
+ class=""
89
+ >
90
+ alias_1
91
+ </td>
92
+ <td
93
+ class=""
94
+ >
95
+ 123456
96
+ </td>
97
+ <td
98
+ class=""
99
+ >
100
+ 5
101
+ </td>
102
+ <td
103
+ class=""
104
+ >
105
+ <button
106
+ class="ui button"
107
+ >
108
+ elastic_index.delete
109
+ </button>
110
+ </td>
111
+ </tr>
112
+ <tr
113
+ class=""
114
+ >
115
+ <td
116
+ class=""
117
+ >
118
+ index_2
119
+ </td>
120
+ <td
121
+ class=""
122
+ >
123
+ alias_2
124
+ </td>
125
+ <td
126
+ class=""
127
+ >
128
+ 987654321
129
+ </td>
130
+ <td
131
+ class=""
132
+ >
133
+ 42
134
+ </td>
135
+ <td
136
+ class=""
137
+ >
138
+ <button
139
+ class="ui button"
140
+ >
141
+ elastic_index.delete
142
+ </button>
143
+ </td>
144
+ </tr>
145
+ </tbody>
146
+ </table>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ `;
@@ -15,5 +15,5 @@ export {
15
15
  SearchLoader,
16
16
  SearchPagination,
17
17
  SearchResults,
18
- SearchRoutes
18
+ SearchRoutes,
19
19
  };
@@ -0,0 +1,76 @@
1
+ import _ from "lodash/fp";
2
+ import { compile } from "path-to-regexp";
3
+ import useSWR from "swr";
4
+ import useSWRMutations from "swr/mutation";
5
+ import { renderHook } from "@testing-library/react-hooks";
6
+ import { apiJson, apiJsonDelete, JSON_OPTS } from "@truedat/core/services/api";
7
+ import { API_ELASTIC_INDEXES, API_ELASTIC_INDEX } from "../../api";
8
+ import { useElasticIndexes, useElasticIndexDelete } from "../useElasticIndexes";
9
+
10
+ jest.mock("swr", () => ({
11
+ __esModule: true,
12
+ ...jest.requireActual("swr"),
13
+ default: jest.fn(() => ({
14
+ data: {
15
+ data: {
16
+ data: [
17
+ { index_name: "index1", size: 123456 },
18
+ { index_name: "index2", size: 987654321 },
19
+ ],
20
+ },
21
+ },
22
+ error: null,
23
+ mutate: jest.fn(),
24
+ })),
25
+ }));
26
+
27
+ jest.mock("swr/mutation", () => ({
28
+ __esModule: true,
29
+ ...jest.requireActual("swr/mutation"),
30
+ default: jest.fn(),
31
+ }));
32
+
33
+ jest.mock("@truedat/core/services/api", () => ({
34
+ __esModule: true,
35
+ ...jest.requireActual("@truedat/core/services/api"),
36
+ apiJsonDelete: jest.fn(),
37
+ }));
38
+
39
+ describe("useElasticIndexes", () => {
40
+ it("useElasticIndexes calls useSWR with correct api route", () => {
41
+ const { result } = renderHook(() => useElasticIndexes());
42
+
43
+ expect(result.current).toMatchObject({
44
+ indexes: [{ index_name: "index1" }, { index_name: "index2" }],
45
+ error: null,
46
+ loading: false,
47
+ });
48
+
49
+ expect(useSWR).toHaveBeenCalledWith(API_ELASTIC_INDEXES, apiJson);
50
+ });
51
+ it("useElasticIndexes translates index size to megabytes", () => {
52
+ const { result } = renderHook(() => useElasticIndexes());
53
+
54
+ expect(result.current).toMatchObject({
55
+ indexes: [
56
+ { index_name: "index1", size: "0.12" },
57
+ { index_name: "index2", size: "941.90" },
58
+ ],
59
+ error: null,
60
+ loading: false,
61
+ });
62
+
63
+ expect(useSWR).toHaveBeenCalledWith(API_ELASTIC_INDEXES, apiJson);
64
+ });
65
+
66
+ it("useElasticIndexDelete calls useSWRMutations with correct api route", () => {
67
+ const index_name = "index_name_1";
68
+ renderHook(() => useElasticIndexDelete(index_name));
69
+ const [url, func] = _.last(useSWRMutations.mock.calls);
70
+
71
+ expect(url).toBe(compile(API_ELASTIC_INDEX)({ index_name }));
72
+ func(url);
73
+
74
+ expect(apiJsonDelete).toHaveBeenCalledWith(url, JSON_OPTS);
75
+ });
76
+ });
@@ -0,0 +1,37 @@
1
+ import { compile } from "path-to-regexp";
2
+ import useSWR, { useSWRConfig } from "swr";
3
+ import useSWRMutations from "swr/mutation";
4
+ import { apiJson, apiJsonDelete, JSON_OPTS } from "@truedat/core/services/api";
5
+ import { API_ELASTIC_INDEXES, API_ELASTIC_INDEX } from "../api";
6
+
7
+ const sizeInMb = (indexes) => {
8
+ return indexes
9
+ ? indexes.map((index) => ({
10
+ ...index,
11
+ size: (index.size / (1024 * 1024)).toFixed(2),
12
+ }))
13
+ : indexes;
14
+ };
15
+
16
+ export const useElasticIndexes = () => {
17
+ const { data, error, mutate } = useSWR(API_ELASTIC_INDEXES, apiJson);
18
+
19
+ const indexes = sizeInMb(data?.data?.data);
20
+
21
+ return { indexes, error, loading: !error && !data, mutate };
22
+ };
23
+
24
+ export const useElasticIndexDelete = (index_name) => {
25
+ const { mutate } = useSWRConfig();
26
+ const url = compile(API_ELASTIC_INDEX)({ index_name });
27
+
28
+ return useSWRMutations(
29
+ url,
30
+ (url) => {
31
+ apiJsonDelete(url, JSON_OPTS);
32
+ },
33
+ {
34
+ onSuccess: () => mutate(API_ELASTIC_INDEXES),
35
+ }
36
+ );
37
+ };
@@ -15,5 +15,5 @@ export default {
15
15
  "tabs.se.all": "All",
16
16
  "tabs.se.concepts": "Glossary",
17
17
  "tabs.se.structures": "Catalog",
18
- "tabs.se.ingests": "Data Request"
18
+ "tabs.se.ingests": "Data Request",
19
19
  };
@@ -15,5 +15,5 @@ export default {
15
15
  "tabs.se.all": "Todo",
16
16
  "tabs.se.concepts": "Glosario",
17
17
  "tabs.se.structures": "Catálogo",
18
- "tabs.se.ingests": "Peticiones de datos"
18
+ "tabs.se.ingests": "Peticiones de datos",
19
19
  };