@truedat/bg 7.2.3 → 7.2.4

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/bg",
3
- "version": "7.2.3",
3
+ "version": "7.2.4",
4
4
  "description": "Truedat Web Business Glossary",
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": "7.2.3",
37
+ "@truedat/test": "7.2.4",
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,9 +86,9 @@
86
86
  ]
87
87
  },
88
88
  "dependencies": {
89
- "@truedat/core": "7.2.3",
90
- "@truedat/df": "7.2.3",
91
- "@truedat/lm": "7.2.3",
89
+ "@truedat/core": "7.2.4",
90
+ "@truedat/df": "7.2.4",
91
+ "@truedat/lm": "7.2.4",
92
92
  "decode-uri-component": "^0.2.2",
93
93
  "file-saver": "^2.0.5",
94
94
  "moment": "^2.29.4",
@@ -111,5 +111,5 @@
111
111
  "react-dom": ">= 16.8.6 < 17",
112
112
  "semantic-ui-react": ">= 2.0.3 < 2.2"
113
113
  },
114
- "gitHead": "1f8b6f4d6fbd6e278493c35885e540c14f9c551b"
114
+ "gitHead": "3371f59270a344803524395e395f6a422d879d01"
115
115
  }
@@ -23,6 +23,7 @@ import {
23
23
  CONCEPTS,
24
24
  } from "@truedat/core/routes";
25
25
  import { useIntl } from "react-intl";
26
+ import { getSidemenuGlossarySubscopes } from "../selectors/getSidemenuGlossarySubscopes";
26
27
  import Concept from "./Concept";
27
28
  import ConceptCrumbs from "./ConceptCrumbs";
28
29
  import ConceptEdit from "./ConceptEdit";
@@ -47,7 +48,12 @@ const RelationsGraphLoader = React.lazy(() =>
47
48
  import("@truedat/lm/components/RelationsGraphLoader")
48
49
  );
49
50
 
50
- const ConceptRoutes = ({ concept, conceptLoaded, templatesLoaded }) => {
51
+ const ConceptRoutes = ({
52
+ concept,
53
+ conceptLoaded,
54
+ templatesLoaded,
55
+ sidemenuGlossarySubscopes,
56
+ }) => {
51
57
  const { formatMessage } = useIntl();
52
58
  const authorized = useAuthorized([
53
59
  "business_glossary_view",
@@ -57,14 +63,6 @@ const ConceptRoutes = ({ concept, conceptLoaded, templatesLoaded }) => {
57
63
  return <Unauthorized />;
58
64
  }
59
65
 
60
- const pendingConceptsDefaultFilters = {
61
- status: ["pending_approval", "draft", "rejected"],
62
- };
63
-
64
- const archivedConceptsDefaultFilters = {
65
- status: ["deprecated"],
66
- };
67
-
68
66
  const renderConceptsComponent = (props, filters, key) => {
69
67
  const subscope = _.get("match.params.subscope", props) || "";
70
68
  const baseId = `concepts.${subscope || "default"}.${key}`;
@@ -150,7 +148,10 @@ const ConceptRoutes = ({ concept, conceptLoaded, templatesLoaded }) => {
150
148
  authorized ? (
151
149
  renderConceptsComponent(
152
150
  routeProps,
153
- pendingConceptsDefaultFilters,
151
+ {
152
+ status: ["pending_approval", "draft", "rejected"],
153
+ "mustnot.template.subscope": sidemenuGlossarySubscopes,
154
+ },
154
155
  "pending"
155
156
  )
156
157
  ) : (
@@ -165,7 +166,10 @@ const ConceptRoutes = ({ concept, conceptLoaded, templatesLoaded }) => {
165
166
  render={(routeProps) =>
166
167
  renderConceptsComponent(
167
168
  routeProps,
168
- archivedConceptsDefaultFilters,
169
+ {
170
+ status: ["deprecated"],
171
+ "mustnot.template.subscope": sidemenuGlossarySubscopes,
172
+ },
169
173
  "deprecated"
170
174
  )
171
175
  }
@@ -291,12 +295,14 @@ ConceptRoutes.propTypes = {
291
295
  concept: PropTypes.object,
292
296
  conceptLoaded: PropTypes.bool,
293
297
  templatesLoaded: PropTypes.bool,
298
+ sidemenuGlossarySubscopes: PropTypes.array,
294
299
  };
295
300
 
296
301
  const mapStateToProps = (state) => ({
297
302
  concept: state.concept,
298
303
  conceptLoaded: !_.isEmpty(state.concept),
299
304
  templatesLoaded: !state.templatesLoading && !_.isEmpty(state.templates),
305
+ sidemenuGlossarySubscopes: getSidemenuGlossarySubscopes(state),
300
306
  });
301
307
 
302
308
  export default connect(mapStateToProps)(ConceptRoutes);
@@ -1,3 +1,4 @@
1
+ import _ from "lodash/fp";
1
2
  import React from "react";
2
3
  import PropTypes from "prop-types";
3
4
  import { connect } from "react-redux";
@@ -17,7 +18,7 @@ import ConceptsLabelResults from "./ConceptsLabelResults";
17
18
  import ConceptsPagination from "./ConceptsPagination";
18
19
  import ConceptsTable from "./ConceptsTable";
19
20
 
20
- export function ConceptsPanelContent({ actions }) {
21
+ export function ConceptsPanelContent({ actions, subscope }) {
21
22
  const { loading } = useSearchContext();
22
23
 
23
24
  return (
@@ -29,7 +30,7 @@ export function ConceptsPanelContent({ actions }) {
29
30
  <Loader />
30
31
  </Dimmer>
31
32
  <ConceptsLabelResults />
32
- <ConceptsTable />
33
+ <ConceptsTable subscope={subscope} />
33
34
  <ConceptsPagination />
34
35
  </Dimmer.Dimmable>
35
36
  </>
@@ -38,6 +39,7 @@ export function ConceptsPanelContent({ actions }) {
38
39
 
39
40
  ConceptsPanelContent.propTypes = {
40
41
  actions: PropTypes.object,
42
+ subscope: PropTypes.string,
41
43
  };
42
44
 
43
45
  export const ConceptsPanel = ({ defaultFilters, actions, filtersGroup }) => {
@@ -53,9 +55,11 @@ export const ConceptsPanel = ({ defaultFilters, actions, filtersGroup }) => {
53
55
  filtersGroup,
54
56
  };
55
57
 
58
+ const subscope = _.flow(_.get("template.subscope"), _.head)(defaultFilters);
59
+
56
60
  return (
57
61
  <SearchContextProvider {...searchProps} defaultFilters={defaultFilters}>
58
- <ConceptsPanelContent actions={actions} />
62
+ <ConceptsPanelContent actions={actions} subscope={subscope} />
59
63
  </SearchContextProvider>
60
64
  );
61
65
  };
@@ -63,6 +67,7 @@ export const ConceptsPanel = ({ defaultFilters, actions, filtersGroup }) => {
63
67
  ConceptsPanel.propTypes = {
64
68
  actions: PropTypes.object,
65
69
  defaultFilters: PropTypes.object,
70
+ filtersGroup: PropTypes.object,
66
71
  };
67
72
 
68
73
  const mapStateToProps = (state) => ({
@@ -10,7 +10,7 @@ import { useSearchContext } from "@truedat/core/search/SearchContext";
10
10
  import { getConceptColumns } from "../selectors";
11
11
  import ConceptRow from "./ConceptRow";
12
12
 
13
- export const ConceptsTable = ({ columnsByScope }) => {
13
+ export const ConceptsTable = ({ columnsByScope, subscope }) => {
14
14
  const {
15
15
  searchData,
16
16
  loading,
@@ -23,10 +23,14 @@ export const ConceptsTable = ({ columnsByScope }) => {
23
23
  const { pathname } = useLocation();
24
24
 
25
25
  const scope = _.last(pathname.split("/"));
26
- const columns = columnsByScope[scope] || columnsByScope.concepts;
26
+ const columns =
27
+ _.path([subscope, scope])(columnsByScope) ||
28
+ _.path([scope])(columnsByScope) ||
29
+ _.path(["concepts"])(columnsByScope);
27
30
 
28
31
  const conceptColumns = columns.filter(
29
- (column) => pathname === CONCEPTS_PENDING || column.name != "status"
32
+ (column) =>
33
+ _.last(CONCEPTS_PENDING.split("/")) === scope || column.name != "status"
30
34
  );
31
35
 
32
36
  return (
@@ -79,10 +83,13 @@ export const ConceptsTable = ({ columnsByScope }) => {
79
83
 
80
84
  ConceptsTable.propTypes = {
81
85
  columnsByScope: PropTypes.object,
86
+ subscope: PropTypes.string,
82
87
  };
83
88
 
84
- const mapStateToProps = (state) => ({
85
- columnsByScope: getConceptColumns(state),
86
- });
89
+ const mapStateToProps = (state) => {
90
+ return {
91
+ columnsByScope: getConceptColumns(state),
92
+ };
93
+ };
87
94
 
88
95
  export default connect(mapStateToProps)(ConceptsTable);
@@ -5,7 +5,7 @@ import { ConceptsTable } from "../ConceptsTable";
5
5
 
6
6
  jest.mock("react-router-dom", () => ({
7
7
  ...jest.requireActual("react-router-dom"),
8
- useLocation: () => ({ pathname: "/concept/test" }),
8
+ useLocation: jest.fn(),
9
9
  }));
10
10
 
11
11
  const data = {
@@ -38,8 +38,6 @@ const useSearch = () => ({
38
38
  }),
39
39
  });
40
40
 
41
- const defaultFilters = {};
42
-
43
41
  const searchProps = {
44
42
  initialSortColumn: "name.raw",
45
43
  initialSortDirection: "ascending",
@@ -60,21 +58,309 @@ const renderOpts = {
60
58
  };
61
59
 
62
60
  describe("<ConceptsTable />", () => {
63
- it("matches the latest snapshot", async () => {
64
- const defaultColumns = ["name", "status"].map((name) => ({ name }));
65
- const columnsByScope = {
66
- concepts: [...defaultColumns],
67
- test: [...defaultColumns, { name: "type" }],
68
- };
61
+ const controlColumn = { name: "controlColumn" };
62
+ const defaultColumns = ["name", "status", "globalPublished"].map((name) => ({
63
+ name,
64
+ }));
65
+ const columnsByScope = {
66
+ concepts: [controlColumn, ...defaultColumns],
67
+ pending: [controlColumn, { name: "globalPending" }],
68
+ deprecated: [controlColumn, { name: "globalDeprecated" }],
69
+ subscoped: [controlColumn, { name: "subscoped" }],
70
+ menusubscoped: {
71
+ published: [controlColumn, { name: "menuscopedPublished" }],
72
+ pending: [controlColumn, { name: "menuscopedPending" }],
73
+ deprecated: [controlColumn, { name: "menuscopedDeprecated" }],
74
+ },
75
+ };
76
+
77
+ it(`matches the latest snapshot`, async () => {
78
+ jest
79
+ .spyOn(require("react-router-dom"), "useLocation")
80
+ .mockReturnValue({ pathname: "/concepts" });
69
81
 
70
82
  const props = { columnsByScope };
71
83
  const { container, findByText } = render(
72
- <SearchContextProvider {...searchProps} defaultFilters={defaultFilters}>
84
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
73
85
  <ConceptsTable {...props} />
74
86
  </SearchContextProvider>,
75
87
  renderOpts
76
88
  );
77
- await findByText(/Type/);
89
+ await findByText(/concepts.props.controlColumn/);
78
90
  expect(container).toMatchSnapshot();
79
91
  });
92
+
93
+ it(`default columns`, async () => {
94
+ jest
95
+ .spyOn(require("react-router-dom"), "useLocation")
96
+ .mockReturnValue({ pathname: "/concepts" });
97
+
98
+ const props = { columnsByScope };
99
+ const { findByText, queryByText } = render(
100
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
101
+ <ConceptsTable {...props} />
102
+ </SearchContextProvider>,
103
+ renderOpts
104
+ );
105
+ await findByText(/concepts.props.controlColumn/);
106
+ expect(queryByText(/globalPublished/)).toBeInTheDocument();
107
+ });
108
+
109
+ it(`subscope columns`, async () => {
110
+ jest
111
+ .spyOn(require("react-router-dom"), "useLocation")
112
+ .mockReturnValue({ pathname: "/concepts/subscoped" });
113
+
114
+ const props = { columnsByScope };
115
+ const { findByText, queryByText } = render(
116
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
117
+ <ConceptsTable {...props} />
118
+ </SearchContextProvider>,
119
+ renderOpts
120
+ );
121
+ await findByText(/concepts.props.controlColumn/);
122
+ expect(queryByText(/subscoped/)).toBeInTheDocument();
123
+ });
124
+
125
+ it(`subscope default fallback columns`, async () => {
126
+ jest
127
+ .spyOn(require("react-router-dom"), "useLocation")
128
+ .mockReturnValue({ pathname: "/concepts/subscoped" });
129
+
130
+ const fallbackColumns = {
131
+ concepts: [controlColumn, ...defaultColumns],
132
+ };
133
+
134
+ const props = { columnsByScope: fallbackColumns };
135
+ const { findByText, queryByText } = render(
136
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
137
+ <ConceptsTable {...props} />
138
+ </SearchContextProvider>,
139
+ renderOpts
140
+ );
141
+ await findByText(/concepts.props.controlColumn/);
142
+ expect(queryByText(/globalPublished/)).toBeInTheDocument();
143
+ });
144
+
145
+ it(`pending columns`, async () => {
146
+ jest
147
+ .spyOn(require("react-router-dom"), "useLocation")
148
+ .mockReturnValue({ pathname: "/concepts/pending" });
149
+
150
+ const props = { columnsByScope };
151
+ const { findByText, queryByText } = render(
152
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
153
+ <ConceptsTable {...props} />
154
+ </SearchContextProvider>,
155
+ renderOpts
156
+ );
157
+ await findByText(/concepts.props.controlColumn/);
158
+ expect(queryByText(/globalPending/)).toBeInTheDocument();
159
+ });
160
+
161
+ it(`pending default fallback columns`, async () => {
162
+ jest
163
+ .spyOn(require("react-router-dom"), "useLocation")
164
+ .mockReturnValue({ pathname: "/concepts/pending" });
165
+
166
+ const fallbackColumns = {
167
+ concepts: [controlColumn, ...defaultColumns],
168
+ };
169
+
170
+ const props = { columnsByScope: fallbackColumns };
171
+ const { findByText, queryByText } = render(
172
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
173
+ <ConceptsTable {...props} />
174
+ </SearchContextProvider>,
175
+ renderOpts
176
+ );
177
+ await findByText(/concepts.props.controlColumn/);
178
+ expect(queryByText(/globalPublished/)).toBeInTheDocument();
179
+ });
180
+
181
+ it(`deprecated columns`, async () => {
182
+ jest
183
+ .spyOn(require("react-router-dom"), "useLocation")
184
+ .mockReturnValue({ pathname: "/concepts/deprecated" });
185
+
186
+ const props = { columnsByScope };
187
+ const { findByText, queryByText } = render(
188
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
189
+ <ConceptsTable {...props} />
190
+ </SearchContextProvider>,
191
+ renderOpts
192
+ );
193
+ await findByText(/concepts.props.controlColumn/);
194
+ expect(queryByText(/globalDeprecated/)).toBeInTheDocument();
195
+ });
196
+
197
+ it(`deprecated default fallback columns`, async () => {
198
+ jest
199
+ .spyOn(require("react-router-dom"), "useLocation")
200
+ .mockReturnValue({ pathname: "/concepts/deprecated" });
201
+
202
+ const fallbackColumns = {
203
+ concepts: [controlColumn, ...defaultColumns],
204
+ };
205
+
206
+ const props = { columnsByScope: fallbackColumns };
207
+ const { findByText, queryByText } = render(
208
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
209
+ <ConceptsTable {...props} />
210
+ </SearchContextProvider>,
211
+ renderOpts
212
+ );
213
+ await findByText(/concepts.props.controlColumn/);
214
+ expect(queryByText(/globalPublished/)).toBeInTheDocument();
215
+ });
216
+
217
+ it(`menu-subscoped published columns`, async () => {
218
+ jest.spyOn(require("react-router-dom"), "useLocation").mockReturnValue({
219
+ pathname: "/glossarySubscope/menusubscoped/published",
220
+ });
221
+
222
+ const props = { columnsByScope };
223
+ const { findByText, queryByText } = render(
224
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
225
+ <ConceptsTable {...props} subscope="menusubscoped" />
226
+ </SearchContextProvider>,
227
+ renderOpts
228
+ );
229
+ await findByText(/concepts.props.controlColumn/);
230
+ expect(queryByText(/menuscopedPublished/)).toBeInTheDocument();
231
+ });
232
+
233
+ it(`menu-subscoped default published fallback columns`, async () => {
234
+ jest.spyOn(require("react-router-dom"), "useLocation").mockReturnValue({
235
+ pathname: "/glossarySubscope/menusubscoped/published",
236
+ });
237
+
238
+ const fallbackColumns = {
239
+ concepts: [controlColumn, ...defaultColumns],
240
+ };
241
+
242
+ const props = { columnsByScope: fallbackColumns };
243
+ const { findByText, queryByText } = render(
244
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
245
+ <ConceptsTable {...props} subscope="menusubscoped" />
246
+ </SearchContextProvider>,
247
+ renderOpts
248
+ );
249
+ await findByText(/concepts.props.controlColumn/);
250
+ expect(queryByText(/globalPublished/)).toBeInTheDocument();
251
+ });
252
+
253
+ it(`menu-subscoped pending columns`, async () => {
254
+ jest.spyOn(require("react-router-dom"), "useLocation").mockReturnValue({
255
+ pathname: "/glossarySubscope/menusubscoped/pending",
256
+ });
257
+
258
+ const props = { columnsByScope };
259
+ const { findByText, queryByText } = render(
260
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
261
+ <ConceptsTable {...props} subscope="menusubscoped" />
262
+ </SearchContextProvider>,
263
+ renderOpts
264
+ );
265
+ await findByText(/concepts.props.controlColumn/);
266
+ expect(queryByText(/menuscopedPending/)).toBeInTheDocument();
267
+ });
268
+
269
+ it(`menu-subscoped pending fallback columns`, async () => {
270
+ jest.spyOn(require("react-router-dom"), "useLocation").mockReturnValue({
271
+ pathname: "/glossarySubscope/menusubscoped/pending",
272
+ });
273
+
274
+ const fallbackColumns = {
275
+ concepts: [controlColumn, ...defaultColumns],
276
+ pending: [controlColumn, { name: "globalPending" }],
277
+ };
278
+
279
+ const props = { columnsByScope: fallbackColumns };
280
+ const { findByText, queryByText } = render(
281
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
282
+ <ConceptsTable {...props} subscope="menusubscoped" />
283
+ </SearchContextProvider>,
284
+ renderOpts
285
+ );
286
+ await findByText(/concepts.props.controlColumn/);
287
+ expect(queryByText(/globalPending/)).toBeInTheDocument();
288
+ });
289
+
290
+ it(`menu-subscoped default pending fallback columns`, async () => {
291
+ jest.spyOn(require("react-router-dom"), "useLocation").mockReturnValue({
292
+ pathname: "/glossarySubscope/menusubscoped/deprecated",
293
+ });
294
+
295
+ const fallbackColumns = {
296
+ concepts: [controlColumn, ...defaultColumns],
297
+ };
298
+
299
+ const props = { columnsByScope: fallbackColumns };
300
+ const { findByText, queryByText } = render(
301
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
302
+ <ConceptsTable {...props} subscope="menusubscoped" />
303
+ </SearchContextProvider>,
304
+ renderOpts
305
+ );
306
+ await findByText(/concepts.props.controlColumn/);
307
+ expect(queryByText(/globalPublished/)).toBeInTheDocument();
308
+ });
309
+
310
+ it(`menu-subscoped deprecated columns`, async () => {
311
+ jest.spyOn(require("react-router-dom"), "useLocation").mockReturnValue({
312
+ pathname: "/glossarySubscope/menusubscoped/deprecated",
313
+ });
314
+
315
+ const props = { columnsByScope };
316
+ const { findByText, queryByText } = render(
317
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
318
+ <ConceptsTable {...props} subscope="menusubscoped" />
319
+ </SearchContextProvider>,
320
+ renderOpts
321
+ );
322
+ await findByText(/concepts.props.controlColumn/);
323
+ expect(queryByText(/menuscopedDeprecated/)).toBeInTheDocument();
324
+ });
325
+
326
+ it(`menu-subscoped deprecated fallback columns`, async () => {
327
+ jest.spyOn(require("react-router-dom"), "useLocation").mockReturnValue({
328
+ pathname: "/glossarySubscope/menusubscoped/deprecated",
329
+ });
330
+
331
+ const fallbackColumns = {
332
+ concepts: [controlColumn, ...defaultColumns],
333
+ deprecated: [controlColumn, { name: "globalDeprecated" }],
334
+ };
335
+
336
+ const props = { columnsByScope: fallbackColumns };
337
+ const { findByText, queryByText } = render(
338
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
339
+ <ConceptsTable {...props} subscope="menusubscoped" />
340
+ </SearchContextProvider>,
341
+ renderOpts
342
+ );
343
+ await findByText(/concepts.props.controlColumn/);
344
+ expect(queryByText(/globalDeprecated/)).toBeInTheDocument();
345
+ });
346
+
347
+ it(`menu-subscoped default deprecated fallback columns`, async () => {
348
+ jest.spyOn(require("react-router-dom"), "useLocation").mockReturnValue({
349
+ pathname: "/glossarySubscope/menusubscoped/deprecated",
350
+ });
351
+
352
+ const fallbackColumns = {
353
+ concepts: [controlColumn, ...defaultColumns],
354
+ };
355
+
356
+ const props = { columnsByScope: fallbackColumns };
357
+ const { findByText, queryByText } = render(
358
+ <SearchContextProvider {...searchProps} defaultFilters={{}}>
359
+ <ConceptsTable {...props} subscope="menusubscoped" />
360
+ </SearchContextProvider>,
361
+ renderOpts
362
+ );
363
+ await findByText(/concepts.props.controlColumn/);
364
+ expect(queryByText(/globalPublished/)).toBeInTheDocument();
365
+ });
80
366
  });
@@ -11,6 +11,11 @@ exports[`<ConceptsTable /> matches the latest snapshot 1`] = `
11
11
  <tr
12
12
  class=""
13
13
  >
14
+ <th
15
+ class="disabled"
16
+ >
17
+ concepts.props.controlColumn
18
+ </th>
14
19
  <th
15
20
  class="disabled"
16
21
  >
@@ -19,7 +24,7 @@ exports[`<ConceptsTable /> matches the latest snapshot 1`] = `
19
24
  <th
20
25
  class="disabled"
21
26
  >
22
- Type
27
+ concepts.props.globalPublished
23
28
  </th>
24
29
  </tr>
25
30
  </thead>
@@ -29,6 +34,9 @@ exports[`<ConceptsTable /> matches the latest snapshot 1`] = `
29
34
  <tr
30
35
  class=""
31
36
  >
37
+ <td
38
+ class=""
39
+ />
32
40
  <td
33
41
  class=""
34
42
  >