@perses-dev/tempo-plugin 0.0.0-snapshot-scatter-chart-embed-8efdfab

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 (96) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cjs/index.js +47 -0
  3. package/dist/cjs/model/api-types.js +16 -0
  4. package/dist/cjs/model/index.js +33 -0
  5. package/dist/cjs/model/tempo-client.js +88 -0
  6. package/dist/cjs/model/tempo-selectors.js +46 -0
  7. package/dist/cjs/model/trace-query-model.js +16 -0
  8. package/dist/cjs/plugins/TempoTraceQuery.js +30 -0
  9. package/dist/cjs/plugins/get-trace-data.js +69 -0
  10. package/dist/cjs/plugins/tempo-datasource-types.js +16 -0
  11. package/dist/cjs/plugins/tempo-datasource.js +50 -0
  12. package/dist/cjs/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.js +62 -0
  13. package/dist/cjs/plugins/tempo-trace-query/TempoTraceQuery.js +32 -0
  14. package/dist/cjs/plugins/tempo-trace-query/TempoTraceQueryEditor.js +55 -0
  15. package/dist/cjs/plugins/tempo-trace-query/TraceQLEditor.js +46 -0
  16. package/dist/cjs/plugins/tempo-trace-query/get-trace-data.js +101 -0
  17. package/dist/cjs/plugins/tempo-trace-query/query-editor-model.js +53 -0
  18. package/dist/cjs/test/index.js +30 -0
  19. package/dist/cjs/test/mock-data.js +887 -0
  20. package/dist/cjs/test/setup-tests.js +17 -0
  21. package/dist/index.d.ts +6 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +21 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/model/api-types.d.ts +72 -0
  26. package/dist/model/api-types.d.ts.map +1 -0
  27. package/dist/model/api-types.js +15 -0
  28. package/dist/model/api-types.js.map +1 -0
  29. package/dist/model/index.js +18 -0
  30. package/dist/model/index.js.map +1 -0
  31. package/dist/model/tempo-client.d.ts +36 -0
  32. package/dist/model/tempo-client.d.ts.map +1 -0
  33. package/dist/model/tempo-client.js +80 -0
  34. package/dist/model/tempo-client.js.map +1 -0
  35. package/dist/model/tempo-selectors.d.ts +21 -0
  36. package/dist/model/tempo-selectors.d.ts.map +1 -0
  37. package/dist/model/tempo-selectors.js +30 -0
  38. package/dist/model/tempo-selectors.js.map +1 -0
  39. package/dist/model/trace-query-model.d.ts +9 -0
  40. package/dist/model/trace-query-model.d.ts.map +1 -0
  41. package/dist/model/trace-query-model.js +15 -0
  42. package/dist/model/trace-query-model.js.map +1 -0
  43. package/dist/plugins/TempoTraceQuery.d.ts +11 -0
  44. package/dist/plugins/TempoTraceQuery.d.ts.map +1 -0
  45. package/dist/plugins/TempoTraceQuery.js +24 -0
  46. package/dist/plugins/TempoTraceQuery.js.map +1 -0
  47. package/dist/plugins/get-trace-data.d.ts +4 -0
  48. package/dist/plugins/get-trace-data.d.ts.map +1 -0
  49. package/dist/plugins/get-trace-data.js +61 -0
  50. package/dist/plugins/get-trace-data.js.map +1 -0
  51. package/dist/plugins/tempo-datasource-types.d.ts +6 -0
  52. package/dist/plugins/tempo-datasource-types.d.ts.map +1 -0
  53. package/dist/plugins/tempo-datasource-types.js +15 -0
  54. package/dist/plugins/tempo-datasource-types.js.map +1 -0
  55. package/dist/plugins/tempo-datasource.d.ts +5 -0
  56. package/dist/plugins/tempo-datasource.d.ts.map +1 -0
  57. package/dist/plugins/tempo-datasource.js +42 -0
  58. package/dist/plugins/tempo-datasource.js.map +1 -0
  59. package/dist/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.d.ts +14 -0
  60. package/dist/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.d.ts.map +1 -0
  61. package/dist/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.js +54 -0
  62. package/dist/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.js.map +1 -0
  63. package/dist/plugins/tempo-trace-query/TempoTraceQuery.d.ts +13 -0
  64. package/dist/plugins/tempo-trace-query/TempoTraceQuery.d.ts.map +1 -0
  65. package/dist/plugins/tempo-trace-query/TempoTraceQuery.js +26 -0
  66. package/dist/plugins/tempo-trace-query/TempoTraceQuery.js.map +1 -0
  67. package/dist/plugins/tempo-trace-query/TempoTraceQueryEditor.d.ts +4 -0
  68. package/dist/plugins/tempo-trace-query/TempoTraceQueryEditor.d.ts.map +1 -0
  69. package/dist/plugins/tempo-trace-query/TempoTraceQueryEditor.js +47 -0
  70. package/dist/plugins/tempo-trace-query/TempoTraceQueryEditor.js.map +1 -0
  71. package/dist/plugins/tempo-trace-query/TraceQLEditor.d.ts +5 -0
  72. package/dist/plugins/tempo-trace-query/TraceQLEditor.d.ts.map +1 -0
  73. package/dist/plugins/tempo-trace-query/TraceQLEditor.js +33 -0
  74. package/dist/plugins/tempo-trace-query/TraceQLEditor.js.map +1 -0
  75. package/dist/plugins/tempo-trace-query/get-trace-data.d.ts +9 -0
  76. package/dist/plugins/tempo-trace-query/get-trace-data.d.ts.map +1 -0
  77. package/dist/plugins/tempo-trace-query/get-trace-data.js +85 -0
  78. package/dist/plugins/tempo-trace-query/get-trace-data.js.map +1 -0
  79. package/dist/plugins/tempo-trace-query/query-editor-model.d.ts +15 -0
  80. package/dist/plugins/tempo-trace-query/query-editor-model.d.ts.map +1 -0
  81. package/dist/plugins/tempo-trace-query/query-editor-model.js +50 -0
  82. package/dist/plugins/tempo-trace-query/query-editor-model.js.map +1 -0
  83. package/dist/test/index.d.ts +2 -0
  84. package/dist/test/index.d.ts.map +1 -0
  85. package/dist/test/index.js +15 -0
  86. package/dist/test/index.js.map +1 -0
  87. package/dist/test/mock-data.d.ts +5 -0
  88. package/dist/test/mock-data.d.ts.map +1 -0
  89. package/dist/test/mock-data.js +871 -0
  90. package/dist/test/mock-data.js.map +1 -0
  91. package/dist/test/setup-tests.d.ts +2 -0
  92. package/dist/test/setup-tests.d.ts.map +1 -0
  93. package/dist/test/setup-tests.js +15 -0
  94. package/dist/test/setup-tests.js.map +1 -0
  95. package/package.json +43 -0
  96. package/plugin.json +26 -0
@@ -0,0 +1,62 @@
1
+ // Copyright 2023 The Perses Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ "use strict";
14
+ Object.defineProperty(exports, "__esModule", {
15
+ value: true
16
+ });
17
+ Object.defineProperty(exports, "DashboardTempoTraceQueryEditor", {
18
+ enumerable: true,
19
+ get: function() {
20
+ return DashboardTempoTraceQueryEditor;
21
+ }
22
+ });
23
+ const _jsxruntime = require("react/jsx-runtime");
24
+ const _material = require("@mui/material");
25
+ const _pluginsystem = require("@perses-dev/plugin-system");
26
+ const _temposelectors = require("../../model/tempo-selectors");
27
+ const _TraceQLEditor = require("./TraceQLEditor");
28
+ function DashboardTempoTraceQueryEditor(props) {
29
+ const { selectedDatasource , handleDatasourceChange , datasourceURL , query , handleQueryChange , handleQueryBlur } = props;
30
+ return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
31
+ spacing: 2,
32
+ children: [
33
+ /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.FormControl, {
34
+ margin: "dense",
35
+ fullWidth: false,
36
+ children: [
37
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.InputLabel, {
38
+ id: "tempo-datasource-label",
39
+ children: "Tempo Datasource"
40
+ }),
41
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_pluginsystem.DatasourceSelect, {
42
+ datasourcePluginKind: _temposelectors.TEMPO_DATASOURCE_KIND,
43
+ value: selectedDatasource,
44
+ onChange: handleDatasourceChange,
45
+ labelId: "tempo-datasource-label",
46
+ label: "Tempo Datasource"
47
+ })
48
+ ]
49
+ }),
50
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_TraceQLEditor.TraceQLEditor, {
51
+ completeConfig: {
52
+ remote: {
53
+ url: datasourceURL
54
+ }
55
+ },
56
+ value: query,
57
+ onChange: handleQueryChange,
58
+ onBlur: handleQueryBlur
59
+ })
60
+ ]
61
+ });
62
+ }
@@ -0,0 +1,32 @@
1
+ // Copyright 2023 The Perses Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ "use strict";
14
+ Object.defineProperty(exports, "__esModule", {
15
+ value: true
16
+ });
17
+ Object.defineProperty(exports, "TempoTraceQuery", {
18
+ enumerable: true,
19
+ get: function() {
20
+ return TempoTraceQuery;
21
+ }
22
+ });
23
+ const _gettracedata = require("./get-trace-data");
24
+ const _TempoTraceQueryEditor = require("./TempoTraceQueryEditor");
25
+ const TempoTraceQuery = {
26
+ getTraceData: _gettracedata.getTraceData,
27
+ OptionsEditorComponent: _TempoTraceQueryEditor.TempoTraceQueryEditor,
28
+ createInitialOptions: ()=>({
29
+ query: '{}',
30
+ datasource: undefined
31
+ })
32
+ };
@@ -0,0 +1,55 @@
1
+ // Copyright 2023 The Perses Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ "use strict";
14
+ Object.defineProperty(exports, "__esModule", {
15
+ value: true
16
+ });
17
+ Object.defineProperty(exports, "TempoTraceQueryEditor", {
18
+ enumerable: true,
19
+ get: function() {
20
+ return TempoTraceQueryEditor;
21
+ }
22
+ });
23
+ const _jsxruntime = require("react/jsx-runtime");
24
+ const _pluginsystem = require("@perses-dev/plugin-system");
25
+ const _immer = require("immer");
26
+ const _temposelectors = require("../../model/tempo-selectors");
27
+ const _queryeditormodel = require("./query-editor-model");
28
+ const _DashboardTempoTraceQueryEditor = require("./DashboardTempoTraceQueryEditor");
29
+ function TempoTraceQueryEditor(props) {
30
+ const { onChange , value } = props;
31
+ const { datasource } = value;
32
+ const selectedDatasource = datasource !== null && datasource !== void 0 ? datasource : _temposelectors.DEFAULT_TEMPO;
33
+ const { data: client } = (0, _pluginsystem.useDatasourceClient)(selectedDatasource);
34
+ const datasourceURL = client === null || client === void 0 ? void 0 : client.options.datasourceUrl;
35
+ const { query , handleQueryChange , handleQueryBlur } = (0, _queryeditormodel.useQueryState)(props);
36
+ const handleDatasourceChange = (next)=>{
37
+ if ((0, _temposelectors.isTempoDatasourceSelector)(next)) {
38
+ onChange((0, _immer.produce)(value, (draft)=>{
39
+ // If they're using the default, just omit the datasource prop (i.e. set to undefined)
40
+ const nextDatasource = (0, _temposelectors.isDefaultTempoSelector)(next) ? undefined : next;
41
+ draft.datasource = nextDatasource;
42
+ }));
43
+ return;
44
+ }
45
+ throw new Error('Got unexpected non-Tempo datasource selector');
46
+ };
47
+ return /*#__PURE__*/ (0, _jsxruntime.jsx)(_DashboardTempoTraceQueryEditor.DashboardTempoTraceQueryEditor, {
48
+ selectedDatasource: selectedDatasource,
49
+ handleDatasourceChange: handleDatasourceChange,
50
+ datasourceURL: datasourceURL,
51
+ query: query,
52
+ handleQueryChange: handleQueryChange,
53
+ handleQueryBlur: handleQueryBlur
54
+ });
55
+ }
@@ -0,0 +1,46 @@
1
+ // Copyright 2023 The Perses Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ "use strict";
14
+ Object.defineProperty(exports, "__esModule", {
15
+ value: true
16
+ });
17
+ Object.defineProperty(exports, "TraceQLEditor", {
18
+ enumerable: true,
19
+ get: function() {
20
+ return TraceQLEditor;
21
+ }
22
+ });
23
+ const _jsxruntime = require("react/jsx-runtime");
24
+ const _material = require("@mui/material");
25
+ const _reactcodemirror = /*#__PURE__*/ _interop_require_default(require("@uiw/react-codemirror"));
26
+ function _interop_require_default(obj) {
27
+ return obj && obj.__esModule ? obj : {
28
+ default: obj
29
+ };
30
+ }
31
+ function TraceQLEditor({ ...rest }) {
32
+ const theme = (0, _material.useTheme)();
33
+ const isDarkMode = theme.palette.mode === 'dark';
34
+ return /*#__PURE__*/ (0, _jsxruntime.jsx)(_reactcodemirror.default, {
35
+ ...rest,
36
+ style: {
37
+ border: `1px solid ${theme.palette.divider}`
38
+ },
39
+ theme: isDarkMode ? 'dark' : 'light',
40
+ basicSetup: {
41
+ highlightActiveLine: false,
42
+ highlightActiveLineGutter: false,
43
+ foldGutter: false
44
+ }
45
+ });
46
+ }
@@ -0,0 +1,101 @@
1
+ // Copyright 2023 The Perses Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ "use strict";
14
+ Object.defineProperty(exports, "__esModule", {
15
+ value: true
16
+ });
17
+ function _export(target, all) {
18
+ for(var name in all)Object.defineProperty(target, name, {
19
+ enumerable: true,
20
+ get: all[name]
21
+ });
22
+ }
23
+ _export(exports, {
24
+ getUnixTimeRange: function() {
25
+ return getUnixTimeRange;
26
+ },
27
+ getTraceData: function() {
28
+ return getTraceData;
29
+ }
30
+ });
31
+ const _datefns = require("date-fns");
32
+ const _temposelectors = require("../../model/tempo-selectors");
33
+ function getUnixTimeRange(timeRange) {
34
+ const { start , end } = timeRange;
35
+ return {
36
+ start: Math.ceil((0, _datefns.getUnixTime)(start)),
37
+ end: Math.ceil((0, _datefns.getUnixTime)(end))
38
+ };
39
+ }
40
+ const getTraceData = async (spec, context)=>{
41
+ var _client_options;
42
+ if (spec.query === undefined || spec.query === null || spec.query === '') {
43
+ // Do not make a request to the backend, instead return an empty TraceData
44
+ console.error('TempoTraceQuery is undefined, null, or an empty string.');
45
+ return {
46
+ traces: []
47
+ };
48
+ }
49
+ const defaultTempoDatasource = {
50
+ kind: _temposelectors.TEMPO_DATASOURCE_KIND
51
+ };
52
+ var _spec_datasource;
53
+ const client = await context.datasourceStore.getDatasourceClient((_spec_datasource = spec.datasource) !== null && _spec_datasource !== void 0 ? _spec_datasource : defaultTempoDatasource);
54
+ const datasourceUrl = client === null || client === void 0 ? void 0 : (_client_options = client.options) === null || _client_options === void 0 ? void 0 : _client_options.datasourceUrl;
55
+ if (datasourceUrl === undefined || datasourceUrl === null || datasourceUrl === '') {
56
+ console.error('TempoDatasource is undefined, null, or an empty string.');
57
+ return {
58
+ traces: []
59
+ };
60
+ }
61
+ const getQuery = ()=>{
62
+ // if time range not defined -- only return the query from the spec
63
+ if (context.absoluteTimeRange === undefined) {
64
+ return spec.query;
65
+ }
66
+ // if the query already contains a time range (i.e.start and end times)
67
+ if (spec.query.includes('start=') || spec.query.includes('end=')) {
68
+ return spec.query;
69
+ }
70
+ // handle time range selection from UI drop down (e.g. last 5 minutes, last 1 hour )
71
+ const { start , end } = getUnixTimeRange(context === null || context === void 0 ? void 0 : context.absoluteTimeRange);
72
+ const queryStartTime = '&start=' + start;
73
+ const queryEndTime = '&end=' + end;
74
+ const queryWithTimeRange = encodeURI(spec.query) + queryStartTime + queryEndTime;
75
+ return queryWithTimeRange;
76
+ };
77
+ const enrichedTraceResponse = await client.getEnrichedTraceQuery(getQuery(), datasourceUrl);
78
+ const traces = enrichedTraceResponse.traces.map((traceValue)=>{
79
+ const startTimeUnixMs = parseInt(traceValue.summary.startTimeUnixNano) * 1e-6; // convert to millisecond for eChart time format
80
+ const durationMs = traceValue.summary.durationMs;
81
+ const spanCount = traceValue.spanCount;
82
+ const errorCount = traceValue.errorCount;
83
+ const traceId = traceValue.summary.traceID;
84
+ const name = `rootServiceName="${traceValue.summary.rootServiceName.trim()}", rootTraceName="${traceValue.summary.rootServiceName.trim()}"`;
85
+ return {
86
+ startTimeUnixMs,
87
+ durationMs,
88
+ spanCount,
89
+ errorCount,
90
+ traceId,
91
+ name
92
+ };
93
+ });
94
+ const traceData = {
95
+ traces,
96
+ metadata: {
97
+ executedQueryString: spec.query
98
+ }
99
+ };
100
+ return traceData;
101
+ };
@@ -0,0 +1,53 @@
1
+ // Copyright 2023 The Perses Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ "use strict";
14
+ Object.defineProperty(exports, "__esModule", {
15
+ value: true
16
+ });
17
+ Object.defineProperty(exports, "useQueryState", {
18
+ enumerable: true,
19
+ get: function() {
20
+ return useQueryState;
21
+ }
22
+ });
23
+ const _react = require("react");
24
+ const _immer = require("immer");
25
+ function useQueryState(props) {
26
+ const { onChange , value } = props;
27
+ // Local copy of the query's value
28
+ const [query, setQuery] = (0, _react.useState)(value.query);
29
+ // This is basically "getDerivedStateFromProps" to make sure if spec's value changes external to this component,
30
+ // we render with the latest value
31
+ const [lastSyncedQuery, setLastSyncedQuery] = (0, _react.useState)(value.query);
32
+ if (value.query !== lastSyncedQuery) {
33
+ setQuery(value.query);
34
+ setLastSyncedQuery(value.query);
35
+ }
36
+ // Update our local state's copy as the user types
37
+ const handleQueryChange = (e)=>{
38
+ setQuery(e);
39
+ };
40
+ // Propagate changes to the query's value when the input is blurred to avoid constantly re-running queries in the
41
+ // PanelPreview
42
+ const handleQueryBlur = ()=>{
43
+ setLastSyncedQuery(query);
44
+ onChange((0, _immer.produce)(value, (draft)=>{
45
+ draft.query = query;
46
+ }));
47
+ };
48
+ return {
49
+ query,
50
+ handleQueryChange,
51
+ handleQueryBlur
52
+ };
53
+ }
@@ -0,0 +1,30 @@
1
+ // Copyright 2023 The Perses Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ "use strict";
14
+ Object.defineProperty(exports, "__esModule", {
15
+ value: true
16
+ });
17
+ _export_star(require("./mock-data"), exports);
18
+ function _export_star(from, to) {
19
+ Object.keys(from).forEach(function(k) {
20
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
21
+ Object.defineProperty(to, k, {
22
+ enumerable: true,
23
+ get: function() {
24
+ return from[k];
25
+ }
26
+ });
27
+ }
28
+ });
29
+ return from;
30
+ }