@truedat/dd 5.18.3 → 5.19.0

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/dd",
3
- "version": "5.18.3",
3
+ "version": "5.19.0",
4
4
  "description": "Truedat Web Data Dictionary",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -88,9 +88,9 @@
88
88
  },
89
89
  "dependencies": {
90
90
  "@apollo/client": "^3.7.1",
91
- "@truedat/auth": "5.18.3",
92
- "@truedat/core": "5.18.3",
93
- "@truedat/df": "5.18.3",
91
+ "@truedat/auth": "5.19.0",
92
+ "@truedat/core": "5.19.0",
93
+ "@truedat/df": "5.19.0",
94
94
  "lodash": "^4.17.21",
95
95
  "moment": "^2.29.4",
96
96
  "path-to-regexp": "^1.7.0",
@@ -115,5 +115,5 @@
115
115
  "react-dom": ">= 16.8.6 < 17",
116
116
  "semantic-ui-react": ">= 2.0.3 < 2.2"
117
117
  },
118
- "gitHead": "ba00f358149f00dcdabb3e3edd883c54c0bbd43a"
118
+ "gitHead": "df3fcd6655456c3468259d79762800c3c417147d"
119
119
  }
@@ -67,6 +67,7 @@ export const StructureNoteActions = ({
67
67
 
68
68
  const parsedActions = _.flow(
69
69
  _.toPairs,
70
+ _.reject(([action, _data]) => !_.get(action)(actionToIcon)),
70
71
  _.map(([action, data]) => {
71
72
  const baseAction = {
72
73
  key: action,
@@ -0,0 +1,179 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import {
5
+ Button,
6
+ Checkbox,
7
+ Container,
8
+ Divider,
9
+ Icon,
10
+ List,
11
+ Message,
12
+ Segment,
13
+ } from "semantic-ui-react";
14
+ import { FormattedMessage, useIntl } from "react-intl";
15
+ import { apiJson } from "@truedat/core/services/api";
16
+
17
+ export default function StructureNoteSuggestions({
18
+ aiSuggestionsAction,
19
+ applySuggestions,
20
+ template,
21
+ isModification,
22
+ }) {
23
+ const [loadingSuggestion, setLoadingSuggestion] = useState();
24
+ const [suggestions, setSuggestions] = useState();
25
+ const [suggestionsError, setSuggestionsError] = useState();
26
+ const [selectedSuggestions, setSelectedSuggestions] = useState([]);
27
+
28
+ const { locale } = useIntl();
29
+
30
+ const suggestionKeys = _.keys(suggestions);
31
+ const fieldInSuggestions = ({ name }) => _.includes(name)(suggestionKeys);
32
+ const templateFields = _.flow(
33
+ _.prop("content"),
34
+ _.filter(({ fields }) => _.any(fieldInSuggestions)(fields)),
35
+ _.map(({ name, fields }) => ({
36
+ name,
37
+ fields: _.filter(fieldInSuggestions)(fields),
38
+ }))
39
+ )(template);
40
+ const validFields = _.flow(
41
+ _.prop("content"),
42
+ _.flatMap(({ fields }) =>
43
+ _.flow(
44
+ _.filter(({ editable }) => editable || !isModification),
45
+ _.map(({ name }) => name)
46
+ )(fields)
47
+ )
48
+ )(template);
49
+
50
+ const handleRequestSuggestions = () => {
51
+ setLoadingSuggestion(true);
52
+ setSuggestionsError(null);
53
+ setSelectedSuggestions([]);
54
+ setSuggestions(null);
55
+ const url = `${aiSuggestionsAction.href}?language=${locale}`;
56
+ apiJson(url).then(({ data: { data: suggestions } }) => {
57
+ if (_.prop("[0]")(suggestions) === "error") {
58
+ setSuggestionsError(
59
+ _.prop("[1].error.message")(suggestions) || _.prop("[1]")(suggestions)
60
+ );
61
+ } else {
62
+ setSuggestions(suggestions);
63
+ const selectedFields = _.flow(
64
+ _.keys,
65
+ _.filter(
66
+ (key) =>
67
+ _.includes(key)(validFields) && !_.isEmpty(suggestions[key])
68
+ )
69
+ )(suggestions);
70
+ setSelectedSuggestions(selectedFields);
71
+ }
72
+ setLoadingSuggestion(false);
73
+ });
74
+ };
75
+ const handleApplySuggestions = () => {
76
+ applySuggestions(_.pick(selectedSuggestions)(suggestions));
77
+ setSelectedSuggestions([]);
78
+ setSuggestions(null);
79
+ };
80
+
81
+ const toggleSelectedSuggestion = (name) => {
82
+ setSelectedSuggestions(
83
+ _.includes(name)(selectedSuggestions)
84
+ ? _.reject((v) => v == name)(selectedSuggestions)
85
+ : [...selectedSuggestions, name]
86
+ );
87
+ };
88
+
89
+ return (
90
+ <Segment loading={loadingSuggestion}>
91
+ <Container style={{ display: "flex", justifyContent: "space-between" }}>
92
+ <h4>
93
+ {suggestions ? (
94
+ <Icon name="lightbulb outline" />
95
+ ) : (
96
+ <Icon name="lightbulb" />
97
+ )}
98
+ <FormattedMessage id="structure_note.ai_suggestion.header" />
99
+ </h4>
100
+ <Divider hidden />
101
+ {suggestions ? null : (
102
+ <Button onClick={handleRequestSuggestions}>
103
+ <FormattedMessage id="actions.ai_suggestion" />
104
+ </Button>
105
+ )}
106
+ </Container>
107
+ {suggestionsError ? (
108
+ <Message negative>
109
+ <Message.Header>
110
+ <FormattedMessage id="structure_note.ai_suggestion.error" />
111
+ </Message.Header>
112
+ <p>{suggestionsError}</p>
113
+ </Message>
114
+ ) : null}
115
+ {suggestions ? (
116
+ <>
117
+ {_.map(({ name: groupName, fields }) => (
118
+ <Container key={`group-${groupName}`}>
119
+ <>
120
+ <h5>{groupName ? groupName : ""}</h5>
121
+ <List>
122
+ {_.map(({ name, label, editable }) => (
123
+ <List.Item
124
+ key={name}
125
+ style={{
126
+ display: "flex",
127
+ alignItems: "center",
128
+ gap: "12px",
129
+ }}
130
+ >
131
+ <Checkbox
132
+ id={name}
133
+ disabled={
134
+ (isModification && !editable) ||
135
+ _.isEmpty(suggestions[name])
136
+ }
137
+ checked={_.includes(name)(selectedSuggestions)}
138
+ onChange={() => toggleSelectedSuggestion(name)}
139
+ />
140
+ <label
141
+ htmlFor={name}
142
+ style={{ cursor: "pointer" }}
143
+ disabled
144
+ >
145
+ <List.Header>{label}</List.Header>
146
+ <List.Description>
147
+ {_.isEmpty(suggestions[name])
148
+ ? "-"
149
+ : suggestions[name]}
150
+ </List.Description>
151
+ </label>
152
+ </List.Item>
153
+ ))(fields)}
154
+ </List>
155
+ <Divider hidden />
156
+ </>
157
+ </Container>
158
+ ))(templateFields)}
159
+ <Container textAlign="right">
160
+ <Button
161
+ primary
162
+ onClick={handleApplySuggestions}
163
+ disabled={_.isEmpty(selectedSuggestions)}
164
+ >
165
+ <FormattedMessage id="actions.apply_ai_suggestion" />
166
+ </Button>
167
+ </Container>
168
+ </>
169
+ ) : null}
170
+ </Segment>
171
+ );
172
+ }
173
+
174
+ StructureNoteSuggestions.propTypes = {
175
+ aiSuggestionsAction: PropTypes.object,
176
+ applySuggestions: PropTypes.func,
177
+ template: PropTypes.object,
178
+ isModification: PropTypes.bool,
179
+ };
@@ -9,6 +9,7 @@ import { selectTemplate, selectDomains } from "@truedat/df/routines";
9
9
  import { applyTemplate, validateContent } from "@truedat/df/utils";
10
10
  import { doStructureNoteAction } from "../routines";
11
11
  import { getLatestDfContent } from "../selectors/getSortedStructureNotes";
12
+ import StructureNoteSuggestions from "./StructureNoteSuggestions";
12
13
 
13
14
  const DynamicForm = React.lazy(() =>
14
15
  import("@truedat/df/components/DynamicForm")
@@ -29,6 +30,9 @@ export const StructureNotesEdit = ({
29
30
  latestDfContent,
30
31
  }) => {
31
32
  const [content, setContent] = useState(latestDfContent);
33
+ const aiSuggestionsAction = _.prop("_actions.ai_suggestions")(structureNotes);
34
+
35
+ const isModification = !_.isEmpty(latestDfContent);
32
36
 
33
37
  useEffect(() => {
34
38
  selectDomains(structure.domain_ids);
@@ -41,7 +45,7 @@ export const StructureNotesEdit = ({
41
45
  }
42
46
  }, [applyTemplate, template, latestDfContent, structure.domain_id]);
43
47
 
44
- const { formatMessage } = useIntl();
48
+ const { formatMessage, locale } = useIntl();
45
49
 
46
50
  const submitDisabled =
47
51
  !template || _.size(validateContent(template)(content)) > 0;
@@ -67,14 +71,27 @@ export const StructureNotesEdit = ({
67
71
  doStructureNoteAction(payload);
68
72
  };
69
73
 
74
+ const applySuggestions = (suggestions) => {
75
+ setContent({ ...content, ...suggestions });
76
+ };
77
+
70
78
  return (
71
79
  <Segment attached="bottom">
72
80
  <TemplateLoader />
81
+
82
+ {aiSuggestionsAction ? (
83
+ <StructureNoteSuggestions
84
+ aiSuggestionsAction={aiSuggestionsAction}
85
+ template={template}
86
+ applySuggestions={applySuggestions}
87
+ isModification={isModification}
88
+ />
89
+ ) : null}
73
90
  <Form>
74
91
  <DynamicForm
75
92
  onChange={setContent}
76
93
  content={content}
77
- isModification={true}
94
+ isModification={isModification}
78
95
  />
79
96
  <Button
80
97
  primary
@@ -0,0 +1,151 @@
1
+ import React, { Suspense } from "react";
2
+ import { waitFor } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { render } from "@truedat/test/render";
5
+ import StructureNoteSuggestions from "../StructureNoteSuggestions";
6
+
7
+ const mockSuggestions = {
8
+ editable_field: "editable_field_value",
9
+ non_editable_field: "non_editable_field_value",
10
+ unselect_field: "unselect_field_value",
11
+ };
12
+ jest.mock("@truedat/core/services/api", () => ({
13
+ apiJson: (url) => ({
14
+ then: (callback) =>
15
+ callback({
16
+ data: {
17
+ data: url.startsWith("suggestions")
18
+ ? mockSuggestions
19
+ : url.startsWith("error_message")
20
+ ? ["error", { error: { message: "ERROR MESSAGE" } }]
21
+ : ["error", "ERROR TEXT"],
22
+ },
23
+ }),
24
+ }),
25
+ }));
26
+
27
+ const messages = {
28
+ en: {
29
+ "actions.cancel": "cancel",
30
+ "actions.save": "save",
31
+ "structure_note.ai_suggestion.header": "header",
32
+ "actions.apply_ai_suggestion": "actions.apply_ai_suggestion",
33
+ "actions.ai_suggestion": "actions.ai_suggestion",
34
+ "structure_note.ai_suggestion.error": "error",
35
+ },
36
+ };
37
+
38
+ describe("<StructureNoteSuggestions />", () => {
39
+ it("matches the latest snapshot", () => {
40
+ const props = {
41
+ template: {},
42
+ };
43
+ const { container } = render(
44
+ <Suspense fallback={null}>
45
+ <StructureNoteSuggestions {...props} />
46
+ </Suspense>,
47
+ { messages }
48
+ );
49
+ expect(container).toMatchSnapshot();
50
+ });
51
+
52
+ it("component lifecycle", async () => {
53
+ const applySuggestions = jest.fn();
54
+ const props = {
55
+ aiSuggestionsAction: { href: "suggestions" },
56
+ applySuggestions,
57
+ template: {
58
+ content: [
59
+ {
60
+ fields: [
61
+ {
62
+ name: "editable_field",
63
+ label: "EditableField",
64
+ editable: true,
65
+ },
66
+ ],
67
+ },
68
+ {
69
+ name: "Group2",
70
+ fields: [
71
+ {
72
+ name: "non_editable_field",
73
+ label: "NonEditableField",
74
+ editable: false,
75
+ },
76
+ {
77
+ name: "unselect_field",
78
+ label: "UnselectedField",
79
+ editable: true,
80
+ },
81
+ ],
82
+ },
83
+ ],
84
+ },
85
+ isModification: true,
86
+ };
87
+ const { container, findByText, queryByText } = render(
88
+ <Suspense fallback={null}>
89
+ <StructureNoteSuggestions {...props} />
90
+ </Suspense>,
91
+ { messages }
92
+ );
93
+
94
+ userEvent.click(await findByText(/actions.ai_suggestion/i));
95
+ await waitFor(() =>
96
+ expect(queryByText(/actions.ai_suggestion/i)).not.toBeInTheDocument()
97
+ );
98
+
99
+ expect(container).toMatchSnapshot();
100
+
101
+ userEvent.click(await findByText(/UnselectedField/i));
102
+ userEvent.click(await findByText(/UnselectedField/i));
103
+ userEvent.click(await findByText(/UnselectedField/i));
104
+
105
+ userEvent.click(await findByText(/actions.apply_ai_suggestion/i));
106
+ await waitFor(() =>
107
+ expect(
108
+ queryByText(/actions.apply_ai_suggestion/i)
109
+ ).not.toBeInTheDocument()
110
+ );
111
+ expect(applySuggestions).toHaveBeenCalledWith(
112
+ expect.objectContaining({ editable_field: "editable_field_value" })
113
+ );
114
+
115
+ expect(container).toMatchSnapshot();
116
+ });
117
+
118
+ it("renders error with message", async () => {
119
+ const props = {
120
+ aiSuggestionsAction: { href: "error_message" },
121
+ };
122
+ const { container, findByText, queryByText } = render(
123
+ <Suspense fallback={null}>
124
+ <StructureNoteSuggestions {...props} />
125
+ </Suspense>,
126
+ { messages }
127
+ );
128
+
129
+ userEvent.click(await findByText(/actions.ai_suggestion/i));
130
+ await waitFor(() =>
131
+ expect(queryByText(/error message/i)).toBeInTheDocument()
132
+ );
133
+ expect(container).toMatchSnapshot();
134
+ });
135
+
136
+ it("renders error with message", async () => {
137
+ const props = {
138
+ aiSuggestionsAction: { href: "error_text" },
139
+ };
140
+ const { container, findByText, queryByText } = render(
141
+ <Suspense fallback={null}>
142
+ <StructureNoteSuggestions {...props} />
143
+ </Suspense>,
144
+ { messages }
145
+ );
146
+
147
+ userEvent.click(await findByText(/actions.ai_suggestion/i));
148
+ await waitFor(() => expect(queryByText(/error text/i)).toBeInTheDocument());
149
+ expect(container).toMatchSnapshot();
150
+ });
151
+ });
@@ -28,6 +28,40 @@ describe("<StructureNotesEdit />", () => {
28
28
  expect(container).toMatchSnapshot();
29
29
  });
30
30
 
31
+ it("matches the latest snapshot with ai suggestion action", () => {
32
+ const props = {
33
+ structure: { id: 1 },
34
+ template: { id: 2 },
35
+ doStructureNoteAction: () => {},
36
+ selectTemplate: () => {},
37
+ selectDomain: () => {},
38
+ structureNotes: {
39
+ _actions: {
40
+ ai_suggestions: { href: "suggestion" },
41
+ },
42
+ structureNotes: [],
43
+ },
44
+ latestDfContent: { foo: "bar" },
45
+ applyTemplate: applyTemplate({ id: 2 }),
46
+ selectDomains: jest.fn(),
47
+ };
48
+ const { container } = render(
49
+ <Suspense fallback={null}>
50
+ <StructureNotesEdit {...props} />
51
+ </Suspense>,
52
+ {
53
+ messages: {
54
+ en: {
55
+ "actions.cancel": "cancel",
56
+ "actions.save": "save",
57
+ "structure_note.ai_suggestion.header": "header",
58
+ "actions.ai_suggestion": "ai_suggestion",
59
+ },
60
+ },
61
+ }
62
+ );
63
+ expect(container).toMatchSnapshot();
64
+ });
31
65
  it("calls doStructureNoteAction when submit is clicked", async () => {
32
66
  const doStructureNoteAction = jest.fn();
33
67
  const template = {
@@ -0,0 +1,316 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<StructureNoteSuggestions /> component lifecycle 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui segment"
7
+ >
8
+ <div
9
+ class="ui container"
10
+ style="display: flex; justify-content: space-between;"
11
+ >
12
+ <h4>
13
+ <i
14
+ aria-hidden="true"
15
+ class="lightbulb outline icon"
16
+ />
17
+ header
18
+ </h4>
19
+ <div
20
+ class="ui hidden divider"
21
+ />
22
+ </div>
23
+ <div
24
+ class="ui container"
25
+ >
26
+ <h5 />
27
+ <div
28
+ class="ui list"
29
+ role="list"
30
+ >
31
+ <div
32
+ class="item"
33
+ role="listitem"
34
+ style="display: flex; align-items: center; gap: 12px;"
35
+ >
36
+ <div
37
+ class="ui checked fitted checkbox"
38
+ >
39
+ <input
40
+ checked=""
41
+ class="hidden"
42
+ id="editable_field"
43
+ readonly=""
44
+ tabindex="0"
45
+ type="checkbox"
46
+ value=""
47
+ />
48
+ <label
49
+ for="editable_field"
50
+ />
51
+ </div>
52
+ <label
53
+ disabled=""
54
+ for="editable_field"
55
+ style="cursor: pointer;"
56
+ >
57
+ <div
58
+ class="header"
59
+ >
60
+ EditableField
61
+ </div>
62
+ <div
63
+ class="description"
64
+ >
65
+ editable_field_value
66
+ </div>
67
+ </label>
68
+ </div>
69
+ </div>
70
+ <div
71
+ class="ui hidden divider"
72
+ />
73
+ </div>
74
+ <div
75
+ class="ui container"
76
+ >
77
+ <h5>
78
+ Group2
79
+ </h5>
80
+ <div
81
+ class="ui list"
82
+ role="list"
83
+ >
84
+ <div
85
+ class="item"
86
+ role="listitem"
87
+ style="display: flex; align-items: center; gap: 12px;"
88
+ >
89
+ <div
90
+ class="ui disabled fitted checkbox"
91
+ >
92
+ <input
93
+ class="hidden"
94
+ disabled=""
95
+ id="non_editable_field"
96
+ readonly=""
97
+ tabindex="-1"
98
+ type="checkbox"
99
+ value=""
100
+ />
101
+ <label
102
+ for="non_editable_field"
103
+ />
104
+ </div>
105
+ <label
106
+ disabled=""
107
+ for="non_editable_field"
108
+ style="cursor: pointer;"
109
+ >
110
+ <div
111
+ class="header"
112
+ >
113
+ NonEditableField
114
+ </div>
115
+ <div
116
+ class="description"
117
+ >
118
+ non_editable_field_value
119
+ </div>
120
+ </label>
121
+ </div>
122
+ <div
123
+ class="item"
124
+ role="listitem"
125
+ style="display: flex; align-items: center; gap: 12px;"
126
+ >
127
+ <div
128
+ class="ui checked fitted checkbox"
129
+ >
130
+ <input
131
+ checked=""
132
+ class="hidden"
133
+ id="unselect_field"
134
+ readonly=""
135
+ tabindex="0"
136
+ type="checkbox"
137
+ value=""
138
+ />
139
+ <label
140
+ for="unselect_field"
141
+ />
142
+ </div>
143
+ <label
144
+ disabled=""
145
+ for="unselect_field"
146
+ style="cursor: pointer;"
147
+ >
148
+ <div
149
+ class="header"
150
+ >
151
+ UnselectedField
152
+ </div>
153
+ <div
154
+ class="description"
155
+ >
156
+ unselect_field_value
157
+ </div>
158
+ </label>
159
+ </div>
160
+ </div>
161
+ <div
162
+ class="ui hidden divider"
163
+ />
164
+ </div>
165
+ <div
166
+ class="ui right aligned container"
167
+ >
168
+ <button
169
+ class="ui primary button"
170
+ >
171
+ actions.apply_ai_suggestion
172
+ </button>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ `;
177
+
178
+ exports[`<StructureNoteSuggestions /> component lifecycle 2`] = `
179
+ <div>
180
+ <div
181
+ class="ui segment"
182
+ >
183
+ <div
184
+ class="ui container"
185
+ style="display: flex; justify-content: space-between;"
186
+ >
187
+ <h4>
188
+ <i
189
+ aria-hidden="true"
190
+ class="lightbulb icon"
191
+ />
192
+ header
193
+ </h4>
194
+ <div
195
+ class="ui hidden divider"
196
+ />
197
+ <button
198
+ class="ui button"
199
+ >
200
+ actions.ai_suggestion
201
+ </button>
202
+ </div>
203
+ </div>
204
+ </div>
205
+ `;
206
+
207
+ exports[`<StructureNoteSuggestions /> matches the latest snapshot 1`] = `
208
+ <div>
209
+ <div
210
+ class="ui segment"
211
+ >
212
+ <div
213
+ class="ui container"
214
+ style="display: flex; justify-content: space-between;"
215
+ >
216
+ <h4>
217
+ <i
218
+ aria-hidden="true"
219
+ class="lightbulb icon"
220
+ />
221
+ header
222
+ </h4>
223
+ <div
224
+ class="ui hidden divider"
225
+ />
226
+ <button
227
+ class="ui button"
228
+ >
229
+ actions.ai_suggestion
230
+ </button>
231
+ </div>
232
+ </div>
233
+ </div>
234
+ `;
235
+
236
+ exports[`<StructureNoteSuggestions /> renders error with message 1`] = `
237
+ <div>
238
+ <div
239
+ class="ui segment"
240
+ >
241
+ <div
242
+ class="ui container"
243
+ style="display: flex; justify-content: space-between;"
244
+ >
245
+ <h4>
246
+ <i
247
+ aria-hidden="true"
248
+ class="lightbulb icon"
249
+ />
250
+ header
251
+ </h4>
252
+ <div
253
+ class="ui hidden divider"
254
+ />
255
+ <button
256
+ class="ui button"
257
+ >
258
+ actions.ai_suggestion
259
+ </button>
260
+ </div>
261
+ <div
262
+ class="ui negative message"
263
+ >
264
+ <div
265
+ class="header"
266
+ >
267
+ error
268
+ </div>
269
+ <p>
270
+ ERROR MESSAGE
271
+ </p>
272
+ </div>
273
+ </div>
274
+ </div>
275
+ `;
276
+
277
+ exports[`<StructureNoteSuggestions /> renders error with message 2`] = `
278
+ <div>
279
+ <div
280
+ class="ui segment"
281
+ >
282
+ <div
283
+ class="ui container"
284
+ style="display: flex; justify-content: space-between;"
285
+ >
286
+ <h4>
287
+ <i
288
+ aria-hidden="true"
289
+ class="lightbulb icon"
290
+ />
291
+ header
292
+ </h4>
293
+ <div
294
+ class="ui hidden divider"
295
+ />
296
+ <button
297
+ class="ui button"
298
+ >
299
+ actions.ai_suggestion
300
+ </button>
301
+ </div>
302
+ <div
303
+ class="ui negative message"
304
+ >
305
+ <div
306
+ class="header"
307
+ >
308
+ error
309
+ </div>
310
+ <p>
311
+ ERROR TEXT
312
+ </p>
313
+ </div>
314
+ </div>
315
+ </div>
316
+ `;
@@ -25,3 +25,52 @@ exports[`<StructureNotesEdit /> matches the latest snapshot 1`] = `
25
25
  </div>
26
26
  </div>
27
27
  `;
28
+
29
+ exports[`<StructureNotesEdit /> matches the latest snapshot with ai suggestion action 1`] = `
30
+ <div>
31
+ <div
32
+ class="ui bottom attached segment"
33
+ >
34
+ <div
35
+ class="ui segment"
36
+ >
37
+ <div
38
+ class="ui container"
39
+ style="display: flex; justify-content: space-between;"
40
+ >
41
+ <h4>
42
+ <i
43
+ aria-hidden="true"
44
+ class="lightbulb icon"
45
+ />
46
+ header
47
+ </h4>
48
+ <div
49
+ class="ui hidden divider"
50
+ />
51
+ <button
52
+ class="ui button"
53
+ >
54
+ ai_suggestion
55
+ </button>
56
+ </div>
57
+ </div>
58
+ <form
59
+ class="ui form"
60
+ >
61
+ <button
62
+ class="ui primary button"
63
+ >
64
+ save
65
+ </button>
66
+ <a
67
+ class="ui secondary button"
68
+ href="/"
69
+ role="button"
70
+ >
71
+ cancel
72
+ </a>
73
+ </form>
74
+ </div>
75
+ </div>
76
+ `;