· 4 years ago · Aug 02, 2021, 03:10 PM
1const demoIssue = require('./demo-issue').demoIssue;
2const demoArticle = require('./demo-article').demoArticle;
3const demoUser = require('./demo-user').demoUser;
4const demoProject = require('./demo-project').demoProject;
5
6/**
7 * This is the demo client. Use it as an example for creating your own custom import client.
8 *
9 * When importing data, YouTrack follows the procedure below:
10 *
11 * 1. Establishes connection between YouTrack and the import source. In the demo client, this part is not implemented.
12 * The demo client uses a predefined hard-coded set of data.
13 * 2. Calls the `prepareToImport` function. In this function, the client can perform any necessary initialization.
14 * 3. Via the `getProjects` function, requests projects from the import source.
15 * 4. Searches for users and groups mentioned in each project (project leaders, teams, subsystem owners) and
16 * creates respective users and groups in YouTrack and Hub.
17 * 5. When creating a group, retrieves its members via the `getUsers` function and creates each user in YouTrack and Hub.
18 * 6. For each project, creates custom field prototypes in case the retrieved project contains descriptions of project fields.
19 * 7. For each project, requests issues in chunks via the `getIssues` function.
20 * 8. For each chunk of issues:
21 * * Checks that all required fields have values and correct types.
22 * * Checks that each field uses the same representation (strings or objects) and has the same type in each issue.
23 * * Searches for users and groups mentioned in each issue (authors, updaters, field values) and
24 * creates respective users and groups in YouTrack and Hub.
25 * * Retrieves members of each user group mentioned in each issue via `getUsers` function and creates each user in YouTrack and Hub.
26 * * Defines types for fields that use string representation. If the field already exists in YouTrack, its type is preserved.
27 * If the field does not exist in YouTrack yet, creates this field with the `string` type.
28 * * Creates values to be assigned to YouTrack issue fields.
29 * 9. For each issue from the import source, creates a YouTrack issue and assigns values to its fields.
30 *
31 */
32
33/**
34 * A demo class implementing an API client interface.
35 */
36class DemoClient {
37 /**
38 * Creating a DemoClient instance.
39 * @param {Object} context The object holding parameters entered via the UI.
40 */
41 constructor(context) {
42 const params = context.parameters;
43
44 /**
45 * The URL of the import source entered by the user.
46 */
47 this.url = params.loadValue('url');
48
49 /**
50 * The name of the SSL key if it is selected.
51 */
52 this.sslKeyName = params.loadValue('sslKeyName');
53
54 /**
55 * The authorization token or the password entered by the user.
56 */
57 this.token = params.loadValue('password');
58 }
59
60 /**
61 * A function that is invoked right before an import round is started.
62 * A client can perform any necessary initialization here.
63 */
64 prepareToImport() {}
65
66 /**
67 * Returns timestamp formats (for example, `yyyy-MM-dd'T'HH:mm:ss.SSSX`) that are used by the import source.
68 * The first of the returned values will be used to format the timestamp passed into the `updateAfter` parameter in
69 * the `getIssueUpdates` function.
70 * If the list is `null` or empty, the import implementation will behave as if `DEFAULT_TIME_FORMATS` object was returned.
71 * The string presentations matching against the formats containing timezone (for example, `yyyy-MM-dd'T'HH:mm:ss.SSSX`
72 * or `yyyy-MM-dd'T'HH:mm:ss.SSSZ`) will be converted to timestamps in the timezone from the presentation, otherwise
73 * the timezone returning by {@link #getUserTimeZoneId() getUserTimeZoneId} method is used.
74 * @returns {string[]} A list of timestamp formats.
75 */
76 getTimestampFormats() {
77 return ["yyyy-MM-dd'T'HH:mm:ss'Z'", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", 'yyyy-MM-dd'];
78 }
79
80 /**
81 * Returns the version and the time of the server.
82 * @returns {ServerInfo} An object with the version and time of the server set as its properties.
83 */
84 getServerInfo() {
85 return {
86 version: 'demo',
87 time: new Date().toISOString()
88 };
89 }
90
91 /**
92 * Returns the content of the specified attachment.
93 * @param {ProjectInfo} project The project where the attachment belongs to.
94 * @param {DocumentInfo} document The type of the entity where the attachment belongs to (issue or article).
95 * @param {Attachment} attachment The specified attachment.
96 * @returns {AttachmentContentWithMetadata} The content of the specified attachment.
97 */
98 getAttachmentContent(project, document, attachment) {
99 return {
100 data: '',
101 metadata: {
102 mimeType: 'image/jpeg'
103 }
104 };
105 }
106
107 /**
108 * Returns all link types available in the import source.
109 * @returns {LinkType[]} A list of link types.
110 */
111 getLinkTypes() {
112 return [];
113 }
114
115 /**
116 * Returns an object containing a detailed description of a project including its custom fields schema. In case the
117 * method is not defined by a client, the custom fields schema (types, values, cardinality) will be defined based on
118 * the values provided in the `Issue` objects.
119 * @param {ProjectInfo} projectInfo Information allowing the client to locate the project.
120 * @returns {Project} The detailed project description.
121 */
122 getProject(projectInfo) {
123 return demoProject;
124 }
125
126 /**
127 * Returns information about all projects available in the import source.
128 * @returns {ProjectInfo[]} A list of projects available in the import source.
129 */
130 getProjects() {
131 return [demoProject];
132 }
133
134 /**
135 * This method is invoked during the initial issues loading phase to load issues in chunks.
136 * It's recommended that the issues are ordered by either internal id or created date ascending. The returned
137 * issues will be imported one by one in the same order.
138 * Returns issues from the specified project.
139 * @param {ProjectInfo} projectInfo The project the returned issues belong to.
140 * @param {DocumentInfo=} after The issue that the returned issues follow.
141 * @param {number} top The number of issues to be returned. If there are fewer issues left to import, the method returns them all,
142 * if the method returns more issues than it was requested, those extra issues are not processed, and the next time the method is called,
143 * the last of the processed issues is passed into `after` parameter.
144 * @returns {Issue[]} A list of issues that follow the specified one in the specified project.
145 */
146 getIssues(projectInfo, after, top) {
147 return [demoIssue];
148 }
149
150 /**
151 * Returns users belonging to the specified group.
152 * @param {UserGroup} group The user group to retrieve members of.
153 * @param {number} skip Number of heading records to skip.
154 * @param {number} top Maximum number of records to be returned. If more records are returned, only `top` number
155 * of these records will be processed and the rest will be ignored.
156 * @returns {User[]} A list of users that belong to the specified project.
157 */
158 getUsers(group, skip, top) {
159 return [demoUser];
160 }
161
162 /**
163 * The method is only required if the client supports continuous import mode.
164 * The issues should be ordered either by internal id or created date ascending.
165 * Returns issues that were updated after the specified timestamp.
166 * @param {ProjectInfo} projectInfo The project where the requested issues belong.
167 * @param {DocumentInfo} [after] The issue that the requested issues follow.
168 * @param {string} updatedAfter The requested issues are expected to be updated after this timestamp.
169 * @param {number} top The number of issues to be returned. If there are fewer issues left to import, the method returns them all.
170 * If the method returns more issues than it was requested, those extra issues are not processed, and the next time the method is called,
171 * the last of the processed issues is passed into `after` parameter.
172 * @returns {Issue[]} Issues following the specified one in the specified project that were updated after the specified timestamp.
173 */
174 getIssueUpdates(projectInfo, after, updatedAfter, top) {
175 return [demoIssue];
176 }
177
178 /**
179 * Id of the timezone of the user that the import runs on behalf of. If `null` or empty, YouTrack user timezone id will be taken.
180 * @returns {string} The ID of the user timezone.
181 */
182 getUserTimeZoneId() {
183 return 'Europe/London';
184 }
185
186 /**
187 * Returns articles from the specified project.
188 * @param {ProjectInfo} projectInfo The project where the specified articles belong to.
189 * @param {DocumentInfo} [after] The article that the returned articles follow.
190 * @param {number} top The number of articles to be returned. If there are fewer articles left to import, the method returns them all.
191 * If the method returns more articles than it was requested, those extra articles are not processed, and the next time the method is called,
192 * the last of the processed articles is passed into `after` parameter.
193 * @return {Article[]} Articles following the specified one in the specified project.
194 */
195 getArticles(projectInfo, after, top) {
196 return [demoArticle];
197 }
198}
199
200/**
201 * @typedef {Object} ProjectInfo The base class containing information about a project.
202 * @property {string} id The ID of the project.
203 * @property {string} key The key of the project.
204 * @property {string} name The name of the project.
205 */
206
207/**
208 * @typedef {Object} Project Represents a project entity.
209 * @property {string} id The ID of the project.
210 * @property {string} key The key of the project.
211 * @property {string} name The name of the project.
212 * @property {string} description The description of the project.
213 * @property {boolean} archived When `true`, the project is marked as archived. Considered as `false` if the value of this property is not set.
214 * If the project already exists in YouTrack, its archived status is not updated.
215 * @property {User} lead The leader of the project.
216 * @property {ProjectField[]} fields The set of custom fields of the project.
217 * @property {User[]} [adminUsers] The set of users who have admin permissions in the project. Empty by default.
218 * @property {UserGroup[]} [adminGroups] The set of groups that have admin permissions in the project. Empty by default.
219 * @property {User[]} [teamUsers] The set of team members of the project. Empty by default.
220 * @property {UserGroup[]} [teamGroups] The set of groups included into the project team. Empty by default.
221 */
222
223/**
224 * @typedef {Object} ProjectField Represents a project custom field entity.
225 * @property {string} id The ID of the project custom field.
226 * @property {string} name The name of the project custom field.
227 * @property {string} type The type of the project custom field.
228 * @property {boolean} multiValue When `true`, the project custom field can support multiple values.
229 * A field can be either simple or multi-value. `false` by default.
230 */
231
232/**
233 * @typedef {Object} ServerInfo Represents a set of server properties.
234 * @property {string} version The version of the import source.
235 * @property {string} time The time zone of the import source.
236 */
237
238/**
239 * @typedef {Object} DocumentInfo The base class containing information about an issue or an article.
240 * @property {string} id The ID of the entity.
241 * @property {string} key The key of the entity.
242 */
243
244/**
245 * @typedef {Object} Issue Represents an issue entity.
246 * @property {string} id The issue ID.
247 * @property {string} key The issue key.
248 * @property {Map<string, IssueFieldValues>} [fields] The field values of the issue.
249 * | Issue Field Name | Type | Description | Required or Optional | Can Store Single Value or Multiple Values |
250 * | created | datetime | The date and time of the issue creation. | required | single |
251 * | author | user | The author of the issue. | required | single |
252 * | summary | string | The summary of the issue. | required | single |
253 * | description | text | The description of the issue. | optional | single |
254 * | updatedBy | user | The user who last updated the issue. | optional | single |
255 * | updated | datetime | The date and time when the issue was last updated. | optional | single |
256 * | resolved | datetime | The date and time when the issue was resolved. | optional | single |
257 * | links | link | The links of the issue. | optional | multi |
258 * | comments | comment | The comments of the issue. | optional | multi |
259 * | attachments | attachment | The attachments of the issue. | optional | multi |
260 * | workItems | workItem | The work items of the issue. | optional | multi |
261 * | references | reference | The references of the issue. | optional | multi |
262 * | visibleToUsers | user | The users who can view the issue. | optional | multi |
263 * | visibleToGroups | group | The user groups that can view the issue. | optional | multi |
264 * | watchers | user | The users who are watching the issue. | optional | multi |
265 * | voters | user | The users who voted the issue. | optional | multi |
266 * | tags | tag | The issue tags. | optional | multi |
267 * | project | project | The project where the issue belongs. | optional | single |
268 * @property {HistoryItem[]} [history] The history of the issue.
269 * @property {boolean} [usesMarkdown] When `true`, the issue description is parsed as Markdown. When `false`, the issue description is
270 * parsed as Wiki. Changing this value does not transform the markup from one syntax to another. When not set, acts as `true`.
271 */
272
273/**
274 * @typedef {Object} Article Represents an article entity.
275 * @property {string} id The article ID.
276 * @property {string} key The article key.
277 * @property {Map<string, FieldValue[]|FieldValue|string[]|string|number|Period>} [fields] The field values of the article.
278 * | Article Field Name | Type | Description | Required or Optional | Can Store Single Value or Multiple Values |
279 * | created | datetime | The date and time of the article creation. | required | single |
280 * | author | user | The author of the article. | required | single |
281 * | summary | string | The summary of the article. | required | single |
282 * | content | text | The content of the article. | optional | single |
283 * | updatedBy | user | The user who last updated the article. | optional | single |
284 * | updated | datetime | The date and time when the article was last updated. | optional | single |
285 * | parent | link | The parent of the article. | optional | single |
286 * | comments | comment | The comments of the article. | optional | multi |
287 * | attachments | attachment | The attachments of the article. | optional | multi |
288 * | references | reference | The references of the article. | optional | multi |
289 * | visibleToUsers | user | The users who can view the article. | optional | multi |
290 * | visibleToGroups | group | The user groups that can view the article. | optional | multi |
291 * | watchers | user | The users who are watching the article. | optional | multi |
292 * | project | project | The project where the article belongs. | optional | single |
293 * @property {HistoryItem[]} [history] The article history.
294 */
295
296/**
297 * @typedef {Object} IssueFieldValues Represents field values.
298 */
299
300/**
301 * @typedef {Object} User Represents a user entity.
302 * @property {string} id The ID of the user.
303 * @property {string} name The name of the user.
304 * @property {string=} fullName The full name of the user.
305 * @property {string=} email The email address of the user.
306 * @property {boolean=} banned If the user is currently banned, this property is `true`. The default value is `false`.
307 * @property {boolean=} deleted If the user is deleted, this property is `true`. The default value is `false`.
308 */
309
310/**
311 * @typedef {Object} UserGroup Represents a user group entity.
312 * @property {string} id The ID of the user group.
313 * @property {string} name The name of the user group.
314 */
315
316/**
317 * @typedef {Object} Attachment Represents an attachment entity.
318 * @property {string} id The ID of the attachment.
319 * @property {string} fileName The name of the attachment file.
320 * @property {User} author The user who attached the file to the issue.
321 * @property {number} created The date and time when the attachment was created as a timestamp.
322 * @property {User=} The user who last updated the file. The same as `author` by default.
323 * @property {number=} updated The date and time the attachment was last updated as a timestamp. The same as `created` by default.
324 * @property {string} mimeType The MIME type of the file.
325 * @property {string=} charset The charset type of the file. `null` by default. Applicable to text attachments only.
326 * @property {User[]} visibleToUsers The users who can view the attachment. If none of the `visibleToUsers` and the `visibleToGroups` parameters are specified, the attachment becomes visible to issue or article readers. `null` by default.
327 * @property {Group[]} visibleToGroups The user groups that can view the attachment. `null` by default.
328 */
329
330/**
331 * @typedef {Object} AttachmentContentWithMetadata Represents the content of an attachment with metadata.
332 * @property {Stream|string} data The attachment content as obtained via the `Response.responseAsStream` method or as base64-encoded content.
333 * @property {Object=} metadata The attachment metadata.
334 * @property {string=} metadata.mimeType The MIME type of the file. `null` by default.
335 * @property {string=} metadata.charset The charset type of the file. `null` by default. Applicable to text attachments only.
336 */
337
338/**
339 * @typedef {Object} LinkType Represents an issue link type entity.
340 * @property {string} id The ID of the issue link type.
341 * @property {string} name The name of the issue link type.
342 * @property {string} sourceToTarget The outward name of the issue link type.
343 * @property {string} targetToSource The inward name of the issue link type.
344 */
345
346/**
347 * @interface FieldValue The ancestor for custom field value entities.
348 * @property {'string'|'text'|'integer'|'float'|'date'|'period'|'datetime'|'state'|'ownedField'|'group'|'user'|'enum'|'version'|'build'|'link'|'comment'|'attachment'|'workItem'|'tag'|'reference'|'project'} type The type of the field.
349 */
350
351/**
352 * @typedef {Object} User Represents a user entity.
353 * Can store multiple values.
354 * @implements FieldValue
355 * @property {'user'} type
356 * @property {string} id The ID of the user.
357 * @property {string} name The name of the user.
358 * @property {string} [fullName] The full name of the user.
359 * @property {string} [email] The email address of the user.
360 * @property {boolean} [banned] If the user is currently banned, this property is `true`. The default value is `false`.
361 * @property {boolean} [deleted] If the user is deleted, this property is `true`. The default value is `false`.
362 */
363
364/**
365 * @typedef {Object} UserGroup Represents a user group entity.
366 * Can store multiple values.
367 * @implements FieldValue
368 * @property {'group'} type
369 * @property {string} id The ID of the user group.
370 * @property {string} name The name of the user group.
371 */
372
373/**
374 * @typedef {Object} Reference Represents a reference entity. Reference is a link from one article to another.
375 * When an article that's being imported contains a link to another article, this link is imported as a temporary string at first.
376 * This approach ensures that links to articles that are not imported yet are not lost.
377 * When import is finished, temporary strings are replaced with ordinary references to existing articles when possible.
378 * Can store multiple values.
379 * @implements FieldValue
380 * @property {'reference'} type
381 * @property {string} documentType The type of document in YouTrack where the reference points to. The expected value is `page`.
382 * @property {string} linkedDocumentId The ID of the linked document.
383 * @property {string} refToReplace The temporary reference string. When import is finished, this string will be replaced with a link to a real article.
384 * @property {string} [link] The URL of the linked article. If the linked article is not imported for some reason, this URL will be shown instead of a link.
385 */
386
387/**
388 * @typedef {Object} EnumElement Represents a value of a custom field of `enum` type.
389 * Can store multiple values.
390 * @implements FieldValue
391 * @property {'enum'} type
392 * @property {string} name The name of the enum custom field value.
393 * @property {string} [description] The description of the enum custom field value. When not set, acts as `null`.
394 * @property {boolean} [archived] When `true`, the enum custom field value is marked as archived. Considered as `false` if the value of this property is not set.
395 * If the EnumElement already exists in YouTrack, its archived status is not updated.
396 */
397
398/**
399 * @typedef {Object} State Represents a value of a custom field of `state` type.
400 * Can store multiple values.
401 * @augments EnumElement
402 * @implements FieldValue
403 * @property {'state'} type
404 * @property {boolean} [archived] When `true`, the state custom field value is marked as archived. Considered as `false` if the value of this property is not set.
405 * If the State value already exists in YouTrack, its archived status is not updated.
406 * @propery {boolean} [isResolved] When `true`, the state custom field value is marked as resolved. Considered as `false` if the value of this property is not set.
407 * If the State value already exists in YouTrack, its resolved status is not updated.
408 * /
409
410/**
411 * @typedef {Object} Build Represents a value of a custom field of `build` type.
412 * Can store multiple values.
413 * @augments EnumElement
414 * @implements FieldValue
415 * @property {'build'} type
416 * @property {string} [assembleDate] The date and time of the assemble date of the build custom field value. `null`
417 * by default. The expected string format is one of those returned by the `getTimestampFormats` function.
418 */
419
420/**
421 * @typedef {Object} Version Represents a value of a custom field of `state` type.
422 * Can store multiple values.
423 * @augments EnumElement
424 * @implements FieldValue
425 * @property {'version'} type
426 * @property {string} [releaseDate] The date and time of the release date of the version custom field value. `null`
427 * by default. The expected string format is one of those returned by the `getTimestampFormats` function.
428 * @property {boolean} [released] When `true`, the version custom field value is marked as released. `null` by default.
429 */
430
431/**
432 * @typedef {Object} OwnedValue Represents a value of a custom field of `ownedField` type.
433 * Can store multiple values.
434 * @augments EnumElement
435 * @implements FieldValue
436 * @property {'ownedField'} type
437 * @property {User} [owner] The user associated with the owned custom field value. `null` by default.
438 */
439
440/**
441 * @typedef {Object} SubItem The base class for authored entities that are associated with an issue.
442 * Can store multiple values.
443 * @implements FieldValue
444 * @property {string} id The id of the entity.
445 * @property {User} author The author of the entity.
446 * @property {string} created The date and time when the entity was created. The expected string format is one of those returned by the `getTimestampFormats` function.
447 * @property {User} [updatedBy] The user who last updated the entity.
448 * @property {string} [updated] The date and time when the entity was last updated. The expected string format is one of those returned by the `getTimestampFormats` function.
449 */
450
451/**
452 * @typedef {Object} Comment Represents a comment entity.
453 * Can store multiple values.
454 * @augments SubItem
455 * @implements FieldValue
456 * @property {'comment'} type
457 * @property {User[]} [visibleToUsers] The users who can view the comment. If none of the `visibleToUsers` and the `visibleToGroups` parameters are specified, the comment becomes visible to issue or article readers. `null` by default.
458 * @property {UserGroup[]} [visibleToGroups] The user groups that can view the comment. `null` by default.
459 * @property {boolean} [usesMarkdown] When `true`, the comment is parsed as Markdown. When `false`, the comment is parsed as Wiki.
460 * Changing this value does not transform the markup from one syntax to another. When not set, acts as `true`.
461 */
462
463/**
464 * @typedef {Object} Attachment Represents an attachment entity.
465 * Can store multiple values.
466 * @augments SubItem
467 * @implements FieldValue
468 * @property {'attachment'} type
469 * @property {User[]} [visibleToUsers] The users who can view the attachment. If none of the `visibleToUsers` and the `visibleToGroups` parameters are specified, the attachment becomes visible to issue or article readers. `null` by default.
470 * @property {UserGroup[]} [visibleToGroups] The user groups that can view the attachment. `null` by default.
471 * @property {string} [mimeType] The MIME type of the file.
472 * @property {string} [charset] The charset type of the file. Applicable to text attachments only.
473 */
474
475/**
476 * @typedef {Object} WorkItem Represents a work item entity.
477 * Can store multiple values.
478 * @augments SubItem
479 * @implements FieldValue
480 * @property {'workItem'} type
481 * @property {string} created The date when the work item was created. The expected string format is one of those returned by the `getTimestampFormats` function.
482 * @property {string} started The date and time when the work started. The same as `created` by default. The expected string format is one of those returned by the `getTimestampFormats` function.
483 * @property {number} duration The duration of the work item in minutes.
484 */
485
486/**
487 * @typedef {Object} Tag Represents an issue tag entity.
488 * Can store multiple values.
489 * @implements FieldValue
490 * @property {'tag'} type
491 * @property {string} id The ID of the issue tag.
492 * @property {string} name The name of the issue tag.
493 * @property {User} owner The user who created the issue tag.
494 * @property {User[]} [visibleToUsers] The users who can see this issue tag. If the tag is visible only for its owner, this property is `null`. `null` by default.
495 * @property {UserGroup[]} [visibleToGroups] The user groups that can see this issue tag. If the tag is visible only for its owner, this property is `null`. `null` by default.
496 * @property {User[]} [editableByUsers] The users who can edit this issue tag. If the tag is editable only by its owner, this property is `null`. `null` by default.
497 * @property {UserGroup[]} [editableByGroups] The user groups that can edit this issue tag. If the tag is editable only by its owner, this property is `null`. `null` by default.
498 */
499
500/**
501 * @typedef {Object} LinkValue Represents a link from one issue to another issue.
502 * When an issue that's being imported contains a link to another issue, this link is imported as a temporary string at first.
503 * This approach ensures that links to issues that are not imported yet are not lost.
504 * When import is finished, temporary strings are replaced with ordinary references to existing issues when possible.
505 * Can store multiple values.
506 * @implements FieldValue
507 * @property {'link'} type
508 * @property {string} linkName The name of the link.
509 * @property {DocumentInfo} target The target entity that the link points to.
510 * @property {LinkType} [linkType] The type of the link.
511 */
512
513/**
514 * @typedef {Object} StringFieldValue Represents a value of a custom field of `string` type.
515 * Can only store single values.
516 * @implements FieldValue
517 * @property {'string'} type
518 * @property {string} value.
519 */
520
521/**
522 * @typedef {Object} TextFieldValue Represents a value of a custom field of `text` type.
523 * Can only store single values.
524 * @implements FieldValue
525 * @property {'text'} type
526 * @property {string} value.
527 */
528
529/**
530 * @typedef {Object} IntegerFieldValue Represents a value of a custom field of `integer` type.
531 * Can only store single values.
532 * @implements FieldValue
533 * @property {'integer'} type
534 * @property {number} value.
535 */
536
537/**
538 * @typedef {Object} FloatFieldValue Represents a value of a custom field of `float` type.
539 * Can only store single values.
540 * @implements FieldValue
541 * @property {'float'} type
542 * @property {number} value.
543 */
544
545/**
546 * @typedef {Object} DateFieldValue Represents a value of a custom field of `date` type.
547 * Can only store single values.
548 * @implements FieldValue
549 * @property {'date'} type
550 * @property {string} value The expected string format is one of those returned by the `getTimestampFormats` function.
551 */
552
553/**
554 * @typedef {Object} DateTimeFieldValue Represents a value of a custom field of `date and time` type.
555 * Can only store single values.
556 * @implements FieldValue
557 * @property {'dateTime'} type
558 * @property {string} value The expected string format is one of those returned by the `getTimestampFormats` function.
559 */
560
561/**
562 * @typedef {Object} PeriodFieldValue Represents a value of a custom field of `period` type.
563 * Can only store single values.
564 * @implements FieldValue
565 * @property {'period'} type
566 * @property {string|number} value The expected string format is one of those returned by the `getTimestampFormats` function.
567 */
568
569
570exports.Client = context => new DemoClient(context);