@finos/legend-application-marketplace 0.2.2 → 0.2.4

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 (137) hide show
  1. package/lib/__lib__/LegendMarketplaceAppEvent.d.ts +3 -0
  2. package/lib/__lib__/LegendMarketplaceAppEvent.d.ts.map +1 -1
  3. package/lib/__lib__/LegendMarketplaceAppEvent.js +3 -0
  4. package/lib/__lib__/LegendMarketplaceAppEvent.js.map +1 -1
  5. package/lib/__lib__/LegendMarketplaceNavigation.d.ts +7 -1
  6. package/lib/__lib__/LegendMarketplaceNavigation.d.ts.map +1 -1
  7. package/lib/__lib__/LegendMarketplaceNavigation.js +9 -1
  8. package/lib/__lib__/LegendMarketplaceNavigation.js.map +1 -1
  9. package/lib/__lib__/LegendMarketplaceTelemetryHelper.d.ts +3 -1
  10. package/lib/__lib__/LegendMarketplaceTelemetryHelper.d.ts.map +1 -1
  11. package/lib/__lib__/LegendMarketplaceTelemetryHelper.js +18 -2
  12. package/lib/__lib__/LegendMarketplaceTelemetryHelper.js.map +1 -1
  13. package/lib/application/LegendMarketplaceWebApplication.d.ts.map +1 -1
  14. package/lib/application/LegendMarketplaceWebApplication.js +4 -1
  15. package/lib/application/LegendMarketplaceWebApplication.js.map +1 -1
  16. package/lib/application/providers/LegendMarketplaceFieldSearchResultsStoreProvider.d.ts +22 -0
  17. package/lib/application/providers/LegendMarketplaceFieldSearchResultsStoreProvider.d.ts.map +1 -0
  18. package/lib/application/providers/LegendMarketplaceFieldSearchResultsStoreProvider.js +37 -0
  19. package/lib/application/providers/LegendMarketplaceFieldSearchResultsStoreProvider.js.map +1 -0
  20. package/lib/components/AddToCart/CartDrawer.d.ts.map +1 -1
  21. package/lib/components/AddToCart/CartDrawer.js +36 -4
  22. package/lib/components/AddToCart/CartDrawer.js.map +1 -1
  23. package/lib/components/AddToCart/RecommendedAddOnsModal.d.ts +2 -1
  24. package/lib/components/AddToCart/RecommendedAddOnsModal.d.ts.map +1 -1
  25. package/lib/components/AddToCart/RecommendedAddOnsModal.js +23 -13
  26. package/lib/components/AddToCart/RecommendedAddOnsModal.js.map +1 -1
  27. package/lib/components/AddToCart/RecommendedItemsCard.d.ts +3 -1
  28. package/lib/components/AddToCart/RecommendedItemsCard.d.ts.map +1 -1
  29. package/lib/components/AddToCart/RecommendedItemsCard.js +14 -11
  30. package/lib/components/AddToCart/RecommendedItemsCard.js.map +1 -1
  31. package/lib/components/FieldSearchFiltersPanel/FieldSearchFiltersPanel.d.ts +23 -0
  32. package/lib/components/FieldSearchFiltersPanel/FieldSearchFiltersPanel.d.ts.map +1 -0
  33. package/lib/components/FieldSearchFiltersPanel/FieldSearchFiltersPanel.js +22 -0
  34. package/lib/components/FieldSearchFiltersPanel/FieldSearchFiltersPanel.js.map +1 -0
  35. package/lib/components/LegendServiceCard/LegendServiceCard.d.ts +2 -0
  36. package/lib/components/LegendServiceCard/LegendServiceCard.d.ts.map +1 -1
  37. package/lib/components/LegendServiceCard/LegendServiceCard.js +12 -8
  38. package/lib/components/LegendServiceCard/LegendServiceCard.js.map +1 -1
  39. package/lib/components/LegendServiceCard/LegendServiceGrid.d.ts +24 -0
  40. package/lib/components/LegendServiceCard/LegendServiceGrid.d.ts.map +1 -0
  41. package/lib/components/LegendServiceCard/LegendServiceGrid.js +124 -0
  42. package/lib/components/LegendServiceCard/LegendServiceGrid.js.map +1 -0
  43. package/lib/components/LegendServiceCard/LegendServiceListRow.d.ts +2 -0
  44. package/lib/components/LegendServiceCard/LegendServiceListRow.d.ts.map +1 -1
  45. package/lib/components/LegendServiceCard/LegendServiceListRow.js +8 -4
  46. package/lib/components/LegendServiceCard/LegendServiceListRow.js.map +1 -1
  47. package/lib/components/MarketplaceCard/FieldSearchResultListItem.d.ts +25 -0
  48. package/lib/components/MarketplaceCard/FieldSearchResultListItem.d.ts.map +1 -0
  49. package/lib/components/MarketplaceCard/FieldSearchResultListItem.js +58 -0
  50. package/lib/components/MarketplaceCard/FieldSearchResultListItem.js.map +1 -0
  51. package/lib/components/MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.d.ts +10 -0
  52. package/lib/components/MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.d.ts.map +1 -1
  53. package/lib/components/MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.js +2 -2
  54. package/lib/components/MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.js.map +1 -1
  55. package/lib/components/Pagination/PaginationControls.d.ts +1 -0
  56. package/lib/components/Pagination/PaginationControls.d.ts.map +1 -1
  57. package/lib/components/Pagination/PaginationControls.js +2 -2
  58. package/lib/components/Pagination/PaginationControls.js.map +1 -1
  59. package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.d.ts.map +1 -1
  60. package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.js +5 -2
  61. package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.js.map +1 -1
  62. package/lib/components/SearchBar/LegendMarketplaceSearchBar.d.ts +2 -1
  63. package/lib/components/SearchBar/LegendMarketplaceSearchBar.d.ts.map +1 -1
  64. package/lib/components/SearchBar/LegendMarketplaceSearchBar.js +20 -7
  65. package/lib/components/SearchBar/LegendMarketplaceSearchBar.js.map +1 -1
  66. package/lib/index.css +2 -2
  67. package/lib/index.css.map +1 -1
  68. package/lib/package.json +1 -1
  69. package/lib/pages/DataAPIs/LegendMarketplaceDataAPIs.d.ts.map +1 -1
  70. package/lib/pages/DataAPIs/LegendMarketplaceDataAPIs.js +17 -11
  71. package/lib/pages/DataAPIs/LegendMarketplaceDataAPIs.js.map +1 -1
  72. package/lib/pages/Lakehouse/MarketplaceLakehouseHome.d.ts.map +1 -1
  73. package/lib/pages/Lakehouse/MarketplaceLakehouseHome.js +10 -5
  74. package/lib/pages/Lakehouse/MarketplaceLakehouseHome.js.map +1 -1
  75. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.d.ts +17 -0
  76. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.d.ts.map +1 -0
  77. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.js +126 -0
  78. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.js.map +1 -0
  79. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.d.ts.map +1 -1
  80. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.js +14 -5
  81. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.js.map +1 -1
  82. package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.d.ts.map +1 -1
  83. package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.js +2 -2
  84. package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.js.map +1 -1
  85. package/lib/stores/cart/CartStore.d.ts +10 -3
  86. package/lib/stores/cart/CartStore.d.ts.map +1 -1
  87. package/lib/stores/cart/CartStore.js +66 -42
  88. package/lib/stores/cart/CartStore.js.map +1 -1
  89. package/lib/stores/dataAPIs/LegendMarketplaceDataAPIsStore.d.ts +8 -1
  90. package/lib/stores/dataAPIs/LegendMarketplaceDataAPIsStore.d.ts.map +1 -1
  91. package/lib/stores/dataAPIs/LegendMarketplaceDataAPIsStore.js +37 -3
  92. package/lib/stores/dataAPIs/LegendMarketplaceDataAPIsStore.js.map +1 -1
  93. package/lib/stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.d.ts +63 -0
  94. package/lib/stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.d.ts.map +1 -0
  95. package/lib/stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.js +228 -0
  96. package/lib/stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.js.map +1 -0
  97. package/lib/stores/lakehouse/LegendMarketplaceProductViewerStore.d.ts.map +1 -1
  98. package/lib/stores/lakehouse/LegendMarketplaceProductViewerStore.js +9 -13
  99. package/lib/stores/lakehouse/LegendMarketplaceProductViewerStore.js.map +1 -1
  100. package/lib/stores/lakehouse/LegendMarketplaceSearchResultsStore.d.ts +2 -0
  101. package/lib/stores/lakehouse/LegendMarketplaceSearchResultsStore.d.ts.map +1 -1
  102. package/lib/stores/lakehouse/LegendMarketplaceSearchResultsStore.js +8 -4
  103. package/lib/stores/lakehouse/LegendMarketplaceSearchResultsStore.js.map +1 -1
  104. package/lib/stores/lakehouse/fieldSearch/FieldSearchResultState.d.ts +40 -0
  105. package/lib/stores/lakehouse/fieldSearch/FieldSearchResultState.d.ts.map +1 -0
  106. package/lib/stores/lakehouse/fieldSearch/FieldSearchResultState.js +84 -0
  107. package/lib/stores/lakehouse/fieldSearch/FieldSearchResultState.js.map +1 -0
  108. package/package.json +13 -13
  109. package/src/__lib__/LegendMarketplaceAppEvent.ts +3 -0
  110. package/src/__lib__/LegendMarketplaceNavigation.ts +18 -1
  111. package/src/__lib__/LegendMarketplaceTelemetryHelper.ts +32 -1
  112. package/src/application/LegendMarketplaceWebApplication.tsx +13 -0
  113. package/src/application/providers/LegendMarketplaceFieldSearchResultsStoreProvider.tsx +67 -0
  114. package/src/components/AddToCart/CartDrawer.tsx +49 -4
  115. package/src/components/AddToCart/RecommendedAddOnsModal.tsx +86 -24
  116. package/src/components/AddToCart/RecommendedItemsCard.tsx +143 -120
  117. package/src/components/FieldSearchFiltersPanel/FieldSearchFiltersPanel.tsx +65 -0
  118. package/src/components/LegendServiceCard/LegendServiceCard.tsx +25 -3
  119. package/src/components/LegendServiceCard/LegendServiceGrid.tsx +206 -0
  120. package/src/components/LegendServiceCard/LegendServiceListRow.tsx +27 -3
  121. package/src/components/MarketplaceCard/FieldSearchResultListItem.tsx +163 -0
  122. package/src/components/MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.tsx +2 -2
  123. package/src/components/Pagination/PaginationControls.tsx +7 -4
  124. package/src/components/ProviderCard/LegendMarketplaceTerminalCard.tsx +7 -0
  125. package/src/components/SearchBar/LegendMarketplaceSearchBar.tsx +44 -3
  126. package/src/pages/DataAPIs/LegendMarketplaceDataAPIs.tsx +80 -14
  127. package/src/pages/Lakehouse/MarketplaceLakehouseHome.tsx +16 -3
  128. package/src/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.tsx +380 -0
  129. package/src/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.tsx +34 -3
  130. package/src/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.tsx +6 -2
  131. package/src/stores/cart/CartStore.ts +86 -51
  132. package/src/stores/dataAPIs/LegendMarketplaceDataAPIsStore.ts +58 -2
  133. package/src/stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.ts +309 -0
  134. package/src/stores/lakehouse/LegendMarketplaceProductViewerStore.ts +23 -30
  135. package/src/stores/lakehouse/LegendMarketplaceSearchResultsStore.ts +11 -6
  136. package/src/stores/lakehouse/fieldSearch/FieldSearchResultState.ts +122 -0
  137. package/tsconfig.json +7 -0
@@ -19,8 +19,15 @@ import {
19
19
  RecommendationSource,
20
20
  type TerminalResult,
21
21
  } from '@finos/legend-server-marketplace';
22
- import { Box, Button, CircularProgress, Typography } from '@mui/material';
22
+ import {
23
+ Box,
24
+ Button,
25
+ CircularProgress,
26
+ Tooltip,
27
+ Typography,
28
+ } from '@mui/material';
23
29
  import { flowResult } from 'mobx';
30
+ import { observer } from 'mobx-react-lite';
24
31
  import { useState } from 'react';
25
32
  import { assertErrorThrown } from '@finos/legend-shared';
26
33
  import { toastManager } from '../Toast/CartToast.js';
@@ -33,166 +40,182 @@ interface RecommendedItemsCardProps {
33
40
  selectedItemId?: number | undefined;
34
41
  }
35
42
 
36
- export const RecommendedItemsCard = (props: RecommendedItemsCardProps) => {
37
- const { recommendedItem, onSelect, isSelecting, selectedItemId } = props;
38
- const legendMarketplaceBaseStore = useLegendMarketplaceBaseStore();
39
- const [isAddingToCart, setIsAddingToCart] = useState(false);
40
- const [inCart, setInCart] = useState(() =>
41
- legendMarketplaceBaseStore.cartStore.isItemInCart(recommendedItem.id),
42
- );
43
+ export const RecommendedItemsCard = observer(
44
+ (props: RecommendedItemsCardProps) => {
45
+ const { recommendedItem, onSelect, isSelecting, selectedItemId } = props;
46
+ const legendMarketplaceBaseStore = useLegendMarketplaceBaseStore();
47
+ const [isAddingToCart, setIsAddingToCart] = useState(false);
48
+ const inCart = legendMarketplaceBaseStore.cartStore.isItemInCart(
49
+ recommendedItem.id,
50
+ );
43
51
 
44
- const isAssociationFlow = onSelect !== undefined;
45
- const isCurrentlySelecting =
46
- isAssociationFlow &&
47
- Boolean(isSelecting) &&
48
- selectedItemId === recommendedItem.id;
49
- const isMarketplaceItem =
50
- recommendedItem.source === RecommendationSource.MARKETPLACE;
52
+ const isAssociationFlow = onSelect !== undefined;
53
+ const isCurrentlySelecting =
54
+ isAssociationFlow &&
55
+ Boolean(isSelecting) &&
56
+ selectedItemId === recommendedItem.id;
57
+ const isMarketplaceItem =
58
+ recommendedItem.source === RecommendationSource.MARKETPLACE;
51
59
 
52
- const handleAddAddonToCart = (addon: TerminalResult) => {
53
- setIsAddingToCart(true);
54
- const cartItemRequest =
55
- legendMarketplaceBaseStore.cartStore.providerToCartRequest(addon);
60
+ const handleAddAddonToCart = (addon: TerminalResult) => {
61
+ setIsAddingToCart(true);
62
+ const cartItemRequest =
63
+ legendMarketplaceBaseStore.cartStore.providerToCartRequest(addon);
56
64
 
57
- flowResult(
58
- legendMarketplaceBaseStore.cartStore.addToCartWithAPI(cartItemRequest),
59
- )
60
- .then((result) => {
61
- if (result.success) {
62
- setInCart(true);
63
- }
64
- setIsAddingToCart(false);
65
- })
66
- .catch((error) => {
67
- assertErrorThrown(error);
68
- toastManager.error(
69
- `Failed to add ${addon.productName} to cart: ${error.message}`,
70
- );
71
- setIsAddingToCart(false);
72
- });
73
- };
74
-
75
- const renderAction = () => {
76
- if (isAssociationFlow) {
77
- if (recommendedItem.isOwned) {
78
- return (
79
- <Box className="recommended-addons-modal__owned-badge">
80
- <CheckCircleIcon />
81
- <Typography variant="body2">Owned</Typography>
82
- </Box>
83
- );
84
- }
65
+ flowResult(
66
+ legendMarketplaceBaseStore.cartStore.addToCartWithAPI(cartItemRequest),
67
+ )
68
+ .catch((error) => {
69
+ assertErrorThrown(error);
70
+ toastManager.error(
71
+ `Failed to add ${addon.productName} to cart: ${error.message}`,
72
+ );
73
+ })
74
+ .finally(() => {
75
+ setIsAddingToCart(false);
76
+ });
77
+ };
85
78
 
86
- if (isMarketplaceItem) {
87
- if (inCart) {
79
+ const renderAction = () => {
80
+ if (isAssociationFlow) {
81
+ if (recommendedItem.isOwned) {
88
82
  return (
89
- <Box className="recommended-addons-modal__in-cart-badge">
90
- <Typography variant="body2">In Cart</Typography>
83
+ <Box className="recommended-addons-modal__owned-badge">
91
84
  <CheckCircleIcon />
85
+ <Typography variant="body2">Owned</Typography>
92
86
  </Box>
93
87
  );
94
88
  }
89
+
90
+ if (isMarketplaceItem) {
91
+ if (inCart) {
92
+ return (
93
+ <Box className="recommended-addons-modal__in-cart-badge">
94
+ <Typography variant="body2">In Cart</Typography>
95
+ <CheckCircleIcon />
96
+ </Box>
97
+ );
98
+ }
99
+ return (
100
+ <Button
101
+ variant="outlined"
102
+ onClick={() => onSelect(recommendedItem)}
103
+ disabled={Boolean(isSelecting)}
104
+ size="small"
105
+ className="recommended-addons-modal__add-btn"
106
+ >
107
+ {isCurrentlySelecting ? (
108
+ <>
109
+ Adding... &nbsp;
110
+ <CircularProgress size={14} />
111
+ </>
112
+ ) : (
113
+ <>
114
+ Add to Cart &nbsp;
115
+ <PlusIcon />
116
+ </>
117
+ )}
118
+ </Button>
119
+ );
120
+ }
121
+
95
122
  return (
96
123
  <Button
97
124
  variant="outlined"
98
125
  onClick={() => onSelect(recommendedItem)}
99
126
  disabled={Boolean(isSelecting)}
100
127
  size="small"
101
- className="recommended-addons-modal__add-btn"
128
+ className="recommended-addons-modal__select-btn"
102
129
  >
103
130
  {isCurrentlySelecting ? (
104
131
  <>
105
- Adding... &nbsp;
132
+ Selecting... &nbsp;
106
133
  <CircularProgress size={14} />
107
134
  </>
108
135
  ) : (
109
136
  <>
110
- Add to Cart &nbsp;
111
- <PlusIcon />
137
+ Select &nbsp;
138
+ <CheckIcon />
112
139
  </>
113
140
  )}
114
141
  </Button>
115
142
  );
116
143
  }
117
144
 
118
- return (
145
+ const button = (
119
146
  <Button
120
147
  variant="outlined"
121
- onClick={() => onSelect(recommendedItem)}
122
- disabled={Boolean(isSelecting)}
148
+ onClick={() => handleAddAddonToCart(recommendedItem)}
149
+ disabled={inCart || isAddingToCart}
123
150
  size="small"
124
- className="recommended-addons-modal__select-btn"
151
+ className={clsx('recommended-addons-modal__add-btn', {
152
+ 'recommended-addons-modal__add-btn--added': inCart,
153
+ })}
125
154
  >
126
- {isCurrentlySelecting ? (
155
+ {isAddingToCart ? (
127
156
  <>
128
- Selecting... &nbsp;
157
+ Adding... &nbsp;
129
158
  <CircularProgress size={14} />
130
159
  </>
160
+ ) : inCart ? (
161
+ 'Added to Cart'
131
162
  ) : (
132
163
  <>
133
- Select &nbsp;
134
- <CheckIcon />
164
+ Add to Cart &nbsp;
165
+ <PlusIcon />
135
166
  </>
136
167
  )}
137
168
  </Button>
138
169
  );
139
- }
140
170
 
141
- return (
142
- <Button
143
- variant="outlined"
144
- onClick={() => handleAddAddonToCart(recommendedItem)}
145
- disabled={inCart || isAddingToCart}
146
- size="small"
147
- className={clsx('recommended-addons-modal__add-btn', {
148
- 'recommended-addons-modal__add-btn--added': inCart,
149
- })}
150
- >
151
- {isAddingToCart ? (
152
- <>
153
- Adding... &nbsp;
154
- <CircularProgress size={14} />
155
- </>
156
- ) : inCart ? (
157
- 'Added to Cart'
158
- ) : (
159
- <>
160
- Add to Cart &nbsp;
161
- <PlusIcon />
162
- </>
163
- )}
164
- </Button>
165
- );
166
- };
171
+ if (inCart) {
172
+ return (
173
+ <Tooltip
174
+ title={
175
+ recommendedItem.isMandatory
176
+ ? 'This is a mandatory item which needs to be associated with this order.'
177
+ : 'This item is already in your cart.'
178
+ }
179
+ arrow={true}
180
+ placement="top"
181
+ >
182
+ <span>{button}</span>
183
+ </Tooltip>
184
+ );
185
+ }
167
186
 
168
- return (
169
- <Box className="recommended-addons-modal__list-item">
170
- <Typography
171
- variant="body1"
172
- className="recommended-addons-modal__item-name"
173
- >
174
- {recommendedItem.productName}
175
- </Typography>
176
- <Typography
177
- variant="body2"
178
- className="recommended-addons-modal__item-provider"
179
- >
180
- {recommendedItem.providerName}
181
- </Typography>
182
- <Typography
183
- variant="body2"
184
- className="recommended-addons-modal__item-price"
185
- >
186
- {recommendedItem.price.toLocaleString('en-US', {
187
- style: 'currency',
188
- currency: 'USD',
189
- minimumFractionDigits: 2,
190
- maximumFractionDigits: 2,
191
- })}
192
- </Typography>
193
- <Box className="recommended-addons-modal__item-action">
194
- {renderAction()}
187
+ return button;
188
+ };
189
+
190
+ return (
191
+ <Box className="recommended-addons-modal__list-item">
192
+ <Typography
193
+ variant="body1"
194
+ className="recommended-addons-modal__item-name"
195
+ >
196
+ {recommendedItem.productName}
197
+ </Typography>
198
+ <Typography
199
+ variant="body2"
200
+ className="recommended-addons-modal__item-provider"
201
+ >
202
+ {recommendedItem.providerName}
203
+ </Typography>
204
+ <Typography
205
+ variant="body2"
206
+ className="recommended-addons-modal__item-price"
207
+ >
208
+ {recommendedItem.price.toLocaleString('en-US', {
209
+ style: 'currency',
210
+ currency: 'USD',
211
+ minimumFractionDigits: 2,
212
+ maximumFractionDigits: 2,
213
+ })}
214
+ </Typography>
215
+ <Box className="recommended-addons-modal__item-action">
216
+ {renderAction()}
217
+ </Box>
195
218
  </Box>
196
- </Box>
197
- );
198
- };
219
+ );
220
+ },
221
+ );
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Copyright (c) 2026-present, Goldman Sachs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { observer } from 'mobx-react-lite';
18
+ import { Typography } from '@mui/material';
19
+ import { DataProductTypeFilter } from '../../stores/lakehouse/LegendMarketplaceSearchResultsStore.js';
20
+ import type { LegendMarketplaceFieldSearchResultsStore } from '../../stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.js';
21
+ import {
22
+ FilterCheckboxOption,
23
+ FilterSection,
24
+ } from '../MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.js';
25
+
26
+ export const FieldSearchFiltersPanel: React.FC<{
27
+ store: LegendMarketplaceFieldSearchResultsStore;
28
+ onToggleProductType: (productType: DataProductTypeFilter) => void;
29
+ onClearFilters: () => void;
30
+ }> = observer(({ store, onToggleProductType, onClearFilters }) => (
31
+ <div className="marketplace-search-filters-panel">
32
+ <div className="marketplace-search-filters-panel__header">
33
+ <Typography className="marketplace-search-filters-panel__header__title">
34
+ Filters
35
+ </Typography>
36
+ {store.hasActiveFilters && (
37
+ <button
38
+ type="button"
39
+ className="marketplace-search-filters-panel__header__clear"
40
+ onClick={onClearFilters}
41
+ >
42
+ Clear all
43
+ </button>
44
+ )}
45
+ </div>
46
+ <div className="marketplace-search-filters-panel__content">
47
+ <FilterSection title="Data Product Type">
48
+ <FilterCheckboxOption
49
+ label="Lakehouse"
50
+ checked={store.selectedProductTypes.has(
51
+ DataProductTypeFilter.LAKEHOUSE,
52
+ )}
53
+ count={store.lakehouseCount}
54
+ onChange={() => onToggleProductType(DataProductTypeFilter.LAKEHOUSE)}
55
+ />
56
+ <FilterCheckboxOption
57
+ label="Legacy"
58
+ checked={store.selectedProductTypes.has(DataProductTypeFilter.LEGACY)}
59
+ count={store.legacyCount}
60
+ onChange={() => onToggleProductType(DataProductTypeFilter.LEGACY)}
61
+ />
62
+ </FilterSection>
63
+ </div>
64
+ </div>
65
+ ));
@@ -15,8 +15,13 @@
15
15
  */
16
16
 
17
17
  import { observer } from 'mobx-react-lite';
18
- import { Box, Chip } from '@mui/material';
19
- import { clsx, MarkdownTextViewer } from '@finos/legend-art';
18
+ import { Box, Chip, IconButton } from '@mui/material';
19
+ import {
20
+ clsx,
21
+ MarkdownTextViewer,
22
+ StarIcon,
23
+ EmptyStarIcon,
24
+ } from '@finos/legend-art';
20
25
  import { LegendMarketplaceCard } from '../MarketplaceCard/LegendMarketplaceCard.js';
21
26
  import { type LegendServiceCardState } from '../../stores/dataAPIs/LegendMarketplaceDataAPIsStore.js';
22
27
  import { ServiceOwnershipType } from '@finos/legend-graph';
@@ -27,8 +32,10 @@ export const LegendServiceCard = observer(
27
32
  (props: {
28
33
  serviceCardState: LegendServiceCardState;
29
34
  onClick: () => void;
35
+ isFavorite: boolean;
36
+ onToggleFavorite: () => void;
30
37
  }): React.ReactNode => {
31
- const { serviceCardState, onClick } = props;
38
+ const { serviceCardState, onClick, isFavorite, onToggleFavorite } = props;
32
39
 
33
40
  const truncatedDescription =
34
41
  serviceCardState.description &&
@@ -43,6 +50,21 @@ export const LegendServiceCard = observer(
43
50
  <Box className="marketplace-legend-service-card__container">
44
51
  <Box className="marketplace-legend-service-card__content">
45
52
  <Box className="marketplace-legend-service-card__tags">
53
+ <IconButton
54
+ className={clsx(
55
+ 'marketplace-legend-service-card__favorite-btn',
56
+ isFavorite &&
57
+ 'marketplace-legend-service-card__favorite-btn--active',
58
+ )}
59
+ onClick={(e) => {
60
+ e.stopPropagation();
61
+ onToggleFavorite();
62
+ }}
63
+ size="small"
64
+ title={isFavorite ? 'Remove from favorites' : 'Add to favorites'}
65
+ >
66
+ {isFavorite ? <StarIcon /> : <EmptyStarIcon />}
67
+ </IconButton>
46
68
  {serviceCardState.ownershipType && (
47
69
  <Chip
48
70
  size="small"
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Copyright (c) 2026-present, Goldman Sachs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { observer } from 'mobx-react-lite';
18
+ import { useCallback, useMemo, useRef } from 'react';
19
+ import { Chip, Tooltip } from '@mui/material';
20
+ import { clsx, DownloadIcon, StarIcon } from '@finos/legend-art';
21
+ import type {
22
+ LegendMarketplaceDataAPIsStore,
23
+ LegendServiceCardState,
24
+ } from '../../stores/dataAPIs/LegendMarketplaceDataAPIsStore.js';
25
+ import { ServiceOwnershipType } from '@finos/legend-graph';
26
+ import { isString } from '@finos/legend-shared';
27
+ import {
28
+ DataGrid,
29
+ type DataGridApi,
30
+ type DataGridCellRendererParams,
31
+ type DataGridColumnDefinition,
32
+ } from '@finos/legend-lego/data-grid';
33
+
34
+ const FavoriteCellRenderer = observer(
35
+ (
36
+ params: DataGridCellRendererParams<LegendServiceCardState> & {
37
+ store: LegendMarketplaceDataAPIsStore;
38
+ },
39
+ ): React.ReactNode => {
40
+ const { data, store } = params;
41
+ if (!data) {
42
+ return null;
43
+ }
44
+ const isFav = store.isFavorite(data.service.pattern);
45
+ return (
46
+ <button
47
+ className={clsx('marketplace-legend-service-grid__star-btn', {
48
+ 'marketplace-legend-service-grid__star-btn--active': isFav,
49
+ })}
50
+ onClick={(e) => {
51
+ e.stopPropagation();
52
+ store.toggleFavorite(data.service.pattern);
53
+ }}
54
+ title={isFav ? 'Remove from favorites' : 'Add to favorites'}
55
+ >
56
+ <StarIcon />
57
+ </button>
58
+ );
59
+ },
60
+ );
61
+
62
+ const OwnersCellRenderer = observer(
63
+ (
64
+ params: DataGridCellRendererParams<LegendServiceCardState>,
65
+ ): React.ReactNode => {
66
+ const data = params.data;
67
+ if (!data) {
68
+ return null;
69
+ }
70
+ return (
71
+ <div className="marketplace-legend-service-grid__chips">
72
+ {data.owners.map((owner) => (
73
+ <Chip
74
+ key={owner}
75
+ size="small"
76
+ label={owner}
77
+ className={`marketplace-legend-service-list-row__chip marketplace-legend-service-list-row__chip--${
78
+ data.ownershipType === ServiceOwnershipType.DEPLOYMENT_OWNERSHIP
79
+ ? 'did'
80
+ : 'owner'
81
+ }`}
82
+ />
83
+ ))}
84
+ </div>
85
+ );
86
+ },
87
+ );
88
+
89
+ export const LegendServiceGridView = observer(
90
+ (props: {
91
+ services: LegendServiceCardState[];
92
+ store: LegendMarketplaceDataAPIsStore;
93
+ onRowClick: (serviceCardState: LegendServiceCardState) => void;
94
+ }): React.ReactNode => {
95
+ const { services, store, onRowClick } = props;
96
+ const gridApiRef = useRef<DataGridApi<LegendServiceCardState> | null>(null);
97
+
98
+ const columnDefs: DataGridColumnDefinition<LegendServiceCardState>[] =
99
+ useMemo(
100
+ () => [
101
+ {
102
+ headerName: '',
103
+ colId: 'favorite',
104
+ cellRenderer: FavoriteCellRenderer,
105
+ cellRendererParams: { store },
106
+ width: 50,
107
+ maxWidth: 50,
108
+ minWidth: 50,
109
+ resizable: false,
110
+ sortable: false,
111
+ filter: false,
112
+ suppressHeaderMenuButton: true,
113
+ },
114
+ {
115
+ headerName: 'Title',
116
+ colId: 'title',
117
+ valueGetter: (p) => p.data?.title,
118
+ minWidth: 150,
119
+ flex: 2,
120
+ filter: true,
121
+ resizable: true,
122
+ },
123
+ {
124
+ headerName: 'URL Path',
125
+ colId: 'urlPath',
126
+ valueGetter: (p) => p.data?.service.pattern,
127
+ minWidth: 200,
128
+ flex: 3,
129
+ filter: true,
130
+ resizable: true,
131
+ },
132
+ {
133
+ headerName: 'Description',
134
+ colId: 'description',
135
+ valueGetter: (p) => p.data?.description,
136
+ minWidth: 200,
137
+ flex: 4,
138
+ filter: true,
139
+ resizable: true,
140
+ },
141
+ {
142
+ headerName: 'Owner / DID',
143
+ colId: 'owners',
144
+ cellRenderer: OwnersCellRenderer,
145
+ valueGetter: (p) => p.data?.owners.join(', '),
146
+ minWidth: 150,
147
+ flex: 2,
148
+ filter: true,
149
+ resizable: true,
150
+ autoHeight: true,
151
+ wrapText: true,
152
+ },
153
+ ],
154
+ [store],
155
+ );
156
+
157
+ const exportToCSV = useCallback((): void => {
158
+ gridApiRef.current?.exportDataAsCsv({
159
+ fileName: 'legend-services.csv',
160
+ columnKeys: ['title', 'urlPath', 'description', 'owners'],
161
+ processCellCallback: (params) => {
162
+ const value: unknown = params.value;
163
+ if (isString(value)) {
164
+ return value.replaceAll(/[\r\n]+/g, ' ');
165
+ }
166
+ return isString(value) ? value : '';
167
+ },
168
+ });
169
+ }, []);
170
+
171
+ return (
172
+ <div className="marketplace-legend-service-grid">
173
+ <div className="marketplace-legend-service-grid__toolbar">
174
+ <span className="marketplace-legend-service-grid__toolbar__count">
175
+ {`${services.length} result${services.length === 1 ? '' : 's'}`}
176
+ </span>
177
+ <Tooltip title="Export visible rows to CSV" placement="left">
178
+ <button
179
+ className="marketplace-legend-service-grid__toolbar__export-btn"
180
+ onClick={exportToCSV}
181
+ >
182
+ <DownloadIcon />
183
+ Export to CSV
184
+ </button>
185
+ </Tooltip>
186
+ </div>
187
+ <div className="marketplace-legend-service-grid__ag-grid ag-theme-balham">
188
+ <DataGrid
189
+ rowData={services}
190
+ columnDefs={columnDefs}
191
+ onGridReady={(params) => {
192
+ gridApiRef.current = params.api;
193
+ }}
194
+ onCellClicked={(event) => {
195
+ if (event.data && event.column.getColId() !== 'favorite') {
196
+ onRowClick(event.data);
197
+ }
198
+ }}
199
+ suppressCellFocus={true}
200
+ overlayNoRowsTemplate="No services match the filters."
201
+ />
202
+ </div>
203
+ </div>
204
+ );
205
+ },
206
+ );