@justbrunasso/n8n-nodes-glpi-v2 1.0.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.
Files changed (44) hide show
  1. package/.github/workflows/ci.yml +28 -0
  2. package/.prettierrc.js +51 -0
  3. package/.vscode/extensions.json +7 -0
  4. package/.vscode/launch.json +12 -0
  5. package/CHANGELOG.md +0 -0
  6. package/CODE_OF_CONDUCT.md +76 -0
  7. package/LICENSE.md +19 -0
  8. package/README.md +247 -0
  9. package/README_TEMPLATE.md +48 -0
  10. package/api_docs.json +1 -0
  11. package/credentials/GithubIssuesApi.credentials.ts +45 -0
  12. package/credentials/GithubIssuesOAuth2Api.credentials.ts +54 -0
  13. package/eslint.config.mjs +3 -0
  14. package/icons/github.dark.svg +3 -0
  15. package/icons/github.svg +3 -0
  16. package/nodes/Example/Example.node.json +18 -0
  17. package/nodes/Example/Example.node.ts +78 -0
  18. package/nodes/Example/example.dark.svg +13 -0
  19. package/nodes/Example/example.svg +13 -0
  20. package/nodes/GithubIssues/GithubIssues.node.json +18 -0
  21. package/nodes/GithubIssues/GithubIssues.node.ts +96 -0
  22. package/nodes/GithubIssues/listSearch/getIssues.ts +49 -0
  23. package/nodes/GithubIssues/listSearch/getRepositories.ts +50 -0
  24. package/nodes/GithubIssues/listSearch/getUsers.ts +49 -0
  25. package/nodes/GithubIssues/resources/issue/create.ts +74 -0
  26. package/nodes/GithubIssues/resources/issue/get.ts +14 -0
  27. package/nodes/GithubIssues/resources/issue/getAll.ts +124 -0
  28. package/nodes/GithubIssues/resources/issue/index.ts +75 -0
  29. package/nodes/GithubIssues/resources/issueComment/getAll.ts +65 -0
  30. package/nodes/GithubIssues/resources/issueComment/index.ts +47 -0
  31. package/nodes/GithubIssues/shared/descriptions.ts +151 -0
  32. package/nodes/GithubIssues/shared/transport.ts +32 -0
  33. package/nodes/GithubIssues/shared/utils.ts +14 -0
  34. package/nodes/GlpiV2/GlpiV2.credentials.ts +76 -0
  35. package/nodes/GlpiV2/GlpiV2.node.ts +50 -0
  36. package/nodes/GlpiV2/GlpiV2.svg +5 -0
  37. package/nodes/GlpiV2/resources/computer/create.ts +43 -0
  38. package/nodes/GlpiV2/resources/computer/delete.ts +37 -0
  39. package/nodes/GlpiV2/resources/computer/get.ts +37 -0
  40. package/nodes/GlpiV2/resources/computer/getAll.ts +61 -0
  41. package/nodes/GlpiV2/resources/computer/index.ts +61 -0
  42. package/nodes/GlpiV2/resources/computer/update.ts +55 -0
  43. package/package.json +33 -0
  44. package/tsconfig.json +16 -0
@@ -0,0 +1,124 @@
1
+ import type { INodeProperties } from 'n8n-workflow';
2
+ import { parseLinkHeader } from '../../shared/utils';
3
+
4
+ const showOnlyForIssueGetMany = {
5
+ operation: ['getAll'],
6
+ resource: ['issue'],
7
+ };
8
+
9
+ export const issueGetManyDescription: INodeProperties[] = [
10
+ {
11
+ displayName: 'Limit',
12
+ name: 'limit',
13
+ type: 'number',
14
+ displayOptions: {
15
+ show: {
16
+ ...showOnlyForIssueGetMany,
17
+ returnAll: [false],
18
+ },
19
+ },
20
+ typeOptions: {
21
+ minValue: 1,
22
+ maxValue: 100,
23
+ },
24
+ default: 50,
25
+ routing: {
26
+ send: {
27
+ type: 'query',
28
+ property: 'per_page',
29
+ },
30
+ output: {
31
+ maxResults: '={{$value}}',
32
+ },
33
+ },
34
+ description: 'Max number of results to return',
35
+ },
36
+ {
37
+ displayName: 'Return All',
38
+ name: 'returnAll',
39
+ type: 'boolean',
40
+ displayOptions: {
41
+ show: showOnlyForIssueGetMany,
42
+ },
43
+ default: false,
44
+ description: 'Whether to return all results or only up to a given limit',
45
+ routing: {
46
+ send: {
47
+ paginate: '={{ $value }}',
48
+ type: 'query',
49
+ property: 'per_page',
50
+ value: '100',
51
+ },
52
+ operations: {
53
+ pagination: {
54
+ type: 'generic',
55
+ properties: {
56
+ continue: `={{ !!(${parseLinkHeader.toString()})($response.headers?.link).next }}`,
57
+ request: {
58
+ url: `={{ (${parseLinkHeader.toString()})($response.headers?.link)?.next ?? $request.url }}`,
59
+ },
60
+ },
61
+ },
62
+ },
63
+ },
64
+ },
65
+ {
66
+ displayName: 'Filters',
67
+ name: 'filters',
68
+ type: 'collection',
69
+ typeOptions: {
70
+ multipleValueButtonText: 'Add Filter',
71
+ },
72
+ displayOptions: {
73
+ show: showOnlyForIssueGetMany,
74
+ },
75
+ default: {},
76
+ options: [
77
+ {
78
+ displayName: 'Updated Since',
79
+ name: 'since',
80
+ type: 'dateTime',
81
+ default: '',
82
+ description: 'Return only issues updated at or after this time',
83
+ routing: {
84
+ request: {
85
+ qs: {
86
+ since: '={{$value}}',
87
+ },
88
+ },
89
+ },
90
+ },
91
+ {
92
+ displayName: 'State',
93
+ name: 'state',
94
+ type: 'options',
95
+ options: [
96
+ {
97
+ name: 'All',
98
+ value: 'all',
99
+ description: 'Returns issues with any state',
100
+ },
101
+ {
102
+ name: 'Closed',
103
+ value: 'closed',
104
+ description: 'Return issues with "closed" state',
105
+ },
106
+ {
107
+ name: 'Open',
108
+ value: 'open',
109
+ description: 'Return issues with "open" state',
110
+ },
111
+ ],
112
+ default: 'open',
113
+ description: 'The issue state to filter on',
114
+ routing: {
115
+ request: {
116
+ qs: {
117
+ state: '={{$value}}',
118
+ },
119
+ },
120
+ },
121
+ },
122
+ ],
123
+ },
124
+ ];
@@ -0,0 +1,75 @@
1
+ import type { INodeProperties } from 'n8n-workflow';
2
+ import { repoNameSelect, repoOwnerSelect } from '../../shared/descriptions';
3
+ import { issueGetManyDescription } from './getAll';
4
+ import { issueGetDescription } from './get';
5
+ import { issueCreateDescription } from './create';
6
+
7
+ const showOnlyForIssues = {
8
+ resource: ['issue'],
9
+ };
10
+
11
+ export const issueDescription: INodeProperties[] = [
12
+ {
13
+ displayName: 'Operation',
14
+ name: 'operation',
15
+ type: 'options',
16
+ noDataExpression: true,
17
+ displayOptions: {
18
+ show: showOnlyForIssues,
19
+ },
20
+ options: [
21
+ {
22
+ name: 'Get Many',
23
+ value: 'getAll',
24
+ action: 'Get issues in a repository',
25
+ description: 'Get many issues in a repository',
26
+ routing: {
27
+ request: {
28
+ method: 'GET',
29
+ url: '=/repos/{{$parameter.owner}}/{{$parameter.repository}}/issues',
30
+ },
31
+ },
32
+ },
33
+ {
34
+ name: 'Get',
35
+ value: 'get',
36
+ action: 'Get an issue',
37
+ description: 'Get the data of a single issue',
38
+ routing: {
39
+ request: {
40
+ method: 'GET',
41
+ url: '=/repos/{{$parameter.owner}}/{{$parameter.repository}}/issues/{{$parameter.issue}}',
42
+ },
43
+ },
44
+ },
45
+ {
46
+ name: 'Create',
47
+ value: 'create',
48
+ action: 'Create a new issue',
49
+ description: 'Create a new issue',
50
+ routing: {
51
+ request: {
52
+ method: 'POST',
53
+ url: '=/repos/{{$parameter.owner}}/{{$parameter.repository}}/issues',
54
+ },
55
+ },
56
+ },
57
+ ],
58
+ default: 'getAll',
59
+ },
60
+ {
61
+ ...repoOwnerSelect,
62
+ displayOptions: {
63
+ show: showOnlyForIssues,
64
+ },
65
+ },
66
+ {
67
+ ...repoNameSelect,
68
+ displayOptions: {
69
+ show: showOnlyForIssues,
70
+ },
71
+ },
72
+ ...issueGetManyDescription,
73
+ ...issueGetDescription,
74
+ ...issueCreateDescription,
75
+ ];
@@ -0,0 +1,65 @@
1
+ import type { INodeProperties } from 'n8n-workflow';
2
+ import { parseLinkHeader } from '../../shared/utils';
3
+
4
+ const showOnlyForIssueCommentGetMany = {
5
+ operation: ['getAll'],
6
+ resource: ['issueComment'],
7
+ };
8
+
9
+ export const issueCommentGetManyDescription: INodeProperties[] = [
10
+ {
11
+ displayName: 'Limit',
12
+ name: 'limit',
13
+ type: 'number',
14
+ displayOptions: {
15
+ show: {
16
+ ...showOnlyForIssueCommentGetMany,
17
+ returnAll: [false],
18
+ },
19
+ },
20
+ typeOptions: {
21
+ minValue: 1,
22
+ maxValue: 100,
23
+ },
24
+ default: 50,
25
+ routing: {
26
+ send: {
27
+ type: 'query',
28
+ property: 'per_page',
29
+ },
30
+ output: {
31
+ maxResults: '={{$value}}',
32
+ },
33
+ },
34
+ description: 'Max number of results to return',
35
+ },
36
+ {
37
+ displayName: 'Return All',
38
+ name: 'returnAll',
39
+ type: 'boolean',
40
+ displayOptions: {
41
+ show: showOnlyForIssueCommentGetMany,
42
+ },
43
+ default: false,
44
+ description: 'Whether to return all results or only up to a given limit',
45
+ routing: {
46
+ send: {
47
+ paginate: '={{ $value }}',
48
+ type: 'query',
49
+ property: 'per_page',
50
+ value: '100',
51
+ },
52
+ operations: {
53
+ pagination: {
54
+ type: 'generic',
55
+ properties: {
56
+ continue: `={{ !!(${parseLinkHeader.toString()})($response.headers?.link).next }}`,
57
+ request: {
58
+ url: `={{ (${parseLinkHeader.toString()})($response.headers?.link)?.next ?? $request.url }}`,
59
+ },
60
+ },
61
+ },
62
+ },
63
+ },
64
+ },
65
+ ];
@@ -0,0 +1,47 @@
1
+ import type { INodeProperties } from 'n8n-workflow';
2
+ import { repoNameSelect, repoOwnerSelect } from '../../shared/descriptions';
3
+ import { issueCommentGetManyDescription } from './getAll';
4
+
5
+ const showOnlyForIssueComments = {
6
+ resource: ['issueComment'],
7
+ };
8
+
9
+ export const issueCommentDescription: INodeProperties[] = [
10
+ {
11
+ displayName: 'Operation',
12
+ name: 'operation',
13
+ type: 'options',
14
+ noDataExpression: true,
15
+ displayOptions: {
16
+ show: showOnlyForIssueComments,
17
+ },
18
+ options: [
19
+ {
20
+ name: 'Get Many',
21
+ value: 'getAll',
22
+ action: 'Get issue comments',
23
+ description: 'Get issue comments',
24
+ routing: {
25
+ request: {
26
+ method: 'GET',
27
+ url: '=/repos/{{$parameter.owner}}/{{$parameter.repository}}/issues/comments',
28
+ },
29
+ },
30
+ },
31
+ ],
32
+ default: 'getAll',
33
+ },
34
+ {
35
+ ...repoOwnerSelect,
36
+ displayOptions: {
37
+ show: showOnlyForIssueComments,
38
+ },
39
+ },
40
+ {
41
+ ...repoNameSelect,
42
+ displayOptions: {
43
+ show: showOnlyForIssueComments,
44
+ },
45
+ },
46
+ ...issueCommentGetManyDescription,
47
+ ];
@@ -0,0 +1,151 @@
1
+ import type { INodeProperties } from 'n8n-workflow';
2
+
3
+ export const repoOwnerSelect: INodeProperties = {
4
+ displayName: 'Repository Owner',
5
+ name: 'owner',
6
+ type: 'resourceLocator',
7
+ default: { mode: 'list', value: '' },
8
+ required: true,
9
+ modes: [
10
+ {
11
+ displayName: 'Repository Owner',
12
+ name: 'list',
13
+ type: 'list',
14
+ placeholder: 'Select an owner...',
15
+ typeOptions: {
16
+ searchListMethod: 'getUsers',
17
+ searchable: true,
18
+ searchFilterRequired: false,
19
+ },
20
+ },
21
+ {
22
+ displayName: 'Link',
23
+ name: 'url',
24
+ type: 'string',
25
+ placeholder: 'e.g. https://github.com/n8n-io',
26
+ extractValue: {
27
+ type: 'regex',
28
+ regex: 'https:\\/\\/github.com\\/([-_0-9a-zA-Z]+)',
29
+ },
30
+ validation: [
31
+ {
32
+ type: 'regex',
33
+ properties: {
34
+ regex: 'https:\\/\\/github.com\\/([-_0-9a-zA-Z]+)(?:.*)',
35
+ errorMessage: 'Not a valid GitHub URL',
36
+ },
37
+ },
38
+ ],
39
+ },
40
+ {
41
+ displayName: 'By Name',
42
+ name: 'name',
43
+ type: 'string',
44
+ placeholder: 'e.g. n8n-io',
45
+ validation: [
46
+ {
47
+ type: 'regex',
48
+ properties: {
49
+ regex: '[-_a-zA-Z0-9]+',
50
+ errorMessage: 'Not a valid GitHub Owner Name',
51
+ },
52
+ },
53
+ ],
54
+ url: '=https://github.com/{{$value}}',
55
+ },
56
+ ],
57
+ };
58
+
59
+ export const repoNameSelect: INodeProperties = {
60
+ displayName: 'Repository Name',
61
+ name: 'repository',
62
+ type: 'resourceLocator',
63
+ default: {
64
+ mode: 'list',
65
+ value: '',
66
+ },
67
+ required: true,
68
+ modes: [
69
+ {
70
+ displayName: 'Repository Name',
71
+ name: 'list',
72
+ type: 'list',
73
+ placeholder: 'Select an Repository...',
74
+ typeOptions: {
75
+ searchListMethod: 'getRepositories',
76
+ searchable: true,
77
+ },
78
+ },
79
+ {
80
+ displayName: 'Link',
81
+ name: 'url',
82
+ type: 'string',
83
+ placeholder: 'e.g. https://github.com/n8n-io/n8n',
84
+ extractValue: {
85
+ type: 'regex',
86
+ regex: 'https:\\/\\/github.com\\/(?:[-_0-9a-zA-Z]+)\\/([-_.0-9a-zA-Z]+)',
87
+ },
88
+ validation: [
89
+ {
90
+ type: 'regex',
91
+ properties: {
92
+ regex: 'https:\\/\\/github.com\\/(?:[-_0-9a-zA-Z]+)\\/([-_.0-9a-zA-Z]+)(?:.*)',
93
+ errorMessage: 'Not a valid GitHub Repository URL',
94
+ },
95
+ },
96
+ ],
97
+ },
98
+ {
99
+ displayName: 'By Name',
100
+ name: 'name',
101
+ type: 'string',
102
+ placeholder: 'e.g. n8n',
103
+ validation: [
104
+ {
105
+ type: 'regex',
106
+ properties: {
107
+ regex: '[-_.0-9a-zA-Z]+',
108
+ errorMessage: 'Not a valid GitHub Repository Name',
109
+ },
110
+ },
111
+ ],
112
+ url: '=https://github.com/{{$parameter["owner"]}}/{{$value}}',
113
+ },
114
+ ],
115
+ displayOptions: {
116
+ hide: {
117
+ resource: ['user', 'organization'],
118
+ operation: ['getRepositories'],
119
+ },
120
+ },
121
+ };
122
+
123
+ export const issueSelect: INodeProperties = {
124
+ displayName: 'Issue',
125
+ name: 'issue',
126
+ type: 'resourceLocator',
127
+ default: {
128
+ mode: 'list',
129
+ value: '',
130
+ },
131
+ required: true,
132
+ modes: [
133
+ {
134
+ displayName: 'Issue',
135
+ name: 'list',
136
+ type: 'list',
137
+ placeholder: 'Select an Issue...',
138
+ typeOptions: {
139
+ searchListMethod: 'getIssues',
140
+ searchable: true,
141
+ },
142
+ },
143
+ {
144
+ displayName: 'By ID',
145
+ name: 'name',
146
+ type: 'string',
147
+ placeholder: 'e.g. 123',
148
+ url: '=https://github.com/{{$parameter.owner}}/{{$parameter.repository}}/issues/{{$value}}',
149
+ },
150
+ ],
151
+ };
@@ -0,0 +1,32 @@
1
+ import type {
2
+ IHookFunctions,
3
+ IExecuteFunctions,
4
+ IExecuteSingleFunctions,
5
+ ILoadOptionsFunctions,
6
+ IHttpRequestMethods,
7
+ IDataObject,
8
+ IHttpRequestOptions,
9
+ } from 'n8n-workflow';
10
+
11
+ export async function githubApiRequest(
12
+ this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
13
+ method: IHttpRequestMethods,
14
+ resource: string,
15
+ qs: IDataObject = {},
16
+ body: IDataObject | undefined = undefined,
17
+ ) {
18
+ const authenticationMethod = this.getNodeParameter('authentication', 0);
19
+
20
+ const options: IHttpRequestOptions = {
21
+ method: method,
22
+ qs,
23
+ body,
24
+ url: `https://api.github.com${resource}`,
25
+ json: true,
26
+ };
27
+
28
+ const credentialType =
29
+ authenticationMethod === 'accessToken' ? 'githubIssuesApi' : 'githubIssuesOAuth2Api';
30
+
31
+ return this.helpers.httpRequestWithAuthentication.call(this, credentialType, options);
32
+ }
@@ -0,0 +1,14 @@
1
+ export function parseLinkHeader(header?: string): { [rel: string]: string } {
2
+ const links: { [rel: string]: string } = {};
3
+
4
+ for (const part of header?.split(',') ?? []) {
5
+ const section = part.trim();
6
+ const match = section.match(/^<([^>]+)>\s*;\s*rel="?([^"]+)"?/);
7
+ if (match) {
8
+ const [, url, rel] = match;
9
+ links[rel] = url;
10
+ }
11
+ }
12
+
13
+ return links;
14
+ }
@@ -0,0 +1,76 @@
1
+ import {
2
+ ICredentialType,
3
+ INodeProperties,
4
+ } from 'n8n-workflow';
5
+
6
+ export class GlpiV2Api implements ICredentialType {
7
+ name = 'glpiV2Api';
8
+ extends = ['oAuth2Api'];
9
+ displayName = 'GLPI v2 API (OAuth2)';
10
+ documentationUrl = 'https://atendimento.centrium.com.br/api.php/v2.1/doc';
11
+ properties: INodeProperties[] = [
12
+ {
13
+ displayName: 'GLPI URL',
14
+ name: 'baseUrl',
15
+ type: 'string',
16
+ default: '',
17
+ placeholder: 'https://seu-glpi.com',
18
+ description: 'URL base do GLPI (sem /api.php).',
19
+ required: true,
20
+ },
21
+ {
22
+ displayName: 'Grant Type',
23
+ name: 'grantType',
24
+ type: 'hidden',
25
+ default: 'password',
26
+ },
27
+ {
28
+ displayName: 'Access Token URL',
29
+ name: 'accessTokenUrl',
30
+ type: 'hidden',
31
+ default: '={{$self["baseUrl"].replace(/\\/$/, "")}}/api.php/token',
32
+ },
33
+ {
34
+ displayName: 'Auth URL',
35
+ name: 'authUrl',
36
+ type: 'hidden',
37
+ default: '={{$self["baseUrl"]}}/api.php/authorize',
38
+ },
39
+ {
40
+ displayName: 'Scope',
41
+ name: 'scope',
42
+ type: 'hidden',
43
+ default: '',
44
+ },
45
+ {
46
+ displayName: 'Authentication',
47
+ name: 'authentication',
48
+ type: 'hidden',
49
+ default: 'body',
50
+ },
51
+ {
52
+ displayName: 'Username',
53
+ name: 'username',
54
+ type: 'string',
55
+ default: '',
56
+ required: true,
57
+ },
58
+ {
59
+ displayName: 'Password',
60
+ name: 'password',
61
+ type: 'string',
62
+ typeOptions: {
63
+ password: true,
64
+ },
65
+ default: '',
66
+ required: true,
67
+ },
68
+ {
69
+ displayName: 'App Token',
70
+ name: 'appToken',
71
+ type: 'string',
72
+ default: '',
73
+ description: 'App-Token (se necessário)',
74
+ },
75
+ ];
76
+ }
@@ -0,0 +1,50 @@
1
+ import { INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ import { computerOperations, computerFields } from './resources/computer';
3
+
4
+ export class GlpiV2 implements INodeType {
5
+ description: INodeTypeDescription = {
6
+ displayName: 'GLPI v2',
7
+ name: 'glpiV2',
8
+ icon: 'file:GlpiV2.svg',
9
+ group: ['transform'],
10
+ version: 1,
11
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
12
+ description: 'Interage com a API GLPI v2 (Declarativo)',
13
+ defaults: {
14
+ name: 'GLPI v2',
15
+ },
16
+ inputs: ['main'],
17
+ outputs: ['main'],
18
+ credentials: [
19
+ {
20
+ name: 'glpiV2Api',
21
+ required: true,
22
+ },
23
+ ],
24
+ requestDefaults: {
25
+ baseURL: '={{$credentials.baseUrl.replace(/\\/$/, "")}}/api.php/v2.1',
26
+ headers: {
27
+ Accept: 'application/json',
28
+ 'Content-Type': 'application/json',
29
+ 'App-Token': '={{$credentials.appToken}}',
30
+ },
31
+ },
32
+ properties: [
33
+ {
34
+ displayName: 'Resource',
35
+ name: 'resource',
36
+ type: 'options',
37
+ noDataExpression: true,
38
+ options: [
39
+ {
40
+ name: 'Computer',
41
+ value: 'computer',
42
+ },
43
+ ],
44
+ default: 'computer',
45
+ },
46
+ ...computerOperations,
47
+ ...computerFields,
48
+ ],
49
+ };
50
+ }
@@ -0,0 +1,5 @@
1
+ <svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="60" height="60" rx="4" fill="#005C84"/>
3
+ <path d="M15 20 H45 V25 H20 V35 H40 V40 H20 V50 H15 Z" fill="white"/>
4
+ <path d="M30 10 L35 15 L30 20 Z" fill="#FFD700"/>
5
+ </svg>
@@ -0,0 +1,43 @@
1
+ import { INodeProperties } from 'n8n-workflow';
2
+
3
+ export const computerCreate = {
4
+ routing: {
5
+ request: {
6
+ method: 'POST' as const,
7
+ url: '/Computer',
8
+ },
9
+ },
10
+ properties: [
11
+ {
12
+ displayName: 'API Endpoint',
13
+ name: 'noticeCreate',
14
+ type: 'notice',
15
+ default: 'POST /api.php/v2.1/Computer',
16
+ displayOptions: {
17
+ show: {
18
+ resource: ['computer'],
19
+ operation: ['create'],
20
+ },
21
+ },
22
+ } as INodeProperties,
23
+ {
24
+ displayName: 'Name',
25
+ name: 'name',
26
+ type: 'string',
27
+ default: '',
28
+ required: true,
29
+ displayOptions: {
30
+ show: {
31
+ resource: ['computer'],
32
+ operation: ['create'],
33
+ },
34
+ },
35
+ routing: {
36
+ send: {
37
+ type: 'body',
38
+ property: 'name',
39
+ },
40
+ },
41
+ } as INodeProperties,
42
+ ],
43
+ };