1 | | - | import React, { Component } from 'react'; |
2 | | - | import * as PropTypes from 'prop-types'; |
3 | | - | import { graphql, createFragmentContainer } from 'react-relay'; |
4 | | - | import { Formik, Form, Field } from 'formik'; |
5 | | - | import withStyles from '@mui/styles/withStyles'; |
6 | | - | import * as Yup from 'yup'; |
7 | | - | import * as R from 'ramda'; |
8 | | - | import inject18n from '../../../../components/i18n'; |
9 | | - | import TextField from '../../../../components/TextField'; |
10 | | - | import { SubscriptionFocus } from '../../../../components/Subscription'; |
11 | | - | import { commitMutation } from '../../../../relay/environment'; |
12 | | - | import CreatedByField from '../../common/form/CreatedByField'; |
13 | | - | import ObjectMarkingField from '../../common/form/ObjectMarkingField'; |
14 | | - | import MarkDownField from '../../../../components/MarkDownField'; |
15 | | - | import CommitMessage from '../../common/form/CommitMessage'; |
16 | | - | import { adaptFieldValue } from '../../../../utils/String'; |
17 | | - | import StatusField from '../../common/form/StatusField'; |
18 | | - | import { |
19 | | - | convertCreatedBy, |
20 | | - | convertMarkings, |
21 | | - | convertStatus, |
22 | | - | } from '../../../../utils/Edition'; |
23 | | - | |
24 | | - | const styles = (theme) => ({ |
25 | | - | drawerPaper: { |
26 | | - | minHeight: '100vh', |
27 | | - | width: '50%', |
28 | | - | position: 'fixed', |
29 | | - | overflow: 'hidden', |
30 | | - | |
31 | | - | transition: theme.transitions.create('width', { |
32 | | - | easing: theme.transitions.easing.sharp, |
33 | | - | duration: theme.transitions.duration.enteringScreen, |
34 | | - | }), |
35 | | - | padding: '30px 30px 30px 30px', |
36 | | - | }, |
37 | | - | createButton: { |
38 | | - | position: 'fixed', |
39 | | - | bottom: 30, |
40 | | - | right: 30, |
41 | | - | }, |
42 | | - | importButton: { |
43 | | - | position: 'absolute', |
44 | | - | top: 30, |
45 | | - | right: 30, |
46 | | - | }, |
47 | | - | }); |
48 | | - | |
49 | | - | const cityMutationFieldPatch = graphql` |
50 | | - | mutation CityEditionOverviewFieldPatchMutation( |
51 | | - | $id: ID! |
52 | | - | $input: [EditInput]! |
53 | | - | $commitMessage: String |
54 | | - | $references: [String] |
55 | | - | ) { |
56 | | - | cityEdit(id: $id) { |
57 | | - | fieldPatch( |
58 | | - | input: $input |
59 | | - | commitMessage: $commitMessage |
60 | | - | references: $references |
61 | | - | ) { |
62 | | - | ...CityEditionOverview_city |
63 | | - | ...City_city |
64 | | - | } |
65 | | - | } |
66 | | - | } |
67 | | - | `; |
68 | | - | |
69 | | - | export const cityEditionOverviewFocus = graphql` |
70 | | - | mutation CityEditionOverviewFocusMutation($id: ID!, $input: EditContext!) { |
71 | | - | cityEdit(id: $id) { |
72 | | - | contextPatch(input: $input) { |
73 | | - | id |
74 | | - | } |
75 | | - | } |
76 | | - | } |
77 | | - | `; |
78 | | - | |
79 | | - | const cityMutationRelationAdd = graphql` |
80 | | - | mutation CityEditionOverviewRelationAddMutation( |
81 | | - | $id: ID! |
82 | | - | $input: StixMetaRelationshipAddInput! |
83 | | - | ) { |
84 | | - | cityEdit(id: $id) { |
85 | | - | relationAdd(input: $input) { |
86 | | - | from { |
87 | | - | ...CityEditionOverview_city |
88 | | - | } |
89 | | - | } |
90 | | - | } |
91 | | - | } |
92 | | - | `; |
93 | | - | |
94 | | - | const cityMutationRelationDelete = graphql` |
95 | | - | mutation CityEditionOverviewRelationDeleteMutation( |
96 | | - | $id: ID! |
97 | | - | $toId: StixRef! |
98 | | - | $relationship_type: String! |
99 | | - | ) { |
100 | | - | cityEdit(id: $id) { |
101 | | - | relationDelete(toId: $toId, relationship_type: $relationship_type) { |
102 | | - | ...CityEditionOverview_city |
103 | | - | } |
104 | | - | } |
105 | | - | } |
106 | | - | `; |
107 | | - | |
108 | | - | const cityValidation = (t) => Yup.object().shape({ |
109 | | - | name: Yup.string().required(t('This field is required')), |
110 | | - | description: Yup.string() |
111 | | - | .min(3, t('The value is too short')) |
112 | | - | .max(5000, t('The value is too long')) |
113 | | - | .required(t('This field is required')), |
114 | | - | latitude: Yup.number().typeError(t('This field must be a number')), |
115 | | - | longitude: Yup.number().typeError(t('This field must be a number')), |
116 | | - | x_opencti_workflow_id: Yup.object(), |
117 | | - | }); |
118 | | - | |
119 | | - | class CityEditionOverviewComponent extends Component { |
120 | | - | handleChangeFocus(name) { |
121 | | - | commitMutation({ |
122 | | - | mutation: cityEditionOverviewFocus, |
123 | | - | variables: { |
124 | | - | id: this.props.city.id, |
125 | | - | input: { |
126 | | - | focusOn: name, |
127 | | - | }, |
128 | | - | }, |
129 | | - | }); |
130 | | - | } |
131 | | - | |
132 | | - | onSubmit(values, { setSubmitting }) { |
133 | | - | const commitMessage = values.message; |
134 | | - | const references = R.pluck('value', values.references || []); |
135 | | - | const inputValues = R.pipe( |
136 | | - | R.dissoc('message'), |
137 | | - | R.dissoc('references'), |
138 | | - | R.assoc('x_opencti_workflow_id', values.x_opencti_workflow_id?.value), |
139 | | - | R.assoc('createdBy', values.createdBy?.value), |
140 | | - | R.assoc('objectMarking', R.pluck('value', values.objectMarking)), |
141 | | - | R.toPairs, |
142 | | - | R.map((n) => ({ |
143 | | - | key: n[0], |
144 | | - | value: adaptFieldValue(n[1]), |
145 | | - | })), |
146 | | - | )(values); |
147 | | - | commitMutation({ |
148 | | - | mutation: cityMutationFieldPatch, |
149 | | - | variables: { |
150 | | - | id: this.props.city.id, |
151 | | - | input: inputValues, |
152 | | - | commitMessage: |
153 | | - | commitMessage && commitMessage.length > 0 ? commitMessage : null, |
154 | | - | references, |
155 | | - | }, |
156 | | - | setSubmitting, |
157 | | - | onCompleted: () => { |
158 | | - | setSubmitting(false); |
159 | | - | this.props.handleClose(); |
160 | | - | }, |
161 | | - | }); |
162 | | - | } |
163 | | - | |
164 | | - | handleSubmitField(name, value) { |
165 | | - | if (!this.props.enableReferences) { |
166 | | - | let finalValue = value; |
167 | | - | if (name === 'x_opencti_workflow_id') { |
168 | | - | finalValue = value.value; |
169 | | - | } |
170 | | - | cityValidation(this.props.t) |
171 | | - | .validateAt(name, { [name]: value }) |
172 | | - | .then(() => { |
173 | | - | commitMutation({ |
174 | | - | mutation: cityMutationFieldPatch, |
175 | | - | variables: { |
176 | | - | id: this.props.city.id, |
177 | | - | input: { key: name, value: finalValue ?? '' }, |
178 | | - | }, |
179 | | - | }); |
180 | | - | }) |
181 | | - | .catch(() => false); |
182 | | - | } |
183 | | - | } |
184 | | - | |
185 | | - | handleChangeCreatedBy(name, value) { |
186 | | - | if (!this.props.enableReferences) { |
187 | | - | commitMutation({ |
188 | | - | mutation: cityMutationRelationAdd, |
189 | | - | variables: { |
190 | | - | id: this.props.city.id, |
191 | | - | input: { key: 'createdBy', value: value.value || '' }, |
192 | | - | }, |
193 | | - | }); |
194 | | - | } |
195 | | - | } |
196 | | - | |
197 | | - | handleChangeObjectMarking(name, values) { |
198 | | - | if (!this.props.enableReferences) { |
199 | | - | const { city } = this.props; |
200 | | - | const currentMarkingDefinitions = R.pipe( |
201 | | - | R.pathOr([], ['objectMarking', 'edges']), |
202 | | - | R.map((n) => ({ |
203 | | - | label: n.node.definition, |
204 | | - | value: n.node.id, |
205 | | - | })), |
206 | | - | )(city); |
207 | | - | const added = R.difference(values, currentMarkingDefinitions); |
208 | | - | const removed = R.difference(currentMarkingDefinitions, values); |
209 | | - | if (added.length > 0) { |
210 | | - | commitMutation({ |
211 | | - | mutation: cityMutationRelationAdd, |
212 | | - | variables: { |
213 | | - | id: this.props.city.id, |
214 | | - | input: { |
215 | | - | toId: R.head(added).value, |
216 | | - | relationship_type: 'object-marking', |
217 | | - | }, |
218 | | - | }, |
219 | | - | }); |
220 | | - | } |
221 | | - | if (removed.length > 0) { |
222 | | - | commitMutation({ |
223 | | - | mutation: cityMutationRelationDelete, |
224 | | - | variables: { |
225 | | - | id: this.props.city.id, |
226 | | - | toId: R.head(removed).value, |
227 | | - | relationship_type: 'object-marking', |
228 | | - | }, |
229 | | - | }); |
230 | | - | } |
231 | | - | } |
232 | | - | } |
233 | | - | |
234 | | - | render() { |
235 | | - | const { t, city, context, enableReferences } = this.props; |
236 | | - | const createdBy = convertCreatedBy(city); |
237 | | - | const objectMarking = convertMarkings(city); |
238 | | - | const status = convertStatus(t, city); |
239 | | - | const initialValues = R.pipe( |
240 | | - | R.assoc('createdBy', createdBy), |
241 | | - | R.assoc('objectMarking', objectMarking), |
242 | | - | R.assoc('x_opencti_workflow_id', status), |
243 | | - | R.pick([ |
244 | | - | 'name', |
245 | | - | 'description', |
246 | | - | 'latitude', |
247 | | - | 'longitude', |
248 | | - | 'createdBy', |
249 | | - | 'objectMarking', |
250 | | - | 'x_opencti_workflow_id', |
251 | | - | ]), |
252 | | - | )(city); |
253 | | - | return ( |
254 | | - | <Formik |
255 | | - | enableReinitialize={true} |
256 | | - | initialValues={initialValues} |
257 | | - | validationSchema={cityValidation(t)} |
258 | | - | onSubmit={this.onSubmit.bind(this)} |
259 | | - | > |
260 | | - | {({ |
261 | | - | submitForm, |
262 | | - | isSubmitting, |
263 | | - | validateForm, |
264 | | - | setFieldValue, |
265 | | - | values, |
266 | | - | }) => ( |
267 | | - | <Form style={{ margin: '20px 0 20px 0' }}> |
268 | | - | <Field |
269 | | - | component={TextField} |
270 | | - | variant="standard" |
271 | | - | name="name" |
272 | | - | label={t('Name')} |
273 | | - | fullWidth={true} |
274 | | - | onFocus={this.handleChangeFocus.bind(this)} |
275 | | - | onSubmit={this.handleSubmitField.bind(this)} |
276 | | - | helperText={ |
277 | | - | <SubscriptionFocus context={context} fieldName="name" /> |
278 | | - | } |
279 | | - | /> |
280 | | - | <Field |
281 | | - | component={MarkDownField} |
282 | | - | name="description" |
283 | | - | label={t('Description')} |
284 | | - | fullWidth={true} |
285 | | - | multiline={true} |
286 | | - | rows="4" |
287 | | - | style={{ marginTop: 20 }} |
288 | | - | onFocus={this.handleChangeFocus.bind(this)} |
289 | | - | onSubmit={this.handleSubmitField.bind(this)} |
290 | | - | helperText={ |
291 | | - | <SubscriptionFocus context={context} fieldName="description" /> |
292 | | - | } |
293 | | - | /> |
294 | | - | <Field |
295 | | - | component={TextField} |
296 | | - | variant="standard" |
297 | | - | style={{ marginTop: 20 }} |
298 | | - | name="latitude" |
299 | | - | label={t('Latitude')} |
300 | | - | fullWidth={true} |
301 | | - | onFocus={this.handleChangeFocus.bind(this)} |
302 | | - | onSubmit={this.handleSubmitField.bind(this)} |
303 | | - | helperText={ |
304 | | - | <SubscriptionFocus context={context} fieldName="latitude" /> |
305 | | - | } |
306 | | - | /> |
307 | | - | <Field |
308 | | - | component={TextField} |
309 | | - | variant="standard" |
310 | | - | style={{ marginTop: 20 }} |
311 | | - | name="longitude" |
312 | | - | label={t('Longitude')} |
313 | | - | fullWidth={true} |
314 | | - | onFocus={this.handleChangeFocus.bind(this)} |
315 | | - | onSubmit={this.handleSubmitField.bind(this)} |
316 | | - | helperText={ |
317 | | - | <SubscriptionFocus context={context} fieldName="longitude" /> |
318 | | - | } |
319 | | - | /> |
320 | | - | {city.workflowEnabled && ( |
321 | | - | <StatusField |
322 | | - | name="x_opencti_workflow_id" |
323 | | - | type="City" |
324 | | - | onFocus={this.handleChangeFocus.bind(this)} |
325 | | - | onChange={this.handleSubmitField.bind(this)} |
326 | | - | setFieldValue={setFieldValue} |
327 | | - | style={{ marginTop: 20 }} |
328 | | - | helpertext={ |
329 | | - | <SubscriptionFocus |
330 | | - | context={context} |
331 | | - | fieldName="x_opencti_workflow_id" |
332 | | - | /> |
333 | | - | } |
334 | | - | /> |
335 | | - | )} |
336 | | - | <CreatedByField |
337 | | - | name="createdBy" |
338 | | - | style={{ marginTop: 20, width: '100%' }} |
339 | | - | setFieldValue={setFieldValue} |
340 | | - | helpertext={ |
341 | | - | <SubscriptionFocus context={context} fieldName="createdBy" /> |
342 | | - | } |
343 | | - | onChange={this.handleChangeCreatedBy.bind(this)} |
344 | | - | /> |
345 | | - | <ObjectMarkingField |
346 | | - | name="objectMarking" |
347 | | - | style={{ marginTop: 20, width: '100%' }} |
348 | | - | helpertext={ |
349 | | - | <SubscriptionFocus |
350 | | - | context={context} |
351 | | - | fieldname="objectMarking" |
352 | | - | /> |
353 | | - | } |
354 | | - | onChange={this.handleChangeObjectMarking.bind(this)} |
355 | | - | /> |
356 | | - | {enableReferences && ( |
357 | | - | <CommitMessage |
358 | | - | submitForm={submitForm} |
359 | | - | disabled={isSubmitting} |
360 | | - | validateForm={validateForm} |
361 | | - | setFieldValue={setFieldValue} |
362 | | - | values={values} |
363 | | - | id={city.id} |
364 | | - | /> |
365 | | - | )} |
366 | | - | </Form> |
367 | | - | )} |
368 | | - | </Formik> |
369 | | - | ); |
370 | | - | } |
371 | | - | } |
372 | | - | |
373 | | - | CityEditionOverviewComponent.propTypes = { |
374 | | - | classes: PropTypes.object, |
375 | | - | theme: PropTypes.object, |
376 | | - | t: PropTypes.func, |
377 | | - | city: PropTypes.object, |
378 | | - | context: PropTypes.array, |
379 | | - | }; |
380 | | - | |
381 | | - | const CityEditionOverview = createFragmentContainer( |
382 | | - | CityEditionOverviewComponent, |
383 | | - | { |
384 | | - | city: graphql` |
385 | | - | fragment CityEditionOverview_city on City { |
386 | | - | id |
387 | | - | name |
388 | | - | description |
389 | | - | latitude |
390 | | - | longitude |
391 | | - | createdBy { |
392 | | - | ... on Identity { |
393 | | - | id |
394 | | - | name |
395 | | - | entity_type |
396 | | - | } |
397 | | - | } |
398 | | - | objectMarking { |
399 | | - | edges { |
400 | | - | node { |
401 | | - | id |
402 | | - | definition |
403 | | - | definition_type |
404 | | - | } |
405 | | - | } |
406 | | - | } |
407 | | - | status { |
408 | | - | id |
409 | | - | order |
410 | | - | template { |
411 | | - | name |
412 | | - | color |
413 | | - | } |
414 | | - | } |
415 | | - | workflowEnabled |
416 | | - | } |
417 | | - | `, |
418 | | - | }, |
419 | | - | ); |
420 | | - | |
421 | | - | export default R.compose( |
422 | | - | inject18n, |
423 | | - | withStyles(styles, { withTheme: true }), |
424 | | - | )(CityEditionOverview); |
425 | | - | |