@finos/legend-application-marketplace 0.2.3 → 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 (103) hide show
  1. package/lib/__lib__/LegendMarketplaceAppEvent.d.ts +2 -0
  2. package/lib/__lib__/LegendMarketplaceAppEvent.d.ts.map +1 -1
  3. package/lib/__lib__/LegendMarketplaceAppEvent.js +2 -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 +2 -1
  10. package/lib/__lib__/LegendMarketplaceTelemetryHelper.d.ts.map +1 -1
  11. package/lib/__lib__/LegendMarketplaceTelemetryHelper.js +10 -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/MarketplaceCard/FieldSearchResultListItem.d.ts +25 -0
  36. package/lib/components/MarketplaceCard/FieldSearchResultListItem.d.ts.map +1 -0
  37. package/lib/components/MarketplaceCard/FieldSearchResultListItem.js +58 -0
  38. package/lib/components/MarketplaceCard/FieldSearchResultListItem.js.map +1 -0
  39. package/lib/components/MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.d.ts +10 -0
  40. package/lib/components/MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.d.ts.map +1 -1
  41. package/lib/components/MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.js +2 -2
  42. package/lib/components/MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.js.map +1 -1
  43. package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.d.ts.map +1 -1
  44. package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.js +5 -2
  45. package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.js.map +1 -1
  46. package/lib/components/SearchBar/LegendMarketplaceSearchBar.d.ts +2 -1
  47. package/lib/components/SearchBar/LegendMarketplaceSearchBar.d.ts.map +1 -1
  48. package/lib/components/SearchBar/LegendMarketplaceSearchBar.js +20 -7
  49. package/lib/components/SearchBar/LegendMarketplaceSearchBar.js.map +1 -1
  50. package/lib/index.css +2 -2
  51. package/lib/index.css.map +1 -1
  52. package/lib/package.json +1 -1
  53. package/lib/pages/Lakehouse/MarketplaceLakehouseHome.d.ts.map +1 -1
  54. package/lib/pages/Lakehouse/MarketplaceLakehouseHome.js +6 -4
  55. package/lib/pages/Lakehouse/MarketplaceLakehouseHome.js.map +1 -1
  56. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.d.ts +17 -0
  57. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.d.ts.map +1 -0
  58. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.js +126 -0
  59. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.js.map +1 -0
  60. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.d.ts.map +1 -1
  61. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.js +8 -3
  62. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.js.map +1 -1
  63. package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.d.ts.map +1 -1
  64. package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.js +2 -2
  65. package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.js.map +1 -1
  66. package/lib/stores/cart/CartStore.d.ts +10 -3
  67. package/lib/stores/cart/CartStore.d.ts.map +1 -1
  68. package/lib/stores/cart/CartStore.js +66 -42
  69. package/lib/stores/cart/CartStore.js.map +1 -1
  70. package/lib/stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.d.ts +63 -0
  71. package/lib/stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.d.ts.map +1 -0
  72. package/lib/stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.js +228 -0
  73. package/lib/stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.js.map +1 -0
  74. package/lib/stores/lakehouse/LegendMarketplaceProductViewerStore.d.ts.map +1 -1
  75. package/lib/stores/lakehouse/LegendMarketplaceProductViewerStore.js +9 -13
  76. package/lib/stores/lakehouse/LegendMarketplaceProductViewerStore.js.map +1 -1
  77. package/lib/stores/lakehouse/fieldSearch/FieldSearchResultState.d.ts +40 -0
  78. package/lib/stores/lakehouse/fieldSearch/FieldSearchResultState.d.ts.map +1 -0
  79. package/lib/stores/lakehouse/fieldSearch/FieldSearchResultState.js +84 -0
  80. package/lib/stores/lakehouse/fieldSearch/FieldSearchResultState.js.map +1 -0
  81. package/package.json +8 -8
  82. package/src/__lib__/LegendMarketplaceAppEvent.ts +2 -0
  83. package/src/__lib__/LegendMarketplaceNavigation.ts +18 -1
  84. package/src/__lib__/LegendMarketplaceTelemetryHelper.ts +17 -1
  85. package/src/application/LegendMarketplaceWebApplication.tsx +13 -0
  86. package/src/application/providers/LegendMarketplaceFieldSearchResultsStoreProvider.tsx +67 -0
  87. package/src/components/AddToCart/CartDrawer.tsx +49 -4
  88. package/src/components/AddToCart/RecommendedAddOnsModal.tsx +86 -24
  89. package/src/components/AddToCart/RecommendedItemsCard.tsx +143 -120
  90. package/src/components/FieldSearchFiltersPanel/FieldSearchFiltersPanel.tsx +65 -0
  91. package/src/components/MarketplaceCard/FieldSearchResultListItem.tsx +163 -0
  92. package/src/components/MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.tsx +2 -2
  93. package/src/components/ProviderCard/LegendMarketplaceTerminalCard.tsx +7 -0
  94. package/src/components/SearchBar/LegendMarketplaceSearchBar.tsx +44 -3
  95. package/src/pages/Lakehouse/MarketplaceLakehouseHome.tsx +9 -2
  96. package/src/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.tsx +380 -0
  97. package/src/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.tsx +19 -1
  98. package/src/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.tsx +6 -2
  99. package/src/stores/cart/CartStore.ts +86 -51
  100. package/src/stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.ts +309 -0
  101. package/src/stores/lakehouse/LegendMarketplaceProductViewerStore.ts +23 -30
  102. package/src/stores/lakehouse/fieldSearch/FieldSearchResultState.ts +122 -0
  103. package/tsconfig.json +6 -0
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Copyright (c) 2020-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 { Container, Paper, Typography } from '@mui/material';
18
+ import { observer } from 'mobx-react-lite';
19
+ import { flowResult } from 'mobx';
20
+ import { useCallback, useEffect, useRef } from 'react';
21
+ import { useSyncStateAndSearchParam } from '@finos/legend-application';
22
+ import { useSearchParams } from '@finos/legend-application/browser';
23
+ import { isNonEmptyString } from '@finos/legend-shared';
24
+ import {
25
+ CubesLoadingIndicator,
26
+ CubesLoadingIndicatorIcon,
27
+ } from '@finos/legend-art';
28
+ import { DATAPRODUCT_TYPE } from '@finos/legend-extension-dsl-data-product';
29
+ import {
30
+ LEGEND_MARKETPLACE_FIELD_SEARCH_RESULTS_QUERY_PARAM_TOKEN,
31
+ generateLakehouseSearchResultsRoute,
32
+ } from '../../../__lib__/LegendMarketplaceNavigation.js';
33
+ import {
34
+ LEGEND_MARKETPLACE_PAGE,
35
+ LegendMarketplaceTelemetryHelper,
36
+ } from '../../../__lib__/LegendMarketplaceTelemetryHelper.js';
37
+ import {
38
+ useLegendMarketplaceFieldSearchResultsStore,
39
+ withLegendMarketplaceFieldSearchResultsStore,
40
+ } from '../../../application/providers/LegendMarketplaceFieldSearchResultsStoreProvider.js';
41
+ import { FieldSearchFiltersPanel } from '../../../components/FieldSearchFiltersPanel/FieldSearchFiltersPanel.js';
42
+ import { FieldSearchResultListRow } from '../../../components/MarketplaceCard/FieldSearchResultListItem.js';
43
+ import { LegendMarketplaceSearchBar } from '../../../components/SearchBar/LegendMarketplaceSearchBar.js';
44
+ import { PaginationControls } from '../../../components/Pagination/PaginationControls.js';
45
+ import { LegendMarketplacePage } from '../../LegendMarketplacePage.js';
46
+ import { DataProductTypeFilter } from '../../../stores/lakehouse/LegendMarketplaceSearchResultsStore.js';
47
+ import type { LegendMarketplaceFieldSearchResultsStore } from '../../../stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.js';
48
+ import { type FieldSearchDataProductEntry } from '../../../stores/lakehouse/fieldSearch/FieldSearchResultState.js';
49
+
50
+ const FieldSearchResultsContent = observer(
51
+ (props: {
52
+ fieldSearchResultsStore: LegendMarketplaceFieldSearchResultsStore;
53
+ handlePageChange: (page: number) => void;
54
+ handleItemsPerPageChange: (itemsPerPage: number) => void;
55
+ handleToggleExpandRow: (rowId: string) => void;
56
+ handleOpenDataProduct: (dataProduct: FieldSearchDataProductEntry) => void;
57
+ }) => {
58
+ const {
59
+ fieldSearchResultsStore,
60
+ handlePageChange,
61
+ handleItemsPerPageChange,
62
+ handleToggleExpandRow,
63
+ handleOpenDataProduct,
64
+ } = props;
65
+
66
+ if (fieldSearchResultsStore.isLoading) {
67
+ return (
68
+ <div className="marketplace-lakehouse-search-results__loading-container">
69
+ <CubesLoadingIndicator isLoading={true}>
70
+ <CubesLoadingIndicatorIcon />
71
+ </CubesLoadingIndicator>
72
+ </div>
73
+ );
74
+ }
75
+
76
+ if (fieldSearchResultsStore.hasFailed) {
77
+ return (
78
+ <div className="marketplace-lakehouse-search-results__empty-state">
79
+ <Typography
80
+ variant="h5"
81
+ className="marketplace-lakehouse-search-results__empty-state__title"
82
+ >
83
+ Field search failed
84
+ </Typography>
85
+ <Typography
86
+ variant="body1"
87
+ className="marketplace-lakehouse-search-results__empty-state__message"
88
+ >
89
+ {fieldSearchResultsStore.errorMessage}
90
+ </Typography>
91
+ </div>
92
+ );
93
+ }
94
+
95
+ if (
96
+ fieldSearchResultsStore.hasActiveFilters &&
97
+ fieldSearchResultsStore.totalItems === 0
98
+ ) {
99
+ return (
100
+ <div className="marketplace-lakehouse-search-results__empty-state">
101
+ <Typography
102
+ variant="h5"
103
+ className="marketplace-lakehouse-search-results__empty-state__title"
104
+ >
105
+ No fields match the current filters
106
+ </Typography>
107
+ <Typography
108
+ variant="body1"
109
+ className="marketplace-lakehouse-search-results__empty-state__message"
110
+ >
111
+ Clear one or more filters to see results again.
112
+ </Typography>
113
+ </div>
114
+ );
115
+ }
116
+
117
+ if (
118
+ !fieldSearchResultsStore.hasActiveFilters &&
119
+ isNonEmptyString(fieldSearchResultsStore.searchQuery) &&
120
+ fieldSearchResultsStore.totalItems === 0
121
+ ) {
122
+ return (
123
+ <div className="marketplace-lakehouse-search-results__empty-state">
124
+ <Typography
125
+ variant="h5"
126
+ className="marketplace-lakehouse-search-results__empty-state__title"
127
+ >
128
+ No fields found
129
+ </Typography>
130
+ <Typography
131
+ variant="body1"
132
+ className="marketplace-lakehouse-search-results__empty-state__message"
133
+ >
134
+ Try a broader query or switch back to product search.
135
+ </Typography>
136
+ </div>
137
+ );
138
+ }
139
+
140
+ if (fieldSearchResultsStore.tableRows.length === 0) {
141
+ return null;
142
+ }
143
+
144
+ return (
145
+ <>
146
+ <Paper
147
+ elevation={1}
148
+ className="marketplace-lakehouse-field-search-results__list"
149
+ >
150
+ <div className="marketplace-lakehouse-field-search-results__list-header">
151
+ <Typography className="marketplace-lakehouse-field-search-results__list-header-cell">
152
+ Field Name
153
+ </Typography>
154
+ <Typography className="marketplace-lakehouse-field-search-results__list-header-cell">
155
+ Type
156
+ </Typography>
157
+ <Typography className="marketplace-lakehouse-field-search-results__list-header-cell">
158
+ Description
159
+ </Typography>
160
+ <Typography className="marketplace-lakehouse-field-search-results__list-header-cell">
161
+ Data Products
162
+ </Typography>
163
+ </div>
164
+ <div className="marketplace-lakehouse-field-search-results__list-body">
165
+ {fieldSearchResultsStore.tableRows.map((row) => (
166
+ <FieldSearchResultListRow
167
+ key={row.id}
168
+ fieldSearchResultState={row}
169
+ expanded={fieldSearchResultsStore.isRowExpanded(row.id)}
170
+ onToggleExpanded={handleToggleExpandRow}
171
+ onOpenDataProduct={handleOpenDataProduct}
172
+ />
173
+ ))}
174
+ </div>
175
+ </Paper>
176
+ <PaginationControls
177
+ totalItems={fieldSearchResultsStore.totalItems}
178
+ itemsPerPage={fieldSearchResultsStore.itemsPerPage}
179
+ page={fieldSearchResultsStore.page}
180
+ onPageChange={handlePageChange}
181
+ onItemsPerPageChange={handleItemsPerPageChange}
182
+ disabled={fieldSearchResultsStore.isLoading}
183
+ />
184
+ </>
185
+ );
186
+ },
187
+ );
188
+
189
+ const LegendMarketplaceFieldSearchResultsPage = observer(() => {
190
+ const fieldSearchResultsStore = useLegendMarketplaceFieldSearchResultsStore();
191
+ const marketplaceBaseStore = fieldSearchResultsStore.marketplaceBaseStore;
192
+ const applicationStore = marketplaceBaseStore.applicationStore;
193
+ const [searchParams, setSearchParams] = useSearchParams();
194
+ const pageRef = useRef<HTMLDivElement>(null);
195
+
196
+ useSyncStateAndSearchParam(
197
+ fieldSearchResultsStore.searchQuery,
198
+ useCallback(
199
+ (val: string | null) => {
200
+ if (val === null) {
201
+ return;
202
+ }
203
+ fieldSearchResultsStore.setSearchQuery(val);
204
+ },
205
+ [fieldSearchResultsStore],
206
+ ),
207
+ LEGEND_MARKETPLACE_FIELD_SEARCH_RESULTS_QUERY_PARAM_TOKEN.QUERY,
208
+ searchParams.get(
209
+ LEGEND_MARKETPLACE_FIELD_SEARCH_RESULTS_QUERY_PARAM_TOKEN.QUERY,
210
+ ),
211
+ setSearchParams,
212
+ useCallback(() => true, []),
213
+ );
214
+
215
+ useEffect(() => {
216
+ flowResult(fieldSearchResultsStore.executeSearch()).catch(
217
+ applicationStore.alertUnhandledError,
218
+ );
219
+ }, [
220
+ applicationStore,
221
+ fieldSearchResultsStore,
222
+ fieldSearchResultsStore.searchQuery,
223
+ ]);
224
+
225
+ const handlePageChange = useCallback(
226
+ (page: number) => {
227
+ pageRef.current?.scrollIntoView({ behavior: 'smooth' });
228
+ flowResult(fieldSearchResultsStore.changePageAndFetch(page)).catch(
229
+ applicationStore.alertUnhandledError,
230
+ );
231
+ },
232
+ [fieldSearchResultsStore, applicationStore],
233
+ );
234
+
235
+ const handleItemsPerPageChange = useCallback(
236
+ (itemsPerPage: number) => {
237
+ flowResult(
238
+ fieldSearchResultsStore.changeItemsPerPageAndFetch(itemsPerPage),
239
+ ).catch(applicationStore.alertUnhandledError);
240
+ },
241
+ [fieldSearchResultsStore, applicationStore],
242
+ );
243
+
244
+ const handleToggleProductType = useCallback(
245
+ (productType: DataProductTypeFilter) => {
246
+ flowResult(
247
+ fieldSearchResultsStore.toggleProductTypeAndFetch(productType),
248
+ ).catch(applicationStore.alertUnhandledError);
249
+ },
250
+ [fieldSearchResultsStore, applicationStore],
251
+ );
252
+
253
+ const handleClearFilters = useCallback(() => {
254
+ flowResult(fieldSearchResultsStore.clearAllFiltersAndFetch()).catch(
255
+ applicationStore.alertUnhandledError,
256
+ );
257
+ }, [fieldSearchResultsStore, applicationStore]);
258
+
259
+ const handleSearch = useCallback(
260
+ (
261
+ query: string | undefined,
262
+ useProducerSearch: boolean,
263
+ useFieldSearch: boolean,
264
+ ) => {
265
+ if (!isNonEmptyString(query)) {
266
+ return;
267
+ }
268
+ if (!useFieldSearch) {
269
+ applicationStore.navigationService.navigator.goToLocation(
270
+ generateLakehouseSearchResultsRoute(query, useProducerSearch),
271
+ );
272
+ return;
273
+ }
274
+ fieldSearchResultsStore.setSearchQuery(query);
275
+ LegendMarketplaceTelemetryHelper.logEvent_SearchQuery(
276
+ applicationStore.telemetryService,
277
+ query,
278
+ false,
279
+ LEGEND_MARKETPLACE_PAGE.SEARCH_RESULTS_PAGE,
280
+ true,
281
+ );
282
+ },
283
+ [applicationStore, fieldSearchResultsStore],
284
+ );
285
+
286
+ const handleOpenDataProduct = useCallback(
287
+ (dataProduct: FieldSearchDataProductEntry) => {
288
+ LegendMarketplaceTelemetryHelper.logEvent_ClickingDataProductCard(
289
+ applicationStore.telemetryService,
290
+ {
291
+ origin:
292
+ dataProduct.productType === DataProductTypeFilter.LEGACY
293
+ ? {
294
+ type: DATAPRODUCT_TYPE.SDLC,
295
+ groupId: dataProduct.groupId,
296
+ artifactId: dataProduct.artifactId,
297
+ versionId: dataProduct.versionId,
298
+ path: dataProduct.entityPath,
299
+ }
300
+ : {
301
+ type: DATAPRODUCT_TYPE.ADHOC,
302
+ },
303
+ dataProductId: dataProduct.dataProductId,
304
+ name: dataProduct.name,
305
+ deploymentId: dataProduct.deploymentId,
306
+ },
307
+ LEGEND_MARKETPLACE_PAGE.SEARCH_RESULTS_PAGE,
308
+ );
309
+ applicationStore.navigationService.navigator.visitAddress(
310
+ applicationStore.navigationService.navigator.generateAddress(
311
+ dataProduct.path,
312
+ ),
313
+ );
314
+ },
315
+ [applicationStore],
316
+ );
317
+
318
+ const handleToggleExpandRow = useCallback(
319
+ (rowId: string) => {
320
+ fieldSearchResultsStore.toggleExpandRow(rowId);
321
+ },
322
+ [fieldSearchResultsStore],
323
+ );
324
+
325
+ return (
326
+ <LegendMarketplacePage className="marketplace-lakehouse-search-results marketplace-lakehouse-field-search-results">
327
+ <div ref={pageRef} />
328
+ <Container className="marketplace-lakehouse-search-results__search-container">
329
+ <LegendMarketplaceSearchBar
330
+ showSettings={true}
331
+ onSearch={handleSearch}
332
+ stateSearchQuery={fieldSearchResultsStore.searchQuery}
333
+ stateUseProducerSearch={false}
334
+ stateUseFieldSearch={true}
335
+ placeholder="Search Marketplace fields"
336
+ className="marketplace-lakehouse-search-results__search-bar"
337
+ enableAutosuggest={false}
338
+ />
339
+ </Container>
340
+ <div className="legend-marketplace-search-results__sort-bar">
341
+ <div className="legend-marketplace-search-results__sort-bar__container">
342
+ <Typography
343
+ variant="h4"
344
+ className="marketplace-lakehouse-search-results__subtitles"
345
+ >
346
+ {fieldSearchResultsStore.totalFieldMatches} Fields
347
+ </Typography>
348
+ </div>
349
+ </div>
350
+ <Container
351
+ maxWidth="xxxl"
352
+ className="marketplace-lakehouse-search-results__results-container"
353
+ >
354
+ <div className="marketplace-lakehouse-search-results__results-layout">
355
+ <div className="marketplace-lakehouse-search-results__sidebar">
356
+ <FieldSearchFiltersPanel
357
+ store={fieldSearchResultsStore}
358
+ onToggleProductType={handleToggleProductType}
359
+ onClearFilters={handleClearFilters}
360
+ />
361
+ </div>
362
+ <div className="marketplace-lakehouse-search-results__main-content">
363
+ <FieldSearchResultsContent
364
+ fieldSearchResultsStore={fieldSearchResultsStore}
365
+ handlePageChange={handlePageChange}
366
+ handleItemsPerPageChange={handleItemsPerPageChange}
367
+ handleToggleExpandRow={handleToggleExpandRow}
368
+ handleOpenDataProduct={handleOpenDataProduct}
369
+ />
370
+ </div>
371
+ </div>
372
+ </Container>
373
+ </LegendMarketplacePage>
374
+ );
375
+ });
376
+
377
+ export const LegendMarketplaceFieldSearchResults =
378
+ withLegendMarketplaceFieldSearchResultsStore(
379
+ LegendMarketplaceFieldSearchResultsPage,
380
+ );
@@ -45,7 +45,10 @@ import {
45
45
  SearchResultsViewMode,
46
46
  type LegendMarketplaceSearchResultsStore,
47
47
  } from '../../../stores/lakehouse/LegendMarketplaceSearchResultsStore.js';
48
- import { LEGEND_MARKETPLACE_LAKEHOUSE_SEARCH_RESULTS_QUERY_PARAM_TOKEN } from '../../../__lib__/LegendMarketplaceNavigation.js';
48
+ import {
49
+ generateFieldSearchResultsRoute,
50
+ LEGEND_MARKETPLACE_LAKEHOUSE_SEARCH_RESULTS_QUERY_PARAM_TOKEN,
51
+ } from '../../../__lib__/LegendMarketplaceNavigation.js';
49
52
  import { LegendMarketplaceSearchBar } from '../../../components/SearchBar/LegendMarketplaceSearchBar.js';
50
53
  import { LegendMarketplacePage } from '../../LegendMarketplacePage.js';
51
54
  import { useAuth } from 'react-oidc-context';
@@ -265,8 +268,22 @@ export const LegendMarketplaceSearchResults =
265
268
  const handleSearch = (
266
269
  _query: string | undefined,
267
270
  _useProducerSearch: boolean,
271
+ _useFieldSearch: boolean,
268
272
  ): void => {
269
273
  if (isNonEmptyString(_query)) {
274
+ if (_useFieldSearch) {
275
+ applicationStore.navigationService.navigator.goToLocation(
276
+ generateFieldSearchResultsRoute(_query),
277
+ );
278
+ LegendMarketplaceTelemetryHelper.logEvent_SearchQuery(
279
+ applicationStore.telemetryService,
280
+ _query,
281
+ false,
282
+ LEGEND_MARKETPLACE_PAGE.SEARCH_RESULTS_PAGE,
283
+ true,
284
+ );
285
+ return;
286
+ }
270
287
  searchResultsStore.setSearchQuery(_query);
271
288
  searchResultsStore.setUseProducerSearch(_useProducerSearch);
272
289
  LegendMarketplaceTelemetryHelper.logEvent_SearchQuery(
@@ -349,6 +366,7 @@ export const LegendMarketplaceSearchResults =
349
366
  onSearch={handleSearch}
350
367
  stateSearchQuery={searchResultsStore.searchQuery}
351
368
  stateUseProducerSearch={searchResultsStore.useProducerSearch}
369
+ stateUseFieldSearch={false}
352
370
  placeholder="Search Legend Marketplace"
353
371
  className="marketplace-lakehouse-search-results__search-bar"
354
372
  enableAutosuggest={false}
@@ -311,10 +311,14 @@ export const LegendMarketplaceVendorData = withLegendMarketplaceVendorDataStore(
311
311
  setUserValue={(_user: LegendUser): void => {
312
312
  if (!_user.id) {
313
313
  marketPlaceVendorDataStore.resetSelectedUser();
314
- cartStore.setTargetUser(undefined);
314
+ flowResult(cartStore.setTargetUser(undefined)).catch(
315
+ marketplaceStore.applicationStore.alertUnhandledError,
316
+ );
315
317
  } else {
316
318
  marketPlaceVendorDataStore.setSelectedUser(_user);
317
- cartStore.setTargetUser(_user.id);
319
+ flowResult(cartStore.setTargetUser(_user.id)).catch(
320
+ marketplaceStore.applicationStore.alertUnhandledError,
321
+ );
318
322
  }
319
323
  }}
320
324
  userSearchService={marketplaceStore.userSearchService}
@@ -14,7 +14,14 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import { makeObservable, observable, action, flow, flowResult } from 'mobx';
17
+ import {
18
+ makeObservable,
19
+ observable,
20
+ action,
21
+ flow,
22
+ flowResult,
23
+ computed,
24
+ } from 'mobx';
18
25
  import {
19
26
  LogEvent,
20
27
  type GeneratorFn,
@@ -22,6 +29,7 @@ import {
22
29
  ActionState,
23
30
  } from '@finos/legend-shared';
24
31
  import {
32
+ TerminalItemType,
25
33
  type CartItem,
26
34
  type CartItemRequest,
27
35
  type CartItemResponse,
@@ -64,8 +72,10 @@ export class CartStore {
64
72
  businessReason: observable,
65
73
  open: observable,
66
74
  cartSummary: observable,
75
+ cartUser: computed,
76
+ cartItemIds: computed,
67
77
  setOpen: action,
68
- setTargetUser: action,
78
+ setTargetUser: flow,
69
79
  setBusinessReason: action,
70
80
  initialize: flow,
71
81
  submitOrder: flow,
@@ -81,12 +91,50 @@ export class CartStore {
81
91
  return this.baseStore.applicationStore.identityService.currentUser;
82
92
  }
83
93
 
94
+ get cartUser(): string {
95
+ return this.targetUser ?? this.currentUser;
96
+ }
97
+
98
+ get cartItemIds(): Set<number> {
99
+ const ids = new Set<number>();
100
+ for (const vendorProfileId in this.items) {
101
+ if (Object.prototype.hasOwnProperty.call(this.items, vendorProfileId)) {
102
+ const cartItems = this.items[Number(vendorProfileId)];
103
+ if (cartItems) {
104
+ for (const item of cartItems) {
105
+ ids.add(item.id);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ return ids;
111
+ }
112
+
84
113
  setOpen(val: boolean): void {
85
114
  this.open = val;
86
115
  }
87
116
 
88
- setTargetUser(val: string | undefined): void {
117
+ *setTargetUser(val: string | undefined): GeneratorFn<void> {
118
+ this.loadingState.inProgress();
89
119
  this.targetUser = val;
120
+ this.items = {};
121
+ this.cartSummary = {
122
+ total_items: 0,
123
+ total_cost: 0,
124
+ formatted_total_cost: '$0.00',
125
+ };
126
+ this.businessReason = undefined;
127
+ try {
128
+ yield flowResult(this.refresh());
129
+ this.loadingState.complete();
130
+ } catch (error) {
131
+ assertErrorThrown(error);
132
+ this.baseStore.applicationStore.logService.error(
133
+ LogEvent.create(APPLICATION_EVENT.IDENTITY_AUTO_FETCH__FAILURE),
134
+ `Failed to load cart for user: ${error.message}`,
135
+ );
136
+ this.loadingState.fail();
137
+ }
90
138
  }
91
139
 
92
140
  setBusinessReason(val: string | undefined): void {
@@ -94,23 +142,39 @@ export class CartStore {
94
142
  }
95
143
 
96
144
  isItemInCart(itemId: number): boolean {
145
+ return this.cartItemIds.has(itemId);
146
+ }
147
+
148
+ /**
149
+ * Returns the add-on items that depend on the given cart item.
150
+ * When a Terminal is deleted, its associated add-ons (same vendor) must also be removed.
151
+ */
152
+ getDependentAddOns(cartId: number): CartItem[] {
97
153
  for (const vendorProfileId in this.items) {
98
154
  if (Object.prototype.hasOwnProperty.call(this.items, vendorProfileId)) {
99
155
  const cartItems = this.items[Number(vendorProfileId)];
100
- if (cartItems?.some((item) => item.id === itemId)) {
101
- return true;
156
+ if (cartItems) {
157
+ const target = cartItems.find((item) => item.cartId === cartId);
158
+ if (target && target.category === TerminalItemType.TERMINAL) {
159
+ return cartItems.filter(
160
+ (item) =>
161
+ item.cartId !== cartId &&
162
+ item.category === TerminalItemType.ADD_ON,
163
+ );
164
+ }
102
165
  }
103
166
  }
104
167
  }
105
- return false;
168
+ return [];
106
169
  }
107
170
 
108
171
  *addToCartWithAPI(cartItemData: CartItemRequest): GeneratorFn<{
109
172
  success: boolean;
110
173
  recommendations?: TerminalResult[];
111
174
  message: string;
175
+ totalCount?: number | null;
112
176
  }> {
113
- const user = this.currentUser;
177
+ const user = this.cartUser;
114
178
 
115
179
  if (!user) {
116
180
  const message = 'User not authenticated';
@@ -125,11 +189,6 @@ export class CartStore {
125
189
  cartItemData,
126
190
  )) as CartItemResponse;
127
191
 
128
- this.cartSummary =
129
- (yield this.baseStore.marketplaceServerClient.getCartSummary(
130
- user,
131
- )) as CartSummary;
132
-
133
192
  yield flowResult(this.refresh());
134
193
 
135
194
  const responseMessage: string = response.message;
@@ -159,6 +218,7 @@ export class CartStore {
159
218
  success: true,
160
219
  recommendations,
161
220
  message: responseMessage,
221
+ totalCount: response.total_count,
162
222
  };
163
223
  } catch (error) {
164
224
  assertErrorThrown(error);
@@ -195,7 +255,7 @@ export class CartStore {
195
255
  }
196
256
  this.initState.inProgress();
197
257
  try {
198
- this.refresh();
258
+ yield flowResult(this.refresh());
199
259
  this.initState.complete();
200
260
  } catch (error) {
201
261
  assertErrorThrown(error);
@@ -208,7 +268,7 @@ export class CartStore {
208
268
  }
209
269
 
210
270
  *refresh(): GeneratorFn<void> {
211
- const user = this.currentUser;
271
+ const user = this.cartUser;
212
272
  if (!user) {
213
273
  return;
214
274
  }
@@ -231,31 +291,6 @@ export class CartStore {
231
291
  }
232
292
  }
233
293
 
234
- *getCartSummary(): GeneratorFn<void> {
235
- const user = this.currentUser;
236
- if (!user) {
237
- return;
238
- }
239
- try {
240
- const cartSummary =
241
- (yield this.baseStore.marketplaceServerClient.getCartSummary(
242
- user,
243
- )) as CartSummary;
244
- this.cartSummary = cartSummary;
245
- } catch (error) {
246
- assertErrorThrown(error);
247
- this.cartSummary = {
248
- total_items: 0,
249
- total_cost: 0,
250
- formatted_total_cost: '$0.00',
251
- };
252
- this.baseStore.applicationStore.logService.error(
253
- LogEvent.create(APPLICATION_EVENT.IDENTITY_AUTO_FETCH__FAILURE),
254
- `Failed to get cart summary: ${error.message}`,
255
- );
256
- }
257
- }
258
-
259
294
  *submitOrder(): GeneratorFn<void> {
260
295
  if (!this.businessReason) {
261
296
  toastManager.warning(
@@ -277,18 +312,16 @@ export class CartStore {
277
312
  try {
278
313
  const orderData: OrderDetails = {
279
314
  ordered_by: user,
280
- kerberos: this.targetUser ?? user,
315
+ kerberos: this.cartUser,
281
316
  order_items: this.items,
282
317
  business_justification: this.businessReason,
283
318
  };
284
319
 
285
320
  yield this.baseStore.marketplaceServerClient.submitOrder(user, orderData);
286
321
 
287
- this.getCartSummary();
288
-
289
322
  toastManager.notify('Order created successfully!', 'success');
290
323
 
291
- this.refresh();
324
+ yield flowResult(this.refresh());
292
325
  this.setBusinessReason(undefined);
293
326
  this.open = false;
294
327
  this.submitState.complete();
@@ -301,7 +334,7 @@ export class CartStore {
301
334
  }
302
335
 
303
336
  *clearCart(): GeneratorFn<void> {
304
- const user = this.currentUser;
337
+ const user = this.cartUser;
305
338
  if (!user) {
306
339
  toastManager.error('User not authenticated');
307
340
  return;
@@ -310,8 +343,7 @@ export class CartStore {
310
343
  this.loadingState.inProgress();
311
344
  try {
312
345
  yield this.baseStore.marketplaceServerClient.clearCart(user);
313
- this.refresh();
314
- this.getCartSummary();
346
+ yield flowResult(this.refresh());
315
347
  toastManager.success('Cart cleared successfully');
316
348
  this.loadingState.complete();
317
349
  } catch (error) {
@@ -322,8 +354,8 @@ export class CartStore {
322
354
  }
323
355
  }
324
356
 
325
- *deleteCartItem(cartId: number): GeneratorFn<void> {
326
- const user = this.currentUser;
357
+ *deleteCartItem(cartId: number, confirmDelete?: boolean): GeneratorFn<void> {
358
+ const user = this.cartUser;
327
359
  if (!user) {
328
360
  toastManager.error('User not authenticated');
329
361
  return;
@@ -331,10 +363,13 @@ export class CartStore {
331
363
 
332
364
  this.loadingState.inProgress();
333
365
  try {
334
- yield this.baseStore.marketplaceServerClient.deleteCartItem(user, cartId);
366
+ yield this.baseStore.marketplaceServerClient.deleteCartItem(
367
+ user,
368
+ cartId,
369
+ confirmDelete,
370
+ );
335
371
 
336
- this.getCartSummary();
337
- this.refresh();
372
+ yield flowResult(this.refresh());
338
373
  toastManager.success('Item removed successfully');
339
374
  this.loadingState.complete();
340
375
  } catch (error) {