complexqa_frontend_core 1.23.1 → 1.25.1

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.
Files changed (38) hide show
  1. package/package.json +1 -1
  2. package/publish/api/attachment_api.js +55 -0
  3. package/publish/api/file_app_api.js +22 -0
  4. package/publish/api/index.js +10 -0
  5. package/publish/api/tag/tag_color_api.js +16 -0
  6. package/publish/api/tag/tag_template_api.js +16 -0
  7. package/publish/api/tag_api.js +52 -0
  8. package/publish/api/tag_relation_api.js +57 -0
  9. package/publish/app_configuration/table/helpers/table_element_models.js +6 -0
  10. package/publish/app_configuration/table/sections/bug.js +7 -1
  11. package/publish/app_configuration/table/sections/tag.js +84 -0
  12. package/publish/app_configuration/table/sections/tag_relation.js +309 -0
  13. package/publish/app_configuration/table/sections/test_account.js +7 -1
  14. package/publish/app_configuration/table/sections/test_case.js +7 -1
  15. package/publish/app_configuration/table/sections/test_run.js +7 -1
  16. package/publish/app_configuration/table/sections/test_run_result.js +7 -1
  17. package/publish/app_configuration/table/sections/text_document.js +7 -1
  18. package/publish/app_configuration/table/table_configuration.js +4 -0
  19. package/publish/index.js +3 -0
  20. package/publish/services/FileAppService.js +67 -21
  21. package/publish/types/core/1_familyTestDocumentation.js +9 -1
  22. package/publish/types/element_translate/typeAttachment.i18n.js +201 -0
  23. package/publish/types/element_translate/typeBug.i18n.js +11 -0
  24. package/publish/types/element_translate/typeTag.i18n.js +69 -0
  25. package/publish/types/element_translate/typeTagRelation.i18n.js +74 -0
  26. package/publish/types/element_translate/typeTestAccount.i18n.js +11 -0
  27. package/publish/types/element_translate/typeTestCase.i18n.js +11 -0
  28. package/publish/types/element_translate/typeTestRun.i18n.js +11 -0
  29. package/publish/types/element_translate/typeTestRunResult.i18n.js +11 -0
  30. package/publish/types/element_translate/typeTextDocument.i18n.js +11 -0
  31. package/publish/types/family_elements/typeAttachment.js +119 -0
  32. package/publish/types/family_elements/typeBug.js +0 -2
  33. package/publish/types/family_elements/typeTag.js +84 -0
  34. package/publish/types/family_elements/typeTestAccount.js +2 -0
  35. package/publish/types/family_events/familyEvent.js +3 -0
  36. package/publish/types/family_files/typeFileScreenshot.js +2 -2
  37. package/publish/types/family_files/typeTextDocument.js +2 -0
  38. package/publish/types/family_relation/typeTagRelation.js +123 -0
@@ -0,0 +1,309 @@
1
+ import { typeTagRelation } from '../../../types/family_relation/typeTagRelation.js';
2
+ import { typeTestCase } from '../../../types/family_elements/typeTestCase.js';
3
+ import { typeBug } from '../../../types/family_elements/typeBug.js';
4
+ import { typeTestRun } from '../../../types/family_elements/typeTestRun.js';
5
+ import { typeTestSuite } from '../../../types/family_elements/typeTestSuite.js';
6
+ import { typeTestAccount } from '../../../types/family_elements/typeTestAccount.js';
7
+ import { typeTestRunResult } from '../../../types/family_elements/typeTestRunResult.js';
8
+ import { typeTextDocument } from '../../../types/family_files/typeTextDocument.js';
9
+ import { typeExecutionContext } from '../../../types/family_context/typeExecutionContext.js';
10
+ import { UserService } from '../../../services/UserService.js';
11
+ import { createColumn, createGoToColumn } from '../helpers/column_factory.js';
12
+ import { get_execution_context_listing_field_specs } from '../helpers/execution_context_listing_fields.js';
13
+ import { createListingFor, registerSection } from '../helpers/section_registry.js';
14
+
15
+ /**
16
+ * Nested column helper: `element_id.<field>` with header from target element type.
17
+ *
18
+ * @param {typeTagRelation} relation_model
19
+ * @param {string} locale
20
+ * @param {string} header_element_type
21
+ * @param {string} field
22
+ * @param {object} [overrides]
23
+ * @returns {import('../../../types/family_service/typeTableColumn.js').typeTableColumn}
24
+ */
25
+ function elementColumn(relation_model, locale, header_element_type, field, overrides = {})
26
+ {
27
+ return createColumn(relation_model, locale, `element_id.${ field }`, {
28
+ header_field : overrides.header_field || field,
29
+ header_element_type: overrides.header_element_type || header_element_type,
30
+ is_editable : false,
31
+ ...overrides,
32
+ });
33
+ }
34
+
35
+
36
+ /**
37
+ * Linked test cases on tag/details — same fields as test_case × listing (read-only).
38
+ *
39
+ * @param {string} locale
40
+ * @returns {import('../../../types/family_service/typeTableColumn.js').typeTableColumn[]}
41
+ */
42
+ function build_tag_relation_test_cases_columns(locale)
43
+ {
44
+ const relation_model = new typeTagRelation();
45
+ const test_case_model = new typeTestCase();
46
+ const col = (field, overrides = {}) =>
47
+ elementColumn(relation_model, locale, 'test_case', field, overrides);
48
+
49
+ return [
50
+ col('test_case_id', { width: '60', default_sort: 'desc' }),
51
+ col('test_case_title', { width: '480', is_required: true, is_title: true }),
52
+ col('assigned_to', { width: '220' }),
53
+ col('test_case_time_to_execute', { width: '80', is_required: true }),
54
+ col('test_case_complexity', { width: '80', is_required: true }),
55
+ col('test_case_importance', { width: '80', is_required: true }),
56
+ col('test_case_status', { width: '150', is_required: true }),
57
+ createGoToColumn(test_case_model, locale, { field: 'element_id' }),
58
+ ];
59
+ }
60
+
61
+
62
+ /**
63
+ * Linked bugs on tag/details — same fields as bug × listing (read-only, without tags).
64
+ *
65
+ * @param {string} locale
66
+ * @returns {import('../../../types/family_service/typeTableColumn.js').typeTableColumn[]}
67
+ */
68
+ function build_tag_relation_bugs_columns(locale)
69
+ {
70
+ const relation_model = new typeTagRelation();
71
+ const bug_model = new typeBug();
72
+ const col = (field, overrides = {}) =>
73
+ elementColumn(relation_model, locale, 'bug', field, overrides);
74
+
75
+ return [
76
+ col('bug_id', { width: '60', default_sort: 'desc' }),
77
+ col('bug_title', { width: '420', is_required: true, is_title: true }),
78
+ col('bug_status', { width: '150', is_required: true }),
79
+ col('bug_priority', { width: '130', is_required: true }),
80
+ col('bug_severity', { width: '130', is_required: true }),
81
+ col('bug_type', { width: '150', is_required: true }),
82
+ col('bug_resolution', { width: '150', is_required: true, is_hide: true }),
83
+ col('assigned_to', { width: '220', is_required: true }),
84
+ col('created_at', { width: '170', is_required: true }),
85
+ col('completed_at', { width: '170', is_required: true, is_hide: true }),
86
+ createGoToColumn(bug_model, locale, { field: 'element_id' }),
87
+ ];
88
+ }
89
+
90
+
91
+ /**
92
+ * Linked test runs on tag/details — same fields as test_run × listing (read-only, without tags).
93
+ *
94
+ * @param {string} locale
95
+ * @returns {import('../../../types/family_service/typeTableColumn.js').typeTableColumn[]}
96
+ */
97
+ function build_tag_relation_test_runs_columns(locale)
98
+ {
99
+ const relation_model = new typeTagRelation();
100
+ const test_run_model = new typeTestRun();
101
+ const col = (field, overrides = {}) =>
102
+ elementColumn(relation_model, locale, 'test_run', field, overrides);
103
+
104
+ return [
105
+ col('test_run_id', { width: '60', default_sort: 'desc' }),
106
+ col('test_run_name', { width: '480', is_required: true, is_title: true }),
107
+ col('test_run_description', { width: '480', is_required: true }),
108
+ col('assigned_to', { width: '220', is_required: true }),
109
+ col('test_run_status', { width: '150', is_required: true }),
110
+ col('created_at', { width: '170', is_required: true }),
111
+ col('completed_at', { width: '170', is_required: true }),
112
+ createGoToColumn(test_run_model, locale, { field: 'element_id' }),
113
+ ];
114
+ }
115
+
116
+
117
+ /**
118
+ * Linked test suites on tag/details.
119
+ *
120
+ * @param {string} locale
121
+ * @returns {import('../../../types/family_service/typeTableColumn.js').typeTableColumn[]}
122
+ */
123
+ function build_tag_relation_test_suites_columns(locale)
124
+ {
125
+ const relation_model = new typeTagRelation();
126
+ const test_suite_model = new typeTestSuite();
127
+ const col = (field, overrides = {}) =>
128
+ elementColumn(relation_model, locale, 'test_suite', field, overrides);
129
+
130
+ return [
131
+ col('test_suite_id', { width: '60', default_sort: 'desc' }),
132
+ col('test_suite_name', { width: '480', is_required: true, is_title: true }),
133
+ createGoToColumn(test_suite_model, locale, { field: 'element_id' }),
134
+ ];
135
+ }
136
+
137
+
138
+ /**
139
+ * Linked test accounts on tag/details — same fields as test_account × listing (read-only, without tags).
140
+ *
141
+ * @param {string} locale
142
+ * @returns {import('../../../types/family_service/typeTableColumn.js').typeTableColumn[]}
143
+ */
144
+ function build_tag_relation_test_accounts_columns(locale)
145
+ {
146
+ const relation_model = new typeTagRelation();
147
+ const test_account_model = new typeTestAccount();
148
+ const col = (field, overrides = {}) =>
149
+ elementColumn(relation_model, locale, 'test_account', field, overrides);
150
+
151
+ return [
152
+ col('test_account_id', { width: '60', default_sort: 'desc' }),
153
+ col('resource', { width: '480', is_required: true, is_title: true }),
154
+ col('login', { width: '480', is_required: true }),
155
+ col('password', { width: '480' }),
156
+ col('test_account_description', { width: '480' }),
157
+ col('created_at', { width: '170' }),
158
+ col('updated_at', { width: '170' }),
159
+ createGoToColumn(test_account_model, locale, { field: 'element_id' }),
160
+ ];
161
+ }
162
+
163
+
164
+ /**
165
+ * Linked test run results on tag/details — same fields as bug_relation × test_run_results.
166
+ *
167
+ * @param {string} locale
168
+ * @returns {import('../../../types/family_service/typeTableColumn.js').typeTableColumn[]}
169
+ */
170
+ function build_tag_relation_test_run_results_columns(locale)
171
+ {
172
+ const relation_model = new typeTagRelation();
173
+ const result_model = new typeTestRunResult();
174
+ const col = (field, overrides = {}) =>
175
+ elementColumn(relation_model, locale, 'test_run_result', field, overrides);
176
+
177
+ return [
178
+ col('test_run_result_id', { width: '60', default_sort: 'desc' }),
179
+ col('assigned_to', { width: '220', is_required: true }),
180
+ col('created_at', { width: '170', is_required: true }),
181
+ col('updated_at', { width: '170', is_required: true }),
182
+ col('completed_at', { width: '170', is_required: true }),
183
+ col('test_result_status', { width: '150', is_required: true }),
184
+ createGoToColumn(result_model, locale, { field: 'element_id' }),
185
+ ];
186
+ }
187
+
188
+
189
+ /**
190
+ * Linked text documents on tag/details — same fields as text_document × listing (read-only, without tags).
191
+ *
192
+ * @param {string} locale
193
+ * @returns {import('../../../types/family_service/typeTableColumn.js').typeTableColumn[]}
194
+ */
195
+ function build_tag_relation_text_documents_columns(locale)
196
+ {
197
+ const relation_model = new typeTagRelation();
198
+ const text_document_model = new typeTextDocument();
199
+ const col = (field, overrides = {}) =>
200
+ elementColumn(relation_model, locale, 'text_document', field, overrides);
201
+
202
+ return [
203
+ col('text_document_id', { width: '60', default_sort: 'desc' }),
204
+ col('text_document_title', { width: '320', is_title: true }),
205
+ col('text_document_status', { width: '140' }),
206
+ col('text_document_folder_id', { width: '120' }),
207
+ col('created_at', { width: '170' }),
208
+ col('updated_at', { width: '170' }),
209
+ createGoToColumn(text_document_model, locale, { field: 'element_id' }),
210
+ ];
211
+ }
212
+
213
+
214
+ /**
215
+ * Linked execution contexts on tag/details.
216
+ *
217
+ * @param {string} locale
218
+ * @returns {import('../../../types/family_service/typeTableColumn.js').typeTableColumn[]}
219
+ */
220
+ function build_tag_relation_execution_contexts_columns(locale)
221
+ {
222
+ const relation_model = new typeTagRelation();
223
+ const context_model = new typeExecutionContext();
224
+ const col = (field, overrides = {}) =>
225
+ elementColumn(relation_model, locale, 'execution_context', field, overrides);
226
+
227
+ return [
228
+ ...get_execution_context_listing_field_specs().map(({ field, overrides }) => col(field, overrides)),
229
+ createGoToColumn(context_model, locale, { field: 'element_id' }),
230
+ ];
231
+ }
232
+
233
+
234
+ /**
235
+ * Tags attached to an element (hydrate tag_id).
236
+ *
237
+ * @param {string} locale
238
+ * @returns {import('../../../types/family_service/typeTableColumn.js').typeTableColumn[]}
239
+ */
240
+ function build_element_tags_columns(locale)
241
+ {
242
+ const relation_model = new typeTagRelation();
243
+ const columns = [];
244
+
245
+ const tagColumn = (field, overrides = {}) => createColumn(relation_model, locale, `tag_id.${ field }`, {
246
+ header_field : field,
247
+ header_element_type: 'tag',
248
+ is_editable : false,
249
+ ...overrides,
250
+ });
251
+
252
+ columns.push(tagColumn('tag_id', { width: '60', default_sort: 'desc' }));
253
+ columns.push(tagColumn('name', {
254
+ width : '280',
255
+ is_required: true,
256
+ is_title : true,
257
+ }));
258
+ columns.push(tagColumn('tag_color_id', { width: '120' }));
259
+
260
+ return columns;
261
+ }
262
+
263
+
264
+ /**
265
+ * Секции `tag_relation` для tag/details tabs и attach на элементах.
266
+ *
267
+ * @param {Object} config
268
+ * @returns {void}
269
+ */
270
+ export function bootstrap_tag_relation(config)
271
+ {
272
+ const locale = UserService.get_current_language();
273
+
274
+ registerSection(config, 'tag_relation', 'test_cases', build_tag_relation_test_cases_columns(locale), {
275
+ for: createListingFor([ 'tag_id', 'element_type' ], { add: [ 'elements' ] }),
276
+ });
277
+
278
+ registerSection(config, 'tag_relation', 'bugs', build_tag_relation_bugs_columns(locale), {
279
+ for: createListingFor([ 'tag_id', 'element_type' ], { add: [ 'elements' ] }),
280
+ });
281
+
282
+ registerSection(config, 'tag_relation', 'test_runs', build_tag_relation_test_runs_columns(locale), {
283
+ for: createListingFor([ 'tag_id', 'element_type' ], { add: [ 'elements' ] }),
284
+ });
285
+
286
+ registerSection(config, 'tag_relation', 'test_suites', build_tag_relation_test_suites_columns(locale), {
287
+ for: createListingFor([ 'tag_id', 'element_type' ], { add: [ 'elements' ] }),
288
+ });
289
+
290
+ registerSection(config, 'tag_relation', 'test_accounts', build_tag_relation_test_accounts_columns(locale), {
291
+ for: createListingFor([ 'tag_id', 'element_type' ], { add: [ 'elements' ] }),
292
+ });
293
+
294
+ registerSection(config, 'tag_relation', 'test_run_results', build_tag_relation_test_run_results_columns(locale), {
295
+ for: createListingFor([ 'tag_id', 'element_type' ], { add: [ 'elements' ] }),
296
+ });
297
+
298
+ registerSection(config, 'tag_relation', 'text_documents', build_tag_relation_text_documents_columns(locale), {
299
+ for: createListingFor([ 'tag_id', 'element_type' ], { add: [ 'elements' ] }),
300
+ });
301
+
302
+ registerSection(config, 'tag_relation', 'execution_contexts', build_tag_relation_execution_contexts_columns(locale), {
303
+ for: createListingFor([ 'tag_id', 'element_type' ], { add: [ 'elements' ] }),
304
+ });
305
+
306
+ registerSection(config, 'tag_relation', 'element_tags', build_element_tags_columns(locale), {
307
+ for: createListingFor([ 'element_type', 'element_id' ], { add: [ 'tags' ] }),
308
+ });
309
+ }
@@ -12,6 +12,12 @@ function build_test_account_listing_columns(locale)
12
12
  createPrimaryKeyColumn(model, locale),
13
13
  createColumn(model, locale, 'resource', { width: '480', is_editable: true, is_required: true }),
14
14
  createColumn(model, locale, 'login', { width: '480', is_editable: true, is_required: true }),
15
+ createColumn(model, locale, 'tags', {
16
+ width : '220',
17
+ is_editable: false,
18
+ is_sortable: false,
19
+ is_required: false,
20
+ }),
15
21
  createColumn(model, locale, 'password', { width: '480', is_editable: true }),
16
22
  createColumn(model, locale, 'test_account_description', { width: '480', is_editable: true }),
17
23
  createColumn(model, locale, 'created_at', { width: '170' }),
@@ -67,7 +73,7 @@ export function bootstrap_test_account(config)
67
73
  const locale = UserService.get_current_language();
68
74
 
69
75
  registerSection(config, 'test_account', 'listing', build_test_account_listing_columns(locale), {
70
- for: createListingFor([ 'project_id' ]),
76
+ for: createListingFor([ 'project_id' ], { add: [ 'tags' ] }),
71
77
  });
72
78
 
73
79
  registerSection(config, 'test_account', 'details', build_test_account_details_columns(locale));
@@ -21,6 +21,12 @@ function build_test_case_listing_columns(locale, { include_select = true, includ
21
21
  is_title : true,
22
22
  }));
23
23
  columns.push(createColumn(model, locale, 'assigned_to', { width: '220', is_editable: true }));
24
+ columns.push(createColumn(model, locale, 'tags', {
25
+ width : '220',
26
+ is_editable: false,
27
+ is_sortable: false,
28
+ is_required: false,
29
+ }));
24
30
  columns.push(createColumn(model, locale, 'test_case_time_to_execute', { width: '80', is_editable: true, is_required: true }));
25
31
  columns.push(createColumn(model, locale, 'test_case_complexity', { width: '80', is_editable: true, is_required: true }));
26
32
  columns.push(createColumn(model, locale, 'test_case_importance', { width: '80', is_editable: true, is_required: true }));
@@ -59,7 +65,7 @@ export function bootstrap_test_case(config)
59
65
  const locale = UserService.get_current_language();
60
66
 
61
67
  registerSection(config, 'test_case', 'listing', build_test_case_listing_columns(locale), {
62
- for: createListingFor([ 'project_id', 'test_suite_id' ]),
68
+ for: createListingFor([ 'project_id', 'test_suite_id' ], { add: [ 'tags' ] }),
63
69
  });
64
70
 
65
71
  registerSection(config, 'test_case', 'todo', build_test_case_todo_columns(locale), {
@@ -25,6 +25,12 @@ function build_test_run_listing_columns(locale, { include_select = true } = {})
25
25
  is_required : true,
26
26
  }));
27
27
  columns.push(createColumn(model, locale, 'assigned_to', { width: '220', is_editable: true, is_required: true }));
28
+ columns.push(createColumn(model, locale, 'tags', {
29
+ width : '220',
30
+ is_editable: false,
31
+ is_sortable: false,
32
+ is_required: false,
33
+ }));
28
34
  columns.push(createColumn(model, locale, 'test_run_status', { width: '150', is_editable: true, is_required: true }));
29
35
  columns.push(createColumn(model, locale, 'created_at', { width: '170', is_required: true }));
30
36
  columns.push(createColumn(model, locale, 'completed_at', { width: '170', is_required: true }));
@@ -70,7 +76,7 @@ export function bootstrap_test_run(config)
70
76
  const locale = UserService.get_current_language();
71
77
 
72
78
  registerSection(config, 'test_run', 'listing', build_test_run_listing_columns(locale), {
73
- for: createListingFor([ 'project_id' ]),
79
+ for: createListingFor([ 'project_id' ], { add: [ 'tags' ] }),
74
80
  });
75
81
 
76
82
  registerSection(config, 'test_run', 'todo', build_test_run_todo_columns(locale), {
@@ -22,6 +22,12 @@ function build_test_run_result_listing_columns(locale, { include_select = true }
22
22
  is_required : true,
23
23
  }));
24
24
  columns.push(createColumn(model, locale, 'assigned_to', { width: '220', is_editable: true, is_required: true }));
25
+ columns.push(createColumn(model, locale, 'tags', {
26
+ width : '220',
27
+ is_editable: false,
28
+ is_sortable: false,
29
+ is_required: false,
30
+ }));
25
31
  columns.push(createColumn(model, locale, 'created_at', { width: '170', is_required: true }));
26
32
  columns.push(createColumn(model, locale, 'updated_at', { width: '170', is_required: true }));
27
33
  columns.push(createColumn(model, locale, 'completed_at', { width: '170', is_required: true }));
@@ -128,7 +134,7 @@ export function bootstrap_test_run_result(config)
128
134
  const locale = UserService.get_current_language();
129
135
 
130
136
  registerSection(config, 'test_run_result', 'listing', build_test_run_result_listing_columns(locale), {
131
- for: createListingFor([ 'project_id', 'test_case_id', 'test_run_id' ]),
137
+ for: createListingFor([ 'project_id', 'test_case_id', 'test_run_id' ], { add: [ 'tags' ] }),
132
138
  });
133
139
 
134
140
  registerSection(config, 'test_run_result', 'picker', build_test_run_result_picker_columns(locale), {
@@ -10,6 +10,12 @@ function build_text_document_listing_columns(locale)
10
10
  return [
11
11
  createPrimaryKeyColumn(model, locale),
12
12
  createColumn(model, locale, 'text_document_title', { width: '320', is_editable: true, is_title: true }),
13
+ createColumn(model, locale, 'tags', {
14
+ width : '220',
15
+ is_editable: false,
16
+ is_sortable: false,
17
+ is_required: false,
18
+ }),
13
19
  createColumn(model, locale, 'text_document_status', { width: '140', is_editable: true }),
14
20
  createColumn(model, locale, 'text_document_folder_id', { width: '120' }),
15
21
  createColumn(model, locale, 'created_at', { width: '170' }),
@@ -26,6 +32,6 @@ export function bootstrap_text_document(config)
26
32
  const locale = UserService.get_current_language();
27
33
 
28
34
  registerSection(config, 'text_document', 'listing', build_text_document_listing_columns(locale), {
29
- for: createListingFor([ 'project_id', 'text_document_folder_id' ]),
35
+ for: createListingFor([ 'project_id', 'text_document_folder_id' ], { add: [ 'tags' ] }),
30
36
  });
31
37
  }
@@ -1,6 +1,8 @@
1
1
  import { abstractAppConfiguration } from '../abstract_app_configuration.js';
2
2
  import { bootstrap_bug } from './sections/bug.js';
3
3
  import { bootstrap_bug_relation } from './sections/bug_relation.js';
4
+ import { bootstrap_tag } from './sections/tag.js';
5
+ import { bootstrap_tag_relation } from './sections/tag_relation.js';
4
6
  import { bootstrap_test_case } from './sections/test_case.js';
5
7
  import { bootstrap_test_case_step } from './sections/test_case_step.js';
6
8
  import { bootstrap_test_run } from './sections/test_run.js';
@@ -111,6 +113,8 @@ export class TableConfiguration extends abstractAppConfiguration
111
113
 
112
114
  bootstrap_bug(this.config);
113
115
  bootstrap_bug_relation(this.config);
116
+ bootstrap_tag(this.config);
117
+ bootstrap_tag_relation(this.config);
114
118
  bootstrap_test_case(this.config);
115
119
  bootstrap_test_case_step(this.config);
116
120
  bootstrap_test_run(this.config);
package/publish/index.js CHANGED
@@ -16,9 +16,12 @@ export { typeTestRun } from './types/family_elements/typeTestRun.js'
16
16
  export { typeTestRunResult } from './types/family_elements/typeTestRunResult.js'
17
17
  export { typeTestAccount } from './types/family_elements/typeTestAccount.js'
18
18
  export {typeBug} from './types/family_elements/typeBug.js';
19
+ export { typeTag } from './types/family_elements/typeTag.js';
20
+ export { typeAttachment } from './types/family_elements/typeAttachment.js';
19
21
 
20
22
  export { familyRelation } from './types/core/1_familyRelation.js';
21
23
  export { typeBugRelation } from './types/family_relation/typeBugRelation.js';
24
+ export { typeTagRelation } from './types/family_relation/typeTagRelation.js';
22
25
 
23
26
  export { familyContext } from './types/core/1_familyContext.js';
24
27
  export { typeBrowserContext } from './types/family_context/typeBrowserContext.js';
@@ -75,13 +75,53 @@ export class FileAppService extends abstractService
75
75
  }
76
76
 
77
77
 
78
+ /**
79
+ * Whether entity_type is present for File App binding.
80
+ * Empty / null / 0 → omit from token and upload init.
81
+ *
82
+ * @param {*} value
83
+ * @returns {boolean}
84
+ */
85
+ static #has_entity_type(value)
86
+ {
87
+ if (value === null || value === undefined)
88
+ {
89
+ return false;
90
+ }
91
+
92
+ const s = String(value).trim();
93
+
94
+ return s !== '' && s !== '0';
95
+ }
96
+
97
+
98
+ /**
99
+ * Whether entity_id is present for File App binding.
100
+ * Empty / null / 0 / NaN → omit from token and upload init.
101
+ *
102
+ * @param {*} value
103
+ * @returns {boolean}
104
+ */
105
+ static #has_entity_id(value)
106
+ {
107
+ if (value === null || value === undefined || value === '')
108
+ {
109
+ return false;
110
+ }
111
+
112
+ const n = Number(value);
113
+
114
+ return !Number.isNaN(n) && n !== 0;
115
+ }
116
+
117
+
78
118
  /**
79
119
  * Upload browser File/Blob to File App (token via Main App bridge).
80
120
  *
81
121
  * @param {object} payload
82
122
  * @param {File|Blob} payload.file
83
- * @param {string} payload.entity_type
84
- * @param {number} payload.entity_id
123
+ * @param {string} [payload.entity_type] — omit / null / 0 for unbound upload (attachments)
124
+ * @param {number} [payload.entity_id] — omit / null / 0 for unbound upload (attachments)
85
125
  * @param {string} payload.category
86
126
  * @param {string} [payload.original_name]
87
127
  * @param {string} [payload.file_uuid] — re-upload / new version for existing file
@@ -97,20 +137,24 @@ export class FileAppService extends abstractService
97
137
  throw new Error('FileAppService.upload_file: file is required');
98
138
  }
99
139
 
100
- const entity_type = String(payload.entity_type || '');
101
- const entity_id = Number(payload.entity_id);
102
- const category = String(payload.category || '');
103
- const mime_type = file.type || 'application/octet-stream';
104
- const size_bytes = Number(file.size || 0);
105
- const original_name = String(payload.original_name || file.name || 'upload');
106
- const api = FileAppService.#get_api_service();
107
-
108
- const token_wrap = await api.file_app.issue_access_token({
109
- scopes : [ 'file:upload', 'file:read' ],
110
- entity_type,
111
- entity_id,
140
+ const has_entity_type = FileAppService.#has_entity_type(payload.entity_type);
141
+ const has_entity_id = FileAppService.#has_entity_id(payload.entity_id);
142
+ const entity_type = has_entity_type ? String(payload.entity_type) : undefined;
143
+ const entity_id = has_entity_id ? Number(payload.entity_id) : undefined;
144
+ const category = String(payload.category || '');
145
+ const mime_type = file.type || 'application/octet-stream';
146
+ const size_bytes = Number(file.size || 0);
147
+ const original_name = String(payload.original_name || file.name || 'upload');
148
+ const api = FileAppService.#get_api_service();
149
+
150
+ const token_payload = {
151
+ scopes: [ 'file:upload', 'file:read' ],
112
152
  size_bytes,
113
- });
153
+ ...(has_entity_type ? { entity_type } : {}),
154
+ ...(has_entity_id ? { entity_id } : {}),
155
+ };
156
+
157
+ const token_wrap = await api.file_app.issue_access_token(token_payload);
114
158
 
115
159
  const token_data = await token_wrap?.response;
116
160
 
@@ -130,15 +174,17 @@ export class FileAppService extends abstractService
130
174
  throw new ApiException({ message: 'File App access token was not issued' });
131
175
  }
132
176
 
133
- const init_response = await axios.post(`${ file_app_url }/api/v1/uploads`, {
177
+ const init_payload = {
134
178
  category,
135
179
  mime_type,
136
180
  size_bytes,
137
181
  original_name,
138
- entity_type,
139
- entity_id,
182
+ ...(has_entity_type ? { entity_type } : {}),
183
+ ...(has_entity_id ? { entity_id } : {}),
140
184
  ...(payload.file_uuid ? { file_uuid: String(payload.file_uuid) } : {}),
141
- }, {
185
+ };
186
+
187
+ const init_response = await axios.post(`${ file_app_url }/api/v1/uploads`, init_payload, {
142
188
  headers: {
143
189
  Authorization: `Bearer ${ access_token }`,
144
190
  Accept : 'application/json',
@@ -183,8 +229,8 @@ export class FileAppService extends abstractService
183
229
  const wire = {
184
230
  ...confirmed,
185
231
  category : confirmed.category || category,
186
- entity_type: confirmed.entity_type || entity_type,
187
- entity_id : confirmed.entity_id ?? entity_id,
232
+ entity_type: confirmed.entity_type ?? entity_type ?? null,
233
+ entity_id : confirmed.entity_id ?? entity_id ?? null,
188
234
  mime_type : confirmed.mime_type || mime_type,
189
235
  };
190
236
 
@@ -8,6 +8,14 @@ import { familyGeneralElement } from "./0_familyGeneralElement.js";
8
8
  */
9
9
  export class familyTestDocumentation extends familyGeneralElement
10
10
  {
11
+ /**
12
+ * Hydrated on element search via options.add: ['tags'].
13
+ * Catalog entities (e.g. typeTag) leave this unset.
14
+ *
15
+ * @type {Array<object>|undefined}
16
+ */
17
+ tags;
18
+
11
19
 
12
20
  /**
13
21
  *
@@ -18,4 +26,4 @@ export class familyTestDocumentation extends familyGeneralElement
18
26
  super();
19
27
  }
20
28
 
21
- }
29
+ }