@truedat/qx 7.13.8 → 7.14.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 (46) hide show
  1. package/package.json +3 -3
  2. package/src/components/common/ClauseViewer.js +183 -21
  3. package/src/components/common/expressions/Condition.js +13 -6
  4. package/src/components/dataViews/DataViewEditor.js +0 -2
  5. package/src/components/dataViews/DataViewSummary.js +73 -0
  6. package/src/components/dataViews/__tests__/AdvancedDataViewEditor.spec.js +4 -1
  7. package/src/components/dataViews/__tests__/DataViewEditor.spec.js +167 -132
  8. package/src/components/dataViews/__tests__/DataViewSummary.spec.js +820 -0
  9. package/src/components/dataViews/__tests__/DataViews.spec.js +57 -17
  10. package/src/components/dataViews/__tests__/SimpleDataViewEditor.spec.js +140 -141
  11. package/src/components/dataViews/__tests__/__snapshots__/AdvancedDataViewEditor.spec.js.snap +963 -759
  12. package/src/components/dataViews/__tests__/__snapshots__/DataViewSelect.spec.js.snap +17 -13
  13. package/src/components/dataViews/__tests__/__snapshots__/DataViewSummary.spec.js.snap +1786 -0
  14. package/src/components/dataViews/__tests__/__snapshots__/Queryable.spec.js.snap +18 -14
  15. package/src/components/dataViews/__tests__/__snapshots__/Queryables.spec.js.snap +18 -14
  16. package/src/components/dataViews/advancedForm/AdvancedDataViewEditor.js +59 -48
  17. package/src/components/dataViews/queryableProperties/Join.js +2 -1
  18. package/src/components/dataViews/queryableProperties/Select.js +22 -30
  19. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Join.spec.js.snap +1 -1
  20. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Select.spec.js.snap +37 -25
  21. package/src/components/dataViews/queryableSummaryHelpers.js +101 -0
  22. package/src/components/dataViews/simpleForm/SimpleDataViewEditor.js +9 -4
  23. package/src/components/dataViews/summary/From.js +45 -0
  24. package/src/components/dataViews/summary/GroupBy.js +82 -0
  25. package/src/components/dataViews/summary/Join.js +60 -0
  26. package/src/components/dataViews/summary/Select.js +31 -0
  27. package/src/components/dataViews/summary/Where.js +37 -0
  28. package/src/components/qualityControls/ControlPropertiesView.js +115 -63
  29. package/src/components/qualityControls/EditQualityControl.js +5 -3
  30. package/src/components/qualityControls/NewDraftQualityControl.js +8 -3
  31. package/src/components/qualityControls/NewQualityControl.js +5 -3
  32. package/src/components/qualityControls/QualityControlCrumbs.js +46 -5
  33. package/src/components/qualityControls/QualityControlRoutes.js +3 -1
  34. package/src/components/qualityControls/QualityControls.js +9 -18
  35. package/src/components/qualityControls/QualityControlsLabelResults.js +2 -2
  36. package/src/components/qualityControls/__tests__/__snapshots__/ControlPropertiesView.spec.js.snap +12 -9
  37. package/src/components/qualityControls/__tests__/__snapshots__/EditQualityControl.spec.js.snap +536 -493
  38. package/src/components/qualityControls/__tests__/__snapshots__/NewDraftQualityControl.spec.js.snap +510 -483
  39. package/src/components/qualityControls/__tests__/__snapshots__/NewQualityControl.spec.js.snap +261 -245
  40. package/src/components/qualityControls/__tests__/__snapshots__/QualityControl.spec.js.snap +11 -8
  41. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlCrumbs.spec.js.snap +1 -1
  42. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlHeader.spec.js.snap +1 -1
  43. package/src/components/qualityControls/__tests__/__snapshots__/QualityControls.spec.js.snap +87 -87
  44. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlsLabelResults.spec.js.snap +6 -2
  45. package/src/hooks/useDataViews.js +1 -1
  46. package/src/styles/Expression.less +25 -1
@@ -556,7 +556,7 @@ exports[`<Queryable /> matches the latest snapshot with join queryable 1`] = `
556
556
  </label>
557
557
  <div
558
558
  aria-expanded="false"
559
- class="ui selection dropdown select-field-dropdown"
559
+ class="ui selection dropdown select-field-dropdown join-type-selector"
560
560
  role="listbox"
561
561
  tabindex="0"
562
562
  >
@@ -787,21 +787,25 @@ exports[`<Queryable /> matches the latest snapshot with select queryable 1`] = `
787
787
  role="list"
788
788
  >
789
789
  <div
790
- class="item"
791
- role="listitem"
790
+ class="field"
792
791
  >
793
- <button
794
- class="ui button"
795
- >
796
- add_select_field
797
- </button>
798
- <button
799
- class="ui disabled button"
800
- disabled=""
801
- tabindex="-1"
792
+ <div
793
+ class="item"
794
+ role="listitem"
802
795
  >
803
- add_all_select_fields
804
- </button>
796
+ <button
797
+ class="ui button"
798
+ >
799
+ add_select_field
800
+ </button>
801
+ <button
802
+ class="ui disabled button"
803
+ disabled=""
804
+ tabindex="-1"
805
+ >
806
+ add_all_select_fields
807
+ </button>
808
+ </div>
805
809
  </div>
806
810
  </div>
807
811
  </div>
@@ -402,7 +402,7 @@ exports[`<Queryables /> matches the latest snapshot with join queryable 1`] = `
402
402
  </label>
403
403
  <div
404
404
  aria-expanded="false"
405
- class="ui selection dropdown select-field-dropdown"
405
+ class="ui selection dropdown select-field-dropdown join-type-selector"
406
406
  role="listbox"
407
407
  tabindex="0"
408
408
  >
@@ -672,21 +672,25 @@ exports[`<Queryables /> matches the latest snapshot with select queryable 1`] =
672
672
  role="list"
673
673
  >
674
674
  <div
675
- class="item"
676
- role="listitem"
675
+ class="field"
677
676
  >
678
- <button
679
- class="ui button"
680
- >
681
- queryables.select.form.add_select_field
682
- </button>
683
- <button
684
- class="ui disabled button"
685
- disabled=""
686
- tabindex="-1"
677
+ <div
678
+ class="item"
679
+ role="listitem"
687
680
  >
688
- queryables.select.form.add_all_select_fields
689
- </button>
681
+ <button
682
+ class="ui button"
683
+ >
684
+ queryables.select.form.add_select_field
685
+ </button>
686
+ <button
687
+ class="ui disabled button"
688
+ disabled=""
689
+ tabindex="-1"
690
+ >
691
+ queryables.select.form.add_all_select_fields
692
+ </button>
693
+ </div>
690
694
  </div>
691
695
  </div>
692
696
  </div>
@@ -14,6 +14,7 @@ import { FormProvider, useForm, Controller } from "react-hook-form";
14
14
  import QxContext from "@truedat/qx/components/QxContext";
15
15
  import DataViewSelect from "./DataViewSelect";
16
16
  import Queryables from "./Queryables";
17
+ import DataViewSummary from "../DataViewSummary";
17
18
 
18
19
  const SourceSelector = lazy(
19
20
  () => import("@truedat/cx/sources/components/SourceSelector")
@@ -37,18 +38,21 @@ export default function AdvancedDataViewEditor({
37
38
 
38
39
  const isEditForm = !_.isNil(selectedDataView?.id);
39
40
  const sourceId = watch("source_id");
41
+ const currentDataView = watch();
42
+
40
43
  if (!selectedDataView) return null;
41
44
 
42
45
  return (
43
46
  <Fragment>
44
- <FormProvider {...form}>
45
- <Form>
46
- <Header as="h3" dividing>
47
- {watch("name") || formatMessage({ id: "dataViews.action.new" })}
48
- </Header>
49
- <Grid>
50
- <Grid.Column>
51
- <Grid.Row className="vertical-space">
47
+ <Grid>
48
+ <Grid.Column width="11">
49
+ <FormProvider {...form}>
50
+ <Form>
51
+ <Header as="h3" dividing>
52
+ {watch("name") || formatMessage({ id: "dataViews.action.new" })}
53
+ </Header>
54
+
55
+ <Grid.Row>
52
56
  <Controller
53
57
  control={control}
54
58
  name="name"
@@ -78,7 +82,7 @@ export default function AdvancedDataViewEditor({
78
82
  )}
79
83
  />
80
84
  </Grid.Row>
81
- <Grid.Row className="vertical-space">
85
+ <Grid.Row>
82
86
  <Controller
83
87
  control={control}
84
88
  name="description"
@@ -104,47 +108,54 @@ export default function AdvancedDataViewEditor({
104
108
  )}
105
109
  />
106
110
  </Grid.Row>
107
- </Grid.Column>
108
- </Grid>
109
- <Controller
110
- control={control}
111
- name="source_id"
112
- rules={{ required: true }}
113
- render={({ field: { onChange, value } }) => (
114
- <Form.Field required>
115
- <label>{formatMessage({ id: "dataViews.form.source" })}</label>
116
- <SourceSelector
117
- disabled={isEditForm}
118
- value={value + ""}
119
- onChange={(_e, { value }) => onChange(value)}
111
+ <Controller
112
+ control={control}
113
+ name="source_id"
114
+ rules={{ required: true }}
115
+ render={({ field: { onChange, value } }) => (
116
+ <Form.Field required>
117
+ <label>
118
+ {formatMessage({ id: "dataViews.form.source" })}
119
+ </label>
120
+ <SourceSelector
121
+ disabled={isEditForm}
122
+ value={value + ""}
123
+ onChange={(_e, { value }) => onChange(value)}
124
+ />
125
+ </Form.Field>
126
+ )}
127
+ />
128
+ {!_.isNil(sourceId) ? (
129
+ <QxContext value={{ ...context, sourceId }}>
130
+ <Queryables />
131
+ <DataViewSelect />
132
+ </QxContext>
133
+ ) : null}
134
+ <Divider hidden />
135
+ <Container textAlign="right">
136
+ <Button
137
+ content={formatMessage({ id: "actions.cancel" })}
138
+ disabled={isSubmitting}
139
+ onClick={onCancel}
120
140
  />
121
- </Form.Field>
122
- )}
123
- />
124
- {!_.isNil(sourceId) ? (
125
- <QxContext value={{ ...context, sourceId }}>
126
- <Queryables />
127
- <DataViewSelect />
128
- </QxContext>
129
- ) : null}
130
- <Divider hidden />
131
- <Container textAlign="right">
132
- <Button
133
- content={formatMessage({ id: "actions.cancel" })}
134
- disabled={isSubmitting}
135
- onClick={onCancel}
136
- />
137
141
 
138
- <Button
139
- onClick={handleSubmit((data) => onSubmit({ ...data, mode }))}
140
- primary
141
- loading={isSubmitting}
142
- disabled={!isValid || !isDirty}
143
- content={formatMessage({ id: "actions.save" })}
144
- />
145
- </Container>
146
- </Form>
147
- </FormProvider>
142
+ <Button
143
+ onClick={handleSubmit((data) => onSubmit({ ...data, mode }))}
144
+ primary
145
+ loading={isSubmitting}
146
+ disabled={!isValid || !isDirty}
147
+ content={formatMessage({ id: "actions.save" })}
148
+ />
149
+ </Container>
150
+ </Form>
151
+ </FormProvider>
152
+ </Grid.Column>
153
+ {!_.isNil(sourceId) && (
154
+ <Grid.Column width="5">
155
+ <DataViewSummary dataView={currentDataView} />
156
+ </Grid.Column>
157
+ )}
158
+ </Grid>
148
159
  </Fragment>
149
160
  );
150
161
  }
@@ -7,6 +7,7 @@ import { Dropdown, Form } from "semantic-ui-react";
7
7
  import ResourceSelector from "@truedat/qx/components/common/ResourceSelector";
8
8
  import QxContext from "@truedat/qx/components/QxContext";
9
9
  import JoinTypeIcon from "./JoinTypeIcon";
10
+ import "@truedat/qx/styles/Expression.less";
10
11
 
11
12
  export default function Join() {
12
13
  const { formatMessage } = useIntl();
@@ -38,7 +39,7 @@ export default function Join() {
38
39
  {formatMessage({ id: "queryables.form.join.type" })}
39
40
  </label>
40
41
  <Dropdown
41
- className="select-field-dropdown"
42
+ className="select-field-dropdown join-type-selector"
42
43
  selection
43
44
  value={value}
44
45
  options={joinTypeOptions}
@@ -2,7 +2,7 @@ import _ from "lodash/fp";
2
2
  import { use } from "react";
3
3
  import { useIntl } from "react-intl";
4
4
  import { useFieldArray, useFormState } from "react-hook-form";
5
- import { Button, List, Popup } from "semantic-ui-react";
5
+ import { Button, Form, Label, List } from "semantic-ui-react";
6
6
  import QxContext from "@truedat/qx/components/QxContext";
7
7
  import SelectField, { newSelectField } from "./SelectField";
8
8
 
@@ -29,12 +29,6 @@ export default function Select() {
29
29
  )(ctxFields);
30
30
  const maxId = _.flow(_.map("id"), _.max)(fields) || 0;
31
31
 
32
- const addFieldButton = (
33
- <Button onClick={() => append(newSelectField(maxId + 1))}>
34
- {formatMessage({ id: "queryables.select.form.add_select_field" })}
35
- </Button>
36
- );
37
-
38
32
  return (
39
33
  <List>
40
34
  {fields.map((selectField, index) => (
@@ -46,30 +40,28 @@ export default function Select() {
46
40
  </QxContext>
47
41
  </List.Item>
48
42
  ))}
49
- <List.Item>
50
- {requiredError ? (
51
- <Popup
52
- className="error-popup"
53
- content={requiredError}
54
- open
55
- pinned
56
- position="bottom center"
57
- trigger={addFieldButton}
58
- />
59
- ) : (
60
- addFieldButton
61
- )}
62
- {_.size(fields) == 0 ? (
63
- <Button
64
- onClick={() => append(allFields)}
65
- disabled={_.isEmpty(allFields)}
66
- >
67
- {formatMessage({
68
- id: "queryables.select.form.add_all_select_fields",
69
- })}
43
+ <Form.Field error={!!requiredError}>
44
+ <List.Item>
45
+ <Button onClick={() => append(newSelectField(maxId + 1))}>
46
+ {formatMessage({ id: "queryables.select.form.add_select_field" })}
70
47
  </Button>
71
- ) : null}
72
- </List.Item>
48
+ {_.size(fields) == 0 ? (
49
+ <Button
50
+ onClick={() => append(allFields)}
51
+ disabled={_.isEmpty(allFields)}
52
+ >
53
+ {formatMessage({
54
+ id: "queryables.select.form.add_all_select_fields",
55
+ })}
56
+ </Button>
57
+ ) : null}
58
+ </List.Item>
59
+ {requiredError && (
60
+ <List.Item>
61
+ <Label content={requiredError} prompt pointing="above" />
62
+ </List.Item>
63
+ )}
64
+ </Form.Field>
73
65
  </List>
74
66
  );
75
67
  }
@@ -60,7 +60,7 @@ exports[`<Join /> matches the latest snapshot 1`] = `
60
60
  </label>
61
61
  <div
62
62
  aria-expanded="false"
63
- class="ui selection dropdown select-field-dropdown"
63
+ class="ui selection dropdown select-field-dropdown join-type-selector"
64
64
  role="listbox"
65
65
  tabindex="0"
66
66
  >
@@ -191,14 +191,18 @@ exports[`<Select /> handles add all fields 1`] = `
191
191
  </div>
192
192
  </div>
193
193
  <div
194
- class="item"
195
- role="listitem"
194
+ class="field"
196
195
  >
197
- <button
198
- class="ui button"
196
+ <div
197
+ class="item"
198
+ role="listitem"
199
199
  >
200
- queryables.select.form.add_select_field
201
- </button>
200
+ <button
201
+ class="ui button"
202
+ >
203
+ queryables.select.form.add_select_field
204
+ </button>
205
+ </div>
202
206
  </div>
203
207
  </div>
204
208
  </div>
@@ -211,21 +215,25 @@ exports[`<Select /> matches the latest snapshot 1`] = `
211
215
  role="list"
212
216
  >
213
217
  <div
214
- class="item"
215
- role="listitem"
218
+ class="field"
216
219
  >
217
- <button
218
- class="ui button"
219
- >
220
- queryables.select.form.add_select_field
221
- </button>
222
- <button
223
- class="ui disabled button"
224
- disabled=""
225
- tabindex="-1"
220
+ <div
221
+ class="item"
222
+ role="listitem"
226
223
  >
227
- queryables.select.form.add_all_select_fields
228
- </button>
224
+ <button
225
+ class="ui button"
226
+ >
227
+ queryables.select.form.add_select_field
228
+ </button>
229
+ <button
230
+ class="ui disabled button"
231
+ disabled=""
232
+ tabindex="-1"
233
+ >
234
+ queryables.select.form.add_all_select_fields
235
+ </button>
236
+ </div>
229
237
  </div>
230
238
  </div>
231
239
  </div>
@@ -583,14 +591,18 @@ exports[`<Select /> matches the latest snapshot with content 1`] = `
583
591
  </div>
584
592
  </div>
585
593
  <div
586
- class="item"
587
- role="listitem"
594
+ class="field"
588
595
  >
589
- <button
590
- class="ui button"
596
+ <div
597
+ class="item"
598
+ role="listitem"
591
599
  >
592
- queryables.select.form.add_select_field
593
- </button>
600
+ <button
601
+ class="ui button"
602
+ >
603
+ queryables.select.form.add_select_field
604
+ </button>
605
+ </div>
594
606
  </div>
595
607
  </div>
596
608
  </div>
@@ -0,0 +1,101 @@
1
+ import _ from "lodash/fp";
2
+
3
+ const labelForQueryable = (queryable) => {
4
+ if (queryable.type === "from")
5
+ return queryable.alias || queryable.properties?.resource?.embedded?.name;
6
+ if (queryable.type === "join")
7
+ return queryable.alias || queryable.properties?.resource?.embedded?.name;
8
+ if (queryable.type === "group_by") return queryable.alias || "Group By";
9
+ return null;
10
+ };
11
+
12
+ export const validResource = (queryable) => {
13
+ return !_.isNil(queryable?.properties?.resource?.id);
14
+ };
15
+
16
+ /**
17
+ * Extracts resource information from a queryable
18
+ */
19
+ export const getResourceInfo = (queryable) => {
20
+ if (!queryable?.properties?.resource?.id) return null;
21
+
22
+ const resource = queryable.properties.resource;
23
+ return {
24
+ id: resource.id,
25
+ type: resource.type,
26
+ name: resource.embedded?.name || resource.name,
27
+ alias: queryable.alias,
28
+ };
29
+ };
30
+
31
+ /**
32
+ * Extracts join information including join type
33
+ */
34
+ export const getJoinInfo = (queryable) => {
35
+ if (!queryable?.properties?.resource?.id) return null;
36
+
37
+ const resource = queryable.properties.resource;
38
+ const joinType = queryable.properties.type || "inner";
39
+
40
+ return {
41
+ id: resource.id,
42
+ type: resource.type,
43
+ name: resource.embedded?.name || resource.name,
44
+ alias: queryable.alias,
45
+ joinType,
46
+ clauses: queryable.properties.clauses,
47
+ };
48
+ };
49
+
50
+ /**
51
+ * Extracts group by fields
52
+ */
53
+ export const getGroupByFields = (queryable) => {
54
+ if (!queryable?.properties?.group_fields) return [];
55
+
56
+ return _.map((field) => ({
57
+ alias: field.alias,
58
+ expression: field.expression,
59
+ }))(queryable.properties.group_fields);
60
+ };
61
+
62
+ /**
63
+ * Extracts aggregate fields
64
+ */
65
+ export const getAggregateFields = (queryable) => {
66
+ if (!queryable?.properties?.aggregate_fields) return [];
67
+
68
+ return _.map((field) => ({
69
+ alias: field.alias,
70
+ expression: field.expression,
71
+ }))(queryable.properties.aggregate_fields);
72
+ };
73
+
74
+ /**
75
+ * Extracts select fields
76
+ */
77
+ export const getSelectFields = (queryable) => {
78
+ if (!queryable?.properties?.fields) return [];
79
+
80
+ return _.map((field) => ({
81
+ alias: field.alias,
82
+ expression: field.expression,
83
+ }))(queryable.properties.fields);
84
+ };
85
+
86
+ /**
87
+ * Checks if queryables array has valid structure
88
+ */
89
+ export const hasValidStructure = (queryables = []) => {
90
+ return (
91
+ _.size(queryables) > 0 &&
92
+ _.some((q) => q.type === "from" && validResource(q))(queryables)
93
+ );
94
+ };
95
+
96
+ export const buildParentResourceMap = (queryables = []) =>
97
+ _.flow(
98
+ _.filter((q) => ["from", "join", "group_by"].includes(q.type)),
99
+ _.map((q) => [q.id, { ...q, label: labelForQueryable(q) }]),
100
+ _.fromPairs
101
+ )(queryables);
@@ -10,6 +10,7 @@ import DatasetForm from "./DatasetForm";
10
10
  import AggregationForm from "./AggregationForm";
11
11
  import SelectionForm from "./SelectionForm";
12
12
  import CancelButton from "../actions/CancelButton";
13
+ import DataViewSummary from "../DataViewSummary";
13
14
 
14
15
  export default function SimpleDataViewEditor({
15
16
  selectedDataView,
@@ -34,6 +35,7 @@ export default function SimpleDataViewEditor({
34
35
 
35
36
  const isEditForm = !_.isNil(selectedDataView?.id);
36
37
  const sourceId = watch("source_id");
38
+ const currentDataView = watch();
37
39
 
38
40
  const doSubmit = async (data) => {
39
41
  const ok = await trigger();
@@ -49,9 +51,7 @@ export default function SimpleDataViewEditor({
49
51
  const updateStatusState = async (steps) => {
50
52
  const validations = await Promise.all(
51
53
  _.map.convert({ cap: false })(async (step) => {
52
- const ok = await trigger(stepInformation[step]["fieldNames"], {
53
- shouldFocus: true,
54
- });
54
+ const ok = await trigger(stepInformation[step]["fieldNames"]);
55
55
  const content = ok ? { status: "valid" } : { status: "error" };
56
56
  return [step, content];
57
57
  })(steps)
@@ -102,7 +102,7 @@ export default function SimpleDataViewEditor({
102
102
  return (
103
103
  <Fragment>
104
104
  <Grid>
105
- <Grid.Column width="12">
105
+ <Grid.Column width="11">
106
106
  <Grid.Row textAlign="center">
107
107
  <Step.Group>
108
108
  <Step active={activeStep == 0} {...stepProps(0)}>
@@ -250,6 +250,11 @@ export default function SimpleDataViewEditor({
250
250
  </FormProvider>
251
251
  </Grid.Row>
252
252
  </Grid.Column>
253
+ {!_.isNil(sourceId) && (
254
+ <Grid.Column width="5">
255
+ <DataViewSummary dataView={currentDataView} />
256
+ </Grid.Column>
257
+ )}
253
258
  </Grid>
254
259
  </Fragment>
255
260
  );
@@ -0,0 +1,45 @@
1
+ import _ from "lodash/fp";
2
+ import { useIntl } from "react-intl";
3
+ import { List, Icon, Label } from "semantic-ui-react";
4
+ import { getResourceInfo } from "../queryableSummaryHelpers";
5
+ import { getColorById } from "../queryableFunctions";
6
+ import "@truedat/qx/styles/Expression.less";
7
+
8
+ export default function From({ queryable }) {
9
+ const { formatMessage } = useIntl();
10
+ const fromResource = getResourceInfo(queryable);
11
+ const color = getColorById(queryable.id);
12
+
13
+ if (_.isNil(fromResource)) {
14
+ return null;
15
+ }
16
+
17
+ return (
18
+ <List.Item>
19
+ <List.Header style={{ marginBottom: 8 }}>
20
+ <Icon name="database" />
21
+ {formatMessage({ id: "dataViews.form.queryable.from" })}
22
+ </List.Header>
23
+ <List.Content>
24
+ <List.Description className="description-flex-wrap text-break-word">
25
+ <Label horizontal>
26
+ {formatMessage({
27
+ id: `queryables.resource.selector.${fromResource.type}`,
28
+ })}
29
+ </Label>
30
+ <Label color={color} className="text-break-word">
31
+ {fromResource.name}
32
+ </Label>
33
+ {fromResource.alias && (
34
+ <>
35
+ <b style={{ marginRight: 4, marginLeft: 4 }}>as</b>
36
+ <Label color={color} className="text-break-word">
37
+ {fromResource.alias}
38
+ </Label>
39
+ </>
40
+ )}
41
+ </List.Description>
42
+ </List.Content>
43
+ </List.Item>
44
+ );
45
+ }