@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
@@ -16,8 +16,13 @@
16
16
 
17
17
  import { observer } from 'mobx-react-lite';
18
18
  import { useState } from 'react';
19
- import { Chip } from '@mui/material';
20
- import { MarkdownTextViewer } from '@finos/legend-art';
19
+ import { Chip, IconButton } from '@mui/material';
20
+ import {
21
+ clsx,
22
+ MarkdownTextViewer,
23
+ StarIcon,
24
+ EmptyStarIcon,
25
+ } from '@finos/legend-art';
21
26
  import type { LegendServiceCardState } from '../../stores/dataAPIs/LegendMarketplaceDataAPIsStore.js';
22
27
  import { ServiceOwnershipType } from '@finos/legend-graph';
23
28
  import { LegendMarketplaceListItem } from '../MarketplaceCard/LegendMarketplaceListItem.js';
@@ -28,8 +33,10 @@ export const LegendServiceListRow = observer(
28
33
  (props: {
29
34
  serviceCardState: LegendServiceCardState;
30
35
  onClick: () => void;
36
+ isFavorite: boolean;
37
+ onToggleFavorite: () => void;
31
38
  }): React.ReactNode => {
32
- const { serviceCardState, onClick } = props;
39
+ const { serviceCardState, onClick, isFavorite, onToggleFavorite } = props;
33
40
  const [expanded, setExpanded] = useState(false);
34
41
  const description = serviceCardState.description;
35
42
  const isTruncatable = description.length > MAX_DESCRIPTION_LENGTH;
@@ -46,6 +53,23 @@ export const LegendServiceListRow = observer(
46
53
  content={
47
54
  <div className="marketplace-legend-service-list-row__body">
48
55
  <div className="marketplace-legend-service-list-row__header">
56
+ <IconButton
57
+ className={clsx(
58
+ 'marketplace-legend-service-list-row__favorite-btn',
59
+ isFavorite &&
60
+ 'marketplace-legend-service-list-row__favorite-btn--active',
61
+ )}
62
+ onClick={(e) => {
63
+ e.stopPropagation();
64
+ onToggleFavorite();
65
+ }}
66
+ size="small"
67
+ title={
68
+ isFavorite ? 'Remove from favorites' : 'Add to favorites'
69
+ }
70
+ >
71
+ {isFavorite ? <StarIcon /> : <EmptyStarIcon />}
72
+ </IconButton>
49
73
  <div className="marketplace-legend-service-list-row__title-block">
50
74
  <div className="marketplace-legend-service-list-row__name">
51
75
  {serviceCardState.title}
@@ -0,0 +1,163 @@
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 { observer } from 'mobx-react-lite';
18
+ import { useState } from 'react';
19
+ import { Chip, Typography } from '@mui/material';
20
+ import { DatasetIcon, PackageIcon } from '@finos/legend-art';
21
+ import type {
22
+ FieldSearchDataProductEntry,
23
+ FieldSearchResultState,
24
+ } from '../../stores/lakehouse/fieldSearch/FieldSearchResultState.js';
25
+ import { LegendMarketplaceListItem } from '../MarketplaceCard/LegendMarketplaceListItem.js';
26
+
27
+ enum FieldSearchResultListItemLabel {
28
+ SHOW_LESS = 'Show Less',
29
+ MORE_SUFFIX = 'More',
30
+ DATASET_SEPARATOR = '|',
31
+ EMPTY_VALUE = '-',
32
+ }
33
+
34
+ enum FieldSearchResultListItemValue {
35
+ COLLAPSED_VISIBLE_DATA_PRODUCTS = 2,
36
+ MAX_DESCRIPTION_LENGTH = 150,
37
+ }
38
+
39
+ export const FieldSearchResultListRow = observer(
40
+ (props: {
41
+ fieldSearchResultState: FieldSearchResultState;
42
+ expanded: boolean;
43
+ onToggleExpanded: (rowId: string) => void;
44
+ onOpenDataProduct: (dataProduct: FieldSearchDataProductEntry) => void;
45
+ }): React.ReactNode => {
46
+ const {
47
+ fieldSearchResultState,
48
+ expanded,
49
+ onToggleExpanded,
50
+ onOpenDataProduct,
51
+ } = props;
52
+
53
+ const [descriptionExpanded, setDescriptionExpanded] = useState(false);
54
+ const description = fieldSearchResultState.fieldDescription;
55
+ const isDescriptionTruncatable =
56
+ description.length >
57
+ FieldSearchResultListItemValue.MAX_DESCRIPTION_LENGTH;
58
+ const displayDescription =
59
+ !descriptionExpanded && isDescriptionTruncatable
60
+ ? `${description.substring(0, FieldSearchResultListItemValue.MAX_DESCRIPTION_LENGTH)}...`
61
+ : description;
62
+
63
+ const visibleDataProducts = expanded
64
+ ? fieldSearchResultState.dataProducts
65
+ : fieldSearchResultState.dataProducts.slice(
66
+ 0,
67
+ FieldSearchResultListItemValue.COLLAPSED_VISIBLE_DATA_PRODUCTS,
68
+ );
69
+ const hiddenDataProductCount = Math.max(
70
+ 0,
71
+ fieldSearchResultState.dataProducts.length -
72
+ FieldSearchResultListItemValue.COLLAPSED_VISIBLE_DATA_PRODUCTS,
73
+ );
74
+ const toggleLabel = expanded
75
+ ? FieldSearchResultListItemLabel.SHOW_LESS
76
+ : `+${hiddenDataProductCount} ${FieldSearchResultListItemLabel.MORE_SUFFIX}`;
77
+
78
+ const content = (
79
+ <div className="marketplace-lakehouse-field-search-results__list-item-grid">
80
+ <div className="marketplace-lakehouse-field-search-results__field-name-cell">
81
+ <Typography className="marketplace-lakehouse-field-search-results__list-item-text marketplace-lakehouse-field-search-results__list-item-text--primary">
82
+ {fieldSearchResultState.fieldName}
83
+ </Typography>
84
+ </div>
85
+ <div className="marketplace-lakehouse-field-search-results__type-cell">
86
+ <Typography className="marketplace-lakehouse-field-search-results__list-item-text">
87
+ {fieldSearchResultState.fieldType}
88
+ </Typography>
89
+ </div>
90
+ <div className="marketplace-lakehouse-field-search-results__description-cell">
91
+ <Typography className="marketplace-lakehouse-field-search-results__list-item-text">
92
+ {displayDescription}
93
+ </Typography>
94
+ {isDescriptionTruncatable && (
95
+ <button
96
+ className="marketplace-lakehouse-field-search-results__description-toggle"
97
+ onClick={(e) => {
98
+ e.stopPropagation();
99
+ setDescriptionExpanded(!descriptionExpanded);
100
+ }}
101
+ >
102
+ {descriptionExpanded ? 'Show less' : 'Show more'}
103
+ </button>
104
+ )}
105
+ </div>
106
+ <div className="marketplace-lakehouse-field-search-results__data-products-cell">
107
+ {fieldSearchResultState.dataProducts.length > 0 ? (
108
+ <>
109
+ {visibleDataProducts.map((dataProduct) => (
110
+ <Chip
111
+ key={`${fieldSearchResultState.id}-${dataProduct.path}-${dataProduct.datasetName ?? ''}`}
112
+ clickable={true}
113
+ label={
114
+ <span className="marketplace-lakehouse-field-search-results__chip-label">
115
+ <PackageIcon className="marketplace-lakehouse-field-search-results__chip-icon" />
116
+ <span>{dataProduct.name}</span>
117
+ {dataProduct.datasetName && (
118
+ <span className="marketplace-lakehouse-field-search-results__chip-secondary">
119
+ <span className="marketplace-lakehouse-field-search-results__chip-separator">
120
+ {FieldSearchResultListItemLabel.DATASET_SEPARATOR}
121
+ </span>
122
+ <DatasetIcon className="marketplace-lakehouse-field-search-results__chip-icon" />
123
+ <span className="marketplace-lakehouse-field-search-results__chip-secondary-text">
124
+ {dataProduct.datasetName}
125
+ </span>
126
+ </span>
127
+ )}
128
+ </span>
129
+ }
130
+ onClick={() => onOpenDataProduct(dataProduct)}
131
+ className="marketplace-lakehouse-field-search-results__data-product-link"
132
+ size="small"
133
+ />
134
+ ))}
135
+ {fieldSearchResultState.dataProducts.length >
136
+ FieldSearchResultListItemValue.COLLAPSED_VISIBLE_DATA_PRODUCTS && (
137
+ <Chip
138
+ key={`${fieldSearchResultState.id}-toggle`}
139
+ label={toggleLabel}
140
+ onClick={() => onToggleExpanded(fieldSearchResultState.id)}
141
+ size="small"
142
+ variant="outlined"
143
+ className="marketplace-lakehouse-field-search-results__data-product-toggle"
144
+ />
145
+ )}
146
+ </>
147
+ ) : (
148
+ <Typography className="marketplace-lakehouse-field-search-results__list-item-text marketplace-lakehouse-field-search-results__list-item-text--empty">
149
+ {FieldSearchResultListItemLabel.EMPTY_VALUE}
150
+ </Typography>
151
+ )}
152
+ </div>
153
+ </div>
154
+ );
155
+
156
+ return (
157
+ <LegendMarketplaceListItem
158
+ className="marketplace-lakehouse-field-search-results__list-item"
159
+ content={content}
160
+ />
161
+ );
162
+ },
163
+ );
@@ -181,7 +181,7 @@ const TaxonomyTreeNode: React.FC<{
181
181
  );
182
182
  });
183
183
 
184
- const FilterSection: React.FC<{
184
+ export const FilterSection: React.FC<{
185
185
  title: string;
186
186
  children: React.ReactNode;
187
187
  }> = ({ title, children }) => (
@@ -195,7 +195,7 @@ const FilterSection: React.FC<{
195
195
  </div>
196
196
  );
197
197
 
198
- const FilterCheckboxOption: React.FC<{
198
+ export const FilterCheckboxOption: React.FC<{
199
199
  label: string;
200
200
  checked: boolean;
201
201
  onChange: () => void;
@@ -33,6 +33,7 @@ export const PaginationControls = observer(
33
33
  onPageChange: (page: number) => void;
34
34
  onItemsPerPageChange: (itemsPerPage: number) => void;
35
35
  disabled?: boolean;
36
+ pageSizeOptions?: number[] | undefined;
36
37
  }) => {
37
38
  const {
38
39
  totalItems,
@@ -41,6 +42,7 @@ export const PaginationControls = observer(
41
42
  onPageChange,
42
43
  onItemsPerPageChange,
43
44
  disabled = false,
45
+ pageSizeOptions = [12, 24, 36, 48],
44
46
  } = props;
45
47
 
46
48
  const totalPages = Math.ceil(totalItems / itemsPerPage);
@@ -75,10 +77,11 @@ export const PaginationControls = observer(
75
77
  size="medium"
76
78
  disabled={disabled}
77
79
  >
78
- <MenuItem value={12}>12</MenuItem>
79
- <MenuItem value={24}>24</MenuItem>
80
- <MenuItem value={36}>36</MenuItem>
81
- <MenuItem value={48}>48</MenuItem>
80
+ {pageSizeOptions.map((option) => (
81
+ <MenuItem key={option} value={option}>
82
+ {option}
83
+ </MenuItem>
84
+ ))}
82
85
  </Select>
83
86
  </Box>
84
87
  <Box className="legend-marketplace-pagination-info">
@@ -48,6 +48,9 @@ export const LegendMarketplaceTerminalCard = observer(
48
48
  );
49
49
 
50
50
  const [modalMessage, setModalMessage] = useState<string>('');
51
+ const [modalTotalCount, setModalTotalCount] = useState<
52
+ number | null | undefined
53
+ >(undefined);
51
54
 
52
55
  const legendMarketplaceBaseStore = useLegendMarketplaceBaseStore();
53
56
  const applicationStore = legendMarketplaceBaseStore.applicationStore;
@@ -72,6 +75,7 @@ export const LegendMarketplaceTerminalCard = observer(
72
75
  if (result.recommendations && result.recommendations.length > 0) {
73
76
  setRecommendedItems(result.recommendations);
74
77
  setModalMessage(result.message);
78
+ setModalTotalCount(result.totalCount);
75
79
  setShowRecommendationsModal(true);
76
80
  }
77
81
  } catch (error) {
@@ -97,9 +101,11 @@ export const LegendMarketplaceTerminalCard = observer(
97
101
  _selectedTerminal: TerminalResult,
98
102
  recommendations: TerminalResult[],
99
103
  responseMessage: string,
104
+ totalCount?: number | null,
100
105
  ) => {
101
106
  setRecommendedItems(recommendations);
102
107
  setModalMessage(responseMessage);
108
+ setModalTotalCount(totalCount);
103
109
  setShowRecommendationsModal(true);
104
110
  },
105
111
  [],
@@ -203,6 +209,7 @@ export const LegendMarketplaceTerminalCard = observer(
203
209
  setShowModal={setShowRecommendationsModal}
204
210
  onViewCart={handleViewCart}
205
211
  onTerminalSelected={handleTerminalSelected}
212
+ totalCount={modalTotalCount}
206
213
  />
207
214
  </Card>
208
215
  );
@@ -63,13 +63,18 @@ export interface Vendor {
63
63
 
64
64
  export const LegendMarketplaceSearchBar = observer(
65
65
  (props: {
66
- onSearch?: (query: string | undefined, useProducerSearch: boolean) => void;
66
+ onSearch?: (
67
+ query: string | undefined,
68
+ useProducerSearch: boolean,
69
+ useFieldSearch: boolean,
70
+ ) => void;
67
71
  stateSearchQuery?: string | undefined;
68
72
  placeholder?: string;
69
73
  onChange?: (query: string) => void;
70
74
  className?: string | undefined;
71
75
  showSettings?: boolean;
72
76
  stateUseProducerSearch?: boolean | undefined;
77
+ stateUseFieldSearch?: boolean;
73
78
  enableAutosuggest?: boolean;
74
79
  }): JSX.Element => {
75
80
  const {
@@ -80,6 +85,7 @@ export const LegendMarketplaceSearchBar = observer(
80
85
  className,
81
86
  showSettings,
82
87
  stateUseProducerSearch,
88
+ stateUseFieldSearch,
83
89
  enableAutosuggest = true,
84
90
  } = props;
85
91
 
@@ -92,6 +98,9 @@ export const LegendMarketplaceSearchBar = observer(
92
98
  const [useProducerSearch, setUseProducerSearch] = useState(
93
99
  stateUseProducerSearch ?? false,
94
100
  );
101
+ const [useFieldSearch, setUseFieldSearch] = useState(
102
+ stateUseFieldSearch ?? false,
103
+ );
95
104
  const [searchMenuAnchorEl, setSearchMenuAnchorEl] =
96
105
  useState<HTMLElement | null>();
97
106
  const [suggestions, setSuggestions] = useState<SearchSuggestion[]>([]);
@@ -181,6 +190,10 @@ export const LegendMarketplaceSearchBar = observer(
181
190
  setUseProducerSearch(stateUseProducerSearch ?? false);
182
191
  }, [stateUseProducerSearch]);
183
192
 
193
+ useEffect(() => {
194
+ setUseFieldSearch(stateUseFieldSearch ?? false);
195
+ }, [stateUseFieldSearch]);
196
+
184
197
  useEffect(() => {
185
198
  const abortController = new AbortController();
186
199
 
@@ -241,7 +254,7 @@ export const LegendMarketplaceSearchBar = observer(
241
254
  selectedSuggestion.type === SearchSuggestionType.SEARCH_QUERY ||
242
255
  selectedSuggestion.type === SearchSuggestionType.DEFAULT
243
256
  ) {
244
- onSearch?.(selectedQuery, useProducerSearch);
257
+ onSearch?.(selectedQuery, useProducerSearch, useFieldSearch);
245
258
  LegendMarketplaceTelemetryHelper.logEvent_SearchAutosuggestSelection(
246
259
  applicationStore.telemetryService,
247
260
  selectedQuery,
@@ -274,7 +287,7 @@ export const LegendMarketplaceSearchBar = observer(
274
287
 
275
288
  const handleSubmit = (event: React.FormEvent): void => {
276
289
  event.preventDefault();
277
- onSearch?.(searchQuery, useProducerSearch);
290
+ onSearch?.(searchQuery, useProducerSearch, useFieldSearch);
278
291
  };
279
292
 
280
293
  const getOptionLabel = (option: SearchSuggestion | string): string =>
@@ -525,6 +538,9 @@ export const LegendMarketplaceSearchBar = observer(
525
538
  checked={useProducerSearch}
526
539
  onChange={(event) => {
527
540
  setUseProducerSearch(event.target.checked);
541
+ if (event.target.checked) {
542
+ setUseFieldSearch(false);
543
+ }
528
544
  LegendMarketplaceTelemetryHelper.logEvent_ToggleProducerSearch(
529
545
  applicationStore.telemetryService,
530
546
  event.target.checked,
@@ -540,6 +556,31 @@ export const LegendMarketplaceSearchBar = observer(
540
556
  }
541
557
  />
542
558
  </MenuItem>
559
+ <MenuItem>
560
+ <FormControlLabel
561
+ control={
562
+ <Switch
563
+ checked={useFieldSearch}
564
+ onChange={(event) => {
565
+ setUseFieldSearch(event.target.checked);
566
+ if (event.target.checked) {
567
+ setUseProducerSearch(false);
568
+ }
569
+ LegendMarketplaceTelemetryHelper.logEvent_ToggleFieldSearch(
570
+ applicationStore.telemetryService,
571
+ event.target.checked,
572
+ );
573
+ }}
574
+ />
575
+ }
576
+ label={
577
+ <>
578
+ Field Search{' '}
579
+ <LegendMarketplaceInfoTooltip title="Use this search to discover data products and datasets that contain a specific field" />
580
+ </>
581
+ }
582
+ />
583
+ </MenuItem>
543
584
  </Menu>
544
585
  )}
545
586
  </form>
@@ -26,6 +26,8 @@ import {
26
26
  CheckIcon,
27
27
  CubesLoadingIndicator,
28
28
  CubesLoadingIndicatorIcon,
29
+ StarIcon,
30
+ TableIcon,
29
31
  ViewHeadlineIcon,
30
32
  WindowIcon,
31
33
  clsx,
@@ -48,6 +50,7 @@ import {
48
50
  } from '../../stores/dataAPIs/LegendMarketplaceDataAPIsStore.js';
49
51
  import { LegendServiceCard } from '../../components/LegendServiceCard/LegendServiceCard.js';
50
52
  import { LegendServiceListRow } from '../../components/LegendServiceCard/LegendServiceListRow.js';
53
+ import { LegendServiceGridView } from '../../components/LegendServiceCard/LegendServiceGrid.js';
51
54
  import { PaginationControls } from '../../components/Pagination/PaginationControls.js';
52
55
  import { DataAPIsFiltersPanel } from '../../components/DataAPIsFiltersPanel/DataAPIsFiltersPanel.js';
53
56
  import { useSearchParams } from '@finos/legend-application/browser';
@@ -68,7 +71,6 @@ export const LegendMarketplaceDataAPIs = withLegendMarketplaceDataAPIsStore(
68
71
  );
69
72
 
70
73
  useEffect(() => {
71
- dataAPIsStore.setItemsPerPage(12);
72
74
  if (queryFromUrl) {
73
75
  dataAPIsStore.setSearchQuery(queryFromUrl);
74
76
  }
@@ -150,6 +152,14 @@ export const LegendMarketplaceDataAPIs = withLegendMarketplaceDataAPIsStore(
150
152
  key={serviceCardState.guid}
151
153
  serviceCardState={serviceCardState}
152
154
  onClick={() => handleServiceClick(serviceCardState)}
155
+ isFavorite={dataAPIsStore.isFavorite(
156
+ serviceCardState.service.pattern,
157
+ )}
158
+ onToggleFavorite={() =>
159
+ dataAPIsStore.toggleFavorite(
160
+ serviceCardState.service.pattern,
161
+ )
162
+ }
153
163
  />
154
164
  ))}
155
165
  </div>
@@ -163,6 +173,14 @@ export const LegendMarketplaceDataAPIs = withLegendMarketplaceDataAPIsStore(
163
173
  )}
164
174
  </>
165
175
  );
176
+ case ServicesViewMode.GRID:
177
+ return (
178
+ <LegendServiceGridView
179
+ services={dataAPIsStore.filteredSortedServices}
180
+ store={dataAPIsStore}
181
+ onRowClick={handleServiceClick}
182
+ />
183
+ );
166
184
  case ServicesViewMode.TILE:
167
185
  return (
168
186
  <Grid
@@ -176,6 +194,14 @@ export const LegendMarketplaceDataAPIs = withLegendMarketplaceDataAPIsStore(
176
194
  <LegendServiceCard
177
195
  serviceCardState={serviceCardState}
178
196
  onClick={() => handleServiceClick(serviceCardState)}
197
+ isFavorite={dataAPIsStore.isFavorite(
198
+ serviceCardState.service.pattern,
199
+ )}
200
+ onToggleFavorite={() =>
201
+ dataAPIsStore.toggleFavorite(
202
+ serviceCardState.service.pattern,
203
+ )
204
+ }
179
205
  />
180
206
  </Grid>
181
207
  ))}
@@ -231,11 +257,34 @@ export const LegendMarketplaceDataAPIs = withLegendMarketplaceDataAPIsStore(
231
257
  label="My Services"
232
258
  />
233
259
  <span className="legend-marketplace-search-results__sort-bar__controls-divider" />
234
- <div className="legend-marketplace-search-results__view-toggle">
260
+ <IconButton
261
+ className={clsx(
262
+ 'legend-marketplace-search-results__favorites-toggle',
263
+ dataAPIsStore.showFavoritesOnly &&
264
+ 'legend-marketplace-search-results__favorites-toggle--active',
265
+ )}
266
+ onClick={() =>
267
+ dataAPIsStore.setShowFavoritesOnly(
268
+ !dataAPIsStore.showFavoritesOnly,
269
+ )
270
+ }
271
+ title={
272
+ dataAPIsStore.showFavoritesOnly
273
+ ? 'Show all services'
274
+ : 'Show favorites only'
275
+ }
276
+ size="small"
277
+ >
278
+ <StarIcon />
279
+ </IconButton>
280
+ <span className="legend-marketplace-search-results__sort-bar__controls-divider" />
281
+ <div className="legend-marketplace-search-results__view-toggle legend-marketplace-search-results__view-toggle--three">
235
282
  <div
236
283
  className={clsx(
237
284
  'legend-marketplace-search-results__view-toggle__slider',
238
285
  viewMode === ServicesViewMode.LIST &&
286
+ 'legend-marketplace-search-results__view-toggle__slider--middle',
287
+ viewMode === ServicesViewMode.GRID &&
239
288
  'legend-marketplace-search-results__view-toggle__slider--right',
240
289
  )}
241
290
  />
@@ -247,8 +296,6 @@ export const LegendMarketplaceDataAPIs = withLegendMarketplaceDataAPIsStore(
247
296
  )}
248
297
  onClick={() => {
249
298
  dataAPIsStore.setViewMode(ServicesViewMode.TILE);
250
- dataAPIsStore.setItemsPerPage(12);
251
- dataAPIsStore.setPage(1);
252
299
  LegendMarketplaceTelemetryHelper.logEvent_ToggleServicesViewMode(
253
300
  applicationStore.telemetryService,
254
301
  ServicesViewMode.TILE,
@@ -267,8 +314,6 @@ export const LegendMarketplaceDataAPIs = withLegendMarketplaceDataAPIsStore(
267
314
  )}
268
315
  onClick={() => {
269
316
  dataAPIsStore.setViewMode(ServicesViewMode.LIST);
270
- dataAPIsStore.setItemsPerPage(12);
271
- dataAPIsStore.setPage(1);
272
317
  LegendMarketplaceTelemetryHelper.logEvent_ToggleServicesViewMode(
273
318
  applicationStore.telemetryService,
274
319
  ServicesViewMode.LIST,
@@ -279,6 +324,24 @@ export const LegendMarketplaceDataAPIs = withLegendMarketplaceDataAPIsStore(
279
324
  >
280
325
  <ViewHeadlineIcon />
281
326
  </IconButton>
327
+ <IconButton
328
+ className={clsx(
329
+ 'legend-marketplace-search-results__view-toggle__btn',
330
+ viewMode === ServicesViewMode.GRID &&
331
+ 'legend-marketplace-search-results__view-toggle__btn--active',
332
+ )}
333
+ onClick={() => {
334
+ dataAPIsStore.setViewMode(ServicesViewMode.GRID);
335
+ LegendMarketplaceTelemetryHelper.logEvent_ToggleServicesViewMode(
336
+ applicationStore.telemetryService,
337
+ ServicesViewMode.GRID,
338
+ );
339
+ }}
340
+ title="Grid View"
341
+ size="small"
342
+ >
343
+ <TableIcon />
344
+ </IconButton>
282
345
  </div>
283
346
  <span className="legend-marketplace-search-results__sort-bar__controls-divider" />
284
347
  <FormControl>
@@ -349,14 +412,17 @@ export const LegendMarketplaceDataAPIs = withLegendMarketplaceDataAPIsStore(
349
412
  >
350
413
  {renderServiceView()}
351
414
  </div>
352
- <PaginationControls
353
- totalItems={dataAPIsStore.totalFilteredCount}
354
- itemsPerPage={dataAPIsStore.itemsPerPage}
355
- page={dataAPIsStore.page}
356
- onPageChange={handlePageChange}
357
- onItemsPerPageChange={handleItemsPerPageChange}
358
- disabled={dataAPIsStore.isLoading}
359
- />
415
+ {viewMode !== ServicesViewMode.GRID && (
416
+ <PaginationControls
417
+ totalItems={dataAPIsStore.totalFilteredCount}
418
+ itemsPerPage={dataAPIsStore.itemsPerPage}
419
+ page={dataAPIsStore.page}
420
+ onPageChange={handlePageChange}
421
+ onItemsPerPageChange={handleItemsPerPageChange}
422
+ disabled={dataAPIsStore.isLoading}
423
+ pageSizeOptions={[12, 25, 50, 100]}
424
+ />
425
+ )}
360
426
  </>
361
427
  )}
362
428
  </div>
@@ -25,7 +25,10 @@ import {
25
25
  CubesLoadingIndicator,
26
26
  CubesLoadingIndicatorIcon,
27
27
  } from '@finos/legend-art';
28
- import { generateLakehouseSearchResultsRoute } from '../../__lib__/LegendMarketplaceNavigation.js';
28
+ import {
29
+ generateFieldSearchResultsRoute,
30
+ generateLakehouseSearchResultsRoute,
31
+ } from '../../__lib__/LegendMarketplaceNavigation.js';
29
32
  import {
30
33
  assertErrorThrown,
31
34
  isNonEmptyString,
@@ -46,6 +49,8 @@ import { logClickingDataProductCard } from '../../utils/LogUtils.js';
46
49
  import { LakehouseProductCard } from '../../components/LakehouseProductCard/LakehouseProductCard.js';
47
50
  import type { HomePageBannerConfig } from '../../application/LegendMarketplaceApplicationPlugin.js';
48
51
 
52
+ const TRENDING_DATA_PRODUCTS = 4;
53
+
49
54
  export const MarketplaceLakehouseHome = observer(() => {
50
55
  const legendMarketplaceBaseStore = useLegendMarketplaceBaseStore();
51
56
  const applicationStore = legendMarketplaceBaseStore.applicationStore;
@@ -138,7 +143,11 @@ export const MarketplaceLakehouseHome = observer(() => {
138
143
  const result: Record<string, ProductCardState[]> = {
139
144
  ...configDataProducts,
140
145
  };
141
- if (trendingDataProducts) {
146
+ if (
147
+ trendingDataProducts &&
148
+ Object.values(trendingDataProducts).flat().length >=
149
+ TRENDING_DATA_PRODUCTS
150
+ ) {
142
151
  Object.assign(result, trendingDataProducts);
143
152
  }
144
153
 
@@ -211,16 +220,20 @@ export const MarketplaceLakehouseHome = observer(() => {
211
220
  const handleSearch = (
212
221
  _query: string | undefined,
213
222
  _useProducerSearch: boolean,
223
+ _useFieldSearch: boolean,
214
224
  ): void => {
215
225
  if (isNonEmptyString(_query)) {
216
226
  applicationStore.navigationService.navigator.goToLocation(
217
- generateLakehouseSearchResultsRoute(_query, _useProducerSearch),
227
+ _useFieldSearch
228
+ ? generateFieldSearchResultsRoute(_query)
229
+ : generateLakehouseSearchResultsRoute(_query, _useProducerSearch),
218
230
  );
219
231
  LegendMarketplaceTelemetryHelper.logEvent_SearchQuery(
220
232
  applicationStore.telemetryService,
221
233
  _query,
222
234
  _useProducerSearch,
223
235
  LEGEND_MARKETPLACE_PAGE.HOME_PAGE,
236
+ _useFieldSearch,
224
237
  );
225
238
  }
226
239
  };