@finos/legend-application-marketplace 0.2.20 → 0.2.22
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.
- package/lib/__lib__/LegendMarketplaceNavigation.d.ts +1 -0
- package/lib/__lib__/LegendMarketplaceNavigation.d.ts.map +1 -1
- package/lib/__lib__/LegendMarketplaceNavigation.js +4 -0
- package/lib/__lib__/LegendMarketplaceNavigation.js.map +1 -1
- package/lib/application/LegendMarketplaceApplicationConfig.d.ts +6 -3
- package/lib/application/LegendMarketplaceApplicationConfig.d.ts.map +1 -1
- package/lib/application/LegendMarketplaceApplicationConfig.js +13 -4
- package/lib/application/LegendMarketplaceApplicationConfig.js.map +1 -1
- package/lib/components/OptionSelector/LegendMarketplaceOptionSelector.d.ts +22 -0
- package/lib/components/OptionSelector/LegendMarketplaceOptionSelector.d.ts.map +1 -0
- package/lib/components/OptionSelector/LegendMarketplaceOptionSelector.js +28 -0
- package/lib/components/OptionSelector/LegendMarketplaceOptionSelector.js.map +1 -0
- package/lib/components/Pagination/PaginationControls.d.ts.map +1 -1
- package/lib/components/Pagination/PaginationControls.js +2 -2
- package/lib/components/Pagination/PaginationControls.js.map +1 -1
- package/lib/components/ProviderCard/LegendMarketplaceOrderProfileCard.d.ts +23 -0
- package/lib/components/ProviderCard/LegendMarketplaceOrderProfileCard.d.ts.map +1 -0
- package/lib/components/ProviderCard/LegendMarketplaceOrderProfileCard.js +93 -0
- package/lib/components/ProviderCard/LegendMarketplaceOrderProfileCard.js.map +1 -0
- package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.d.ts.map +1 -1
- package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.js +5 -11
- package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.js.map +1 -1
- package/lib/components/ProviderCard/OrderProfileDetailModal.d.ts +26 -0
- package/lib/components/ProviderCard/OrderProfileDetailModal.d.ts.map +1 -0
- package/lib/components/ProviderCard/OrderProfileDetailModal.js +36 -0
- package/lib/components/ProviderCard/OrderProfileDetailModal.js.map +1 -0
- package/lib/components/ProviderCard/OrderProfileMultiselectModal.d.ts +26 -0
- package/lib/components/ProviderCard/OrderProfileMultiselectModal.d.ts.map +1 -0
- package/lib/components/ProviderCard/OrderProfileMultiselectModal.js +31 -0
- package/lib/components/ProviderCard/OrderProfileMultiselectModal.js.map +1 -0
- package/lib/components/ProviderCard/orderProfileUtils.d.ts +71 -0
- package/lib/components/ProviderCard/orderProfileUtils.d.ts.map +1 -0
- package/lib/components/ProviderCard/orderProfileUtils.js +122 -0
- package/lib/components/ProviderCard/orderProfileUtils.js.map +1 -0
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/pages/Lakehouse/entitlements/LakehouseDataContract.d.ts.map +1 -1
- package/lib/pages/Lakehouse/entitlements/LakehouseDataContract.js +2 -2
- package/lib/pages/Lakehouse/entitlements/LakehouseDataContract.js.map +1 -1
- package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.d.ts.map +1 -1
- package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.js +5 -2
- package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.js.map +1 -1
- package/lib/pages/Lakehouse/entitlements/WorkflowDataAccessRequest.d.ts.map +1 -1
- package/lib/pages/Lakehouse/entitlements/WorkflowDataAccessRequest.js +2 -2
- package/lib/pages/Lakehouse/entitlements/WorkflowDataAccessRequest.js.map +1 -1
- package/lib/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.d.ts.map +1 -1
- package/lib/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.js +11 -2
- package/lib/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.js.map +1 -1
- package/lib/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.d.ts.map +1 -1
- package/lib/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.js +9 -3
- package/lib/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.js.map +1 -1
- package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.d.ts.map +1 -1
- package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.js +45 -18
- package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.js.map +1 -1
- package/lib/stores/LegendMarketPlaceVendorDataStore.d.ts +6 -2
- package/lib/stores/LegendMarketPlaceVendorDataStore.d.ts.map +1 -1
- package/lib/stores/LegendMarketPlaceVendorDataStore.js +25 -2
- package/lib/stores/LegendMarketPlaceVendorDataStore.js.map +1 -1
- package/lib/stores/LegendMarketplaceBaseStore.d.ts +1 -1
- package/lib/stores/LegendMarketplaceBaseStore.d.ts.map +1 -1
- package/lib/stores/LegendMarketplaceBaseStore.js +10 -5
- package/lib/stores/LegendMarketplaceBaseStore.js.map +1 -1
- package/lib/stores/ai/LegendMarketplaceAIChatStore.d.ts +10 -0
- package/lib/stores/ai/LegendMarketplaceAIChatStore.d.ts.map +1 -1
- package/lib/stores/ai/LegendMarketplaceAIChatStore.js +115 -50
- package/lib/stores/ai/LegendMarketplaceAIChatStore.js.map +1 -1
- package/lib/stores/cart/CartStore.d.ts +14 -2
- package/lib/stores/cart/CartStore.d.ts.map +1 -1
- package/lib/stores/cart/CartStore.js +68 -5
- package/lib/stores/cart/CartStore.js.map +1 -1
- package/lib/stores/lakehouse/LegendMarketplaceSearchResultsStore.d.ts +4 -0
- package/lib/stores/lakehouse/LegendMarketplaceSearchResultsStore.d.ts.map +1 -1
- package/lib/stores/lakehouse/LegendMarketplaceSearchResultsStore.js +5 -0
- package/lib/stores/lakehouse/LegendMarketplaceSearchResultsStore.js.map +1 -1
- package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.d.ts +2 -1
- package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.d.ts.map +1 -1
- package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.js +8 -3
- package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.js.map +1 -1
- package/lib/utils/SearchUtils.d.ts.map +1 -1
- package/lib/utils/SearchUtils.js +17 -2
- package/lib/utils/SearchUtils.js.map +1 -1
- package/package.json +11 -11
- package/src/__lib__/LegendMarketplaceNavigation.ts +9 -0
- package/src/application/LegendMarketplaceApplicationConfig.ts +19 -11
- package/src/components/OptionSelector/LegendMarketplaceOptionSelector.tsx +50 -0
- package/src/components/Pagination/PaginationControls.tsx +19 -17
- package/src/components/ProviderCard/LegendMarketplaceOrderProfileCard.tsx +246 -0
- package/src/components/ProviderCard/LegendMarketplaceTerminalCard.tsx +9 -16
- package/src/components/ProviderCard/OrderProfileDetailModal.tsx +224 -0
- package/src/components/ProviderCard/OrderProfileMultiselectModal.tsx +142 -0
- package/src/components/ProviderCard/orderProfileUtils.ts +165 -0
- package/src/pages/Lakehouse/entitlements/LakehouseDataContract.tsx +4 -0
- package/src/pages/Lakehouse/entitlements/PermitDataAccessRequest.tsx +7 -0
- package/src/pages/Lakehouse/entitlements/WorkflowDataAccessRequest.tsx +4 -0
- package/src/pages/Lakehouse/searchResults/LegendMarketplaceFieldSearchResults.tsx +24 -25
- package/src/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.tsx +19 -29
- package/src/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.tsx +177 -44
- package/src/stores/LegendMarketPlaceVendorDataStore.tsx +33 -1
- package/src/stores/LegendMarketplaceBaseStore.ts +13 -9
- package/src/stores/ai/LegendMarketplaceAIChatStore.ts +273 -69
- package/src/stores/cart/CartStore.ts +90 -4
- package/src/stores/lakehouse/LegendMarketplaceSearchResultsStore.ts +5 -0
- package/src/stores/lakehouse/entitlements/EntitlementsDashboardState.ts +10 -1
- package/src/utils/SearchUtils.tsx +33 -8
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,142 @@
|
|
|
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 { type JSX, useCallback, useState } from 'react';
|
|
18
|
+
import {
|
|
19
|
+
Box,
|
|
20
|
+
Button,
|
|
21
|
+
Dialog,
|
|
22
|
+
DialogActions,
|
|
23
|
+
DialogContent,
|
|
24
|
+
DialogTitle,
|
|
25
|
+
List,
|
|
26
|
+
ListItemButton,
|
|
27
|
+
ListItemText,
|
|
28
|
+
Radio,
|
|
29
|
+
Typography,
|
|
30
|
+
} from '@mui/material';
|
|
31
|
+
import type {
|
|
32
|
+
TraderProfile,
|
|
33
|
+
TraderProfileItem,
|
|
34
|
+
} from '@finos/legend-server-marketplace';
|
|
35
|
+
import { observer } from 'mobx-react-lite';
|
|
36
|
+
import { formatItemPrice, OrderProfileLabel } from './orderProfileUtils.js';
|
|
37
|
+
|
|
38
|
+
export const OrderProfileMultiselectModal = observer(
|
|
39
|
+
(props: {
|
|
40
|
+
profile: TraderProfile;
|
|
41
|
+
open: boolean;
|
|
42
|
+
onClose: () => void;
|
|
43
|
+
onConfirm: (selectedTerminals: TraderProfileItem[]) => void;
|
|
44
|
+
}): JSX.Element => {
|
|
45
|
+
const { profile, open, onClose, onConfirm } = props;
|
|
46
|
+
const terminalItems = profile.items.filter(
|
|
47
|
+
(item) => item.isTerminal && !item.isOwned,
|
|
48
|
+
);
|
|
49
|
+
const [selectedId, setSelectedId] = useState<number | null>(null);
|
|
50
|
+
|
|
51
|
+
const handleConfirm = useCallback((): void => {
|
|
52
|
+
const selectedItem = terminalItems.find((item) => item.id === selectedId);
|
|
53
|
+
onConfirm(selectedItem ? [selectedItem] : []);
|
|
54
|
+
}, [terminalItems, selectedId, onConfirm]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Dialog
|
|
58
|
+
open={open}
|
|
59
|
+
onClose={onClose}
|
|
60
|
+
maxWidth="md"
|
|
61
|
+
fullWidth={true}
|
|
62
|
+
className="order-profile-multiselect-modal"
|
|
63
|
+
aria-labelledby="order-profile-multiselect-title"
|
|
64
|
+
>
|
|
65
|
+
<DialogTitle id="order-profile-multiselect-title">
|
|
66
|
+
{OrderProfileLabel.SELECT_TERMINAL_TITLE}
|
|
67
|
+
</DialogTitle>
|
|
68
|
+
<DialogContent dividers={true}>
|
|
69
|
+
<Typography
|
|
70
|
+
variant="body2"
|
|
71
|
+
className="order-profile-multiselect-modal__description"
|
|
72
|
+
>
|
|
73
|
+
Choose one terminal to include from{' '}
|
|
74
|
+
<strong>{profile.productName}</strong>.{' '}
|
|
75
|
+
{OrderProfileLabel.SELECT_TERMINAL_DESCRIPTION}
|
|
76
|
+
</Typography>
|
|
77
|
+
<List dense={false}>
|
|
78
|
+
{terminalItems.map((item) => (
|
|
79
|
+
<ListItemButton
|
|
80
|
+
key={item.id}
|
|
81
|
+
onClick={() => setSelectedId(item.id)}
|
|
82
|
+
selected={selectedId === item.id}
|
|
83
|
+
className={`order-profile-multiselect-modal__list-item ${selectedId === item.id ? 'order-profile-multiselect-modal__list-item--selected' : ''}`}
|
|
84
|
+
>
|
|
85
|
+
<Radio
|
|
86
|
+
checked={selectedId === item.id}
|
|
87
|
+
onChange={() => setSelectedId(item.id)}
|
|
88
|
+
size="small"
|
|
89
|
+
className="order-profile-multiselect-modal__radio"
|
|
90
|
+
inputProps={{ 'aria-labelledby': `terminal-item-${item.id}` }}
|
|
91
|
+
/>
|
|
92
|
+
<ListItemText
|
|
93
|
+
id={`terminal-item-${item.id}`}
|
|
94
|
+
disableTypography={true}
|
|
95
|
+
primary={
|
|
96
|
+
<Box className="order-profile-multiselect-modal__item-primary">
|
|
97
|
+
<Typography
|
|
98
|
+
variant="body1"
|
|
99
|
+
className="order-profile-multiselect-modal__item-name"
|
|
100
|
+
>
|
|
101
|
+
{item.productName}
|
|
102
|
+
</Typography>
|
|
103
|
+
<Typography
|
|
104
|
+
variant="body2"
|
|
105
|
+
className="order-profile-multiselect-modal__item-price"
|
|
106
|
+
>
|
|
107
|
+
{formatItemPrice(item.price)}
|
|
108
|
+
</Typography>
|
|
109
|
+
</Box>
|
|
110
|
+
}
|
|
111
|
+
secondary={
|
|
112
|
+
item.model !== null && item.model !== undefined ? (
|
|
113
|
+
<Typography
|
|
114
|
+
variant="caption"
|
|
115
|
+
className="order-profile-multiselect-modal__item-model"
|
|
116
|
+
>
|
|
117
|
+
{OrderProfileLabel.MODEL_PREFIX}
|
|
118
|
+
{item.model}
|
|
119
|
+
</Typography>
|
|
120
|
+
) : undefined
|
|
121
|
+
}
|
|
122
|
+
/>
|
|
123
|
+
</ListItemButton>
|
|
124
|
+
))}
|
|
125
|
+
</List>
|
|
126
|
+
</DialogContent>
|
|
127
|
+
<DialogActions>
|
|
128
|
+
<Button onClick={onClose} variant="outlined">
|
|
129
|
+
{OrderProfileLabel.CANCEL}
|
|
130
|
+
</Button>
|
|
131
|
+
<Button
|
|
132
|
+
onClick={handleConfirm}
|
|
133
|
+
variant="contained"
|
|
134
|
+
disabled={selectedId === null}
|
|
135
|
+
>
|
|
136
|
+
{OrderProfileLabel.ADD_TO_CART}
|
|
137
|
+
</Button>
|
|
138
|
+
</DialogActions>
|
|
139
|
+
</Dialog>
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
);
|
|
@@ -0,0 +1,165 @@
|
|
|
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 type { TraderProfileItem } from '@finos/legend-server-marketplace';
|
|
18
|
+
import { MAX_PRODUCT_IMAGE_COUNT } from '../../stores/lakehouse/dataProducts/ProductCardState.js';
|
|
19
|
+
|
|
20
|
+
// ─── String labels ───────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export enum OrderProfileLabel {
|
|
23
|
+
CHIP_LABEL = 'Order Profile',
|
|
24
|
+
ALREADY_HAVE_ACCESS = 'Already have access',
|
|
25
|
+
ADD_TO_CART = 'Add to cart',
|
|
26
|
+
IN_CART = 'In Cart',
|
|
27
|
+
ADDING = 'Adding...',
|
|
28
|
+
SELECT_TERMINAL_TITLE = 'Select Terminal',
|
|
29
|
+
SELECT_TERMINAL_DESCRIPTION = 'All Add-Ons will be added automatically after the terminal is confirmed.',
|
|
30
|
+
CANCEL = 'Cancel',
|
|
31
|
+
CLOSE = 'Close',
|
|
32
|
+
OWNED_SUFFIX = '(Owned)',
|
|
33
|
+
IN_CART_SUFFIX = '(In Cart)',
|
|
34
|
+
MODEL_PREFIX = 'Model: ',
|
|
35
|
+
PRICE_TOTAL_SEPARATOR = ' · Total: ',
|
|
36
|
+
VIEW_DETAILS = 'View details',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export enum OrderProfileTableHeader {
|
|
40
|
+
PRODUCT_NAME = 'PRODUCT NAME',
|
|
41
|
+
PROVIDER = 'PROVIDER',
|
|
42
|
+
CATEGORY = 'CATEGORY',
|
|
43
|
+
COST_MONTHLY = 'COST (Monthly)',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── Image URL ──────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns a stable random product image URL for a given asset base URL.
|
|
50
|
+
* Intended to be called once inside a `useState` initialiser so the image
|
|
51
|
+
* does not change on re-renders.
|
|
52
|
+
*/
|
|
53
|
+
export const getRandomImageUrl = (assetUrl: string): string => {
|
|
54
|
+
const randomValue = crypto.getRandomValues(new Uint32Array(1))[0] ?? 0;
|
|
55
|
+
const randomIndex = (randomValue % MAX_PRODUCT_IMAGE_COUNT) + 1;
|
|
56
|
+
return `${assetUrl}/images${randomIndex}.jpg`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// ─── Price formatting ─────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
const USD_FORMATTER = new Intl.NumberFormat('en-US', {
|
|
62
|
+
style: 'currency',
|
|
63
|
+
currency: 'USD',
|
|
64
|
+
minimumFractionDigits: 2,
|
|
65
|
+
maximumFractionDigits: 2,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/** Formats a price as a plain USD string, e.g. "$1,234.56". Used in detail tables. */
|
|
69
|
+
export const formatItemPrice = (price: number): string =>
|
|
70
|
+
USD_FORMATTER.format(price);
|
|
71
|
+
|
|
72
|
+
/** Formats a price with a "/month" suffix for display on cards. */
|
|
73
|
+
export const formatCardPrice = (price: number): string =>
|
|
74
|
+
`${USD_FORMATTER.format(price)}/month`;
|
|
75
|
+
|
|
76
|
+
// ─── Toast message formatters ────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
export const formatAddToCartSuccessMessage = (productName: string): string =>
|
|
79
|
+
`Order profile ${productName} has been successfully added to cart.`;
|
|
80
|
+
|
|
81
|
+
export const formatAddToCartErrorMessage = (
|
|
82
|
+
productName: string,
|
|
83
|
+
errorMessage: string,
|
|
84
|
+
): string => `Failed to add ${productName} to cart: ${errorMessage}`;
|
|
85
|
+
|
|
86
|
+
// ─── Item summary helpers ─────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
export const getItemSummary = (
|
|
89
|
+
items: TraderProfileItem[],
|
|
90
|
+
): { terminalCount: number; addOnCount: number } => {
|
|
91
|
+
const terminalCount = items.filter((item) => item.isTerminal).length;
|
|
92
|
+
return { terminalCount, addOnCount: items.length - terminalCount };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const formatProfileSummaryLine = (
|
|
96
|
+
terminalCount: number,
|
|
97
|
+
addOnCount: number,
|
|
98
|
+
): string => {
|
|
99
|
+
const terminalLabel =
|
|
100
|
+
terminalCount === 1 ? '1 Terminal' : `${terminalCount} Terminals`;
|
|
101
|
+
const addOnLabel = addOnCount === 1 ? '1 Add-On' : `${addOnCount} Add-Ons`;
|
|
102
|
+
return `${terminalLabel} · ${addOnLabel}`;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// ─── Multiselect price calculation ──────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Calculates the total price for a multiselect order profile.
|
|
109
|
+
* Finds the highest-priced terminal and sums it with its associated add-ons.
|
|
110
|
+
* Returns `undefined` when there are no terminal items.
|
|
111
|
+
*/
|
|
112
|
+
export const calculateMultiselectTotalPrice = (
|
|
113
|
+
items: TraderProfileItem[],
|
|
114
|
+
): number | undefined => {
|
|
115
|
+
const terminals = items.filter((item) => item.isTerminal && !item.isOwned);
|
|
116
|
+
if (terminals.length === 0) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
const highestTerminal = terminals.reduce((max, curr) =>
|
|
120
|
+
curr.price > max.price ? curr : max,
|
|
121
|
+
);
|
|
122
|
+
const addOns = items.filter(
|
|
123
|
+
(item) =>
|
|
124
|
+
!item.isTerminal && !item.isOwned && item.model === highestTerminal.model,
|
|
125
|
+
);
|
|
126
|
+
const addOnsTotal = addOns.reduce((sum, item) => sum + item.price, 0);
|
|
127
|
+
return highestTerminal.price + addOnsTotal;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// ─── Grouping ─────────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Groups items so that each vendor-profile (terminal) is immediately followed
|
|
134
|
+
* by its associated add-ons (matched by item.model). Unmatched add-ons are
|
|
135
|
+
* appended at the end.
|
|
136
|
+
*/
|
|
137
|
+
export const groupOrderProfileItems = (
|
|
138
|
+
items: TraderProfileItem[],
|
|
139
|
+
): { item: TraderProfileItem; isSubItem: boolean }[] => {
|
|
140
|
+
const result: { item: TraderProfileItem; isSubItem: boolean }[] = [];
|
|
141
|
+
|
|
142
|
+
const terminals = items.filter((i) => i.isTerminal);
|
|
143
|
+
const addOns = items.filter((i) => !i.isTerminal);
|
|
144
|
+
const matchedAddonIds = new Set<number>();
|
|
145
|
+
|
|
146
|
+
for (const terminal of terminals) {
|
|
147
|
+
result.push({ item: terminal, isSubItem: false });
|
|
148
|
+
if (terminal.model !== undefined && terminal.model !== null) {
|
|
149
|
+
for (const addon of addOns) {
|
|
150
|
+
if (addon.model === terminal.model && !matchedAddonIds.has(addon.id)) {
|
|
151
|
+
result.push({ item: addon, isSubItem: true });
|
|
152
|
+
matchedAddonIds.add(addon.id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (const addon of addOns) {
|
|
159
|
+
if (!matchedAddonIds.has(addon.id)) {
|
|
160
|
+
result.push({ item: addon, isSubItem: false });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
};
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
import {
|
|
47
47
|
DataAccessRequestContent,
|
|
48
48
|
DataContractViewerState,
|
|
49
|
+
LakehouseResiliencyDisclaimer,
|
|
49
50
|
} from '@finos/legend-extension-dsl-data-product';
|
|
50
51
|
|
|
51
52
|
export const LakehouseDataContractTask =
|
|
@@ -336,6 +337,9 @@ export const LakehouseDataContractTask =
|
|
|
336
337
|
</Button>
|
|
337
338
|
</Box>
|
|
338
339
|
)}
|
|
340
|
+
<LakehouseResiliencyDisclaimer
|
|
341
|
+
applicationStore={marketplaceBaseStore.applicationStore}
|
|
342
|
+
/>
|
|
339
343
|
<DataAccessRequestContent
|
|
340
344
|
viewerState={contractViewerState}
|
|
341
345
|
getDataProductUrl={(
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
} from '@finos/legend-art';
|
|
37
37
|
import {
|
|
38
38
|
DataAccessRequestContent,
|
|
39
|
+
LakehouseResiliencyDisclaimer,
|
|
39
40
|
PermitDataAccessRequestState,
|
|
40
41
|
} from '@finos/legend-extension-dsl-data-product';
|
|
41
42
|
import { flowResult } from 'mobx';
|
|
@@ -74,6 +75,9 @@ export const PermitDataAccessRequestTask =
|
|
|
74
75
|
marketplaceBaseStore.applicationStore.pluginManager;
|
|
75
76
|
const permitClient =
|
|
76
77
|
marketplaceBaseStore.permitWorkflowServerClient;
|
|
78
|
+
if (!permitClient) {
|
|
79
|
+
throw new Error('Permit workflow server is not configured');
|
|
80
|
+
}
|
|
77
81
|
|
|
78
82
|
const state = new PermitDataAccessRequestState(
|
|
79
83
|
dataAccessRequestId,
|
|
@@ -221,6 +225,9 @@ export const PermitDataAccessRequestTask =
|
|
|
221
225
|
</Button>
|
|
222
226
|
</Box>
|
|
223
227
|
)}
|
|
228
|
+
<LakehouseResiliencyDisclaimer
|
|
229
|
+
applicationStore={marketplaceBaseStore.applicationStore}
|
|
230
|
+
/>
|
|
224
231
|
<DataAccessRequestContent
|
|
225
232
|
viewerState={permitState}
|
|
226
233
|
getDataProductUrl={(
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
} from '@finos/legend-art';
|
|
41
41
|
import {
|
|
42
42
|
DataAccessRequestContent,
|
|
43
|
+
LakehouseResiliencyDisclaimer,
|
|
43
44
|
WorkflowDataAccessRequestState,
|
|
44
45
|
} from '@finos/legend-extension-dsl-data-product';
|
|
45
46
|
import { flowResult } from 'mobx';
|
|
@@ -245,6 +246,9 @@ export const WorkflowDataAccessRequestTask =
|
|
|
245
246
|
</Button>
|
|
246
247
|
</Box>
|
|
247
248
|
)}
|
|
249
|
+
<LakehouseResiliencyDisclaimer
|
|
250
|
+
applicationStore={marketplaceBaseStore.applicationStore}
|
|
251
|
+
/>
|
|
248
252
|
<DataAccessRequestContent
|
|
249
253
|
viewerState={workflowState}
|
|
250
254
|
getDataProductUrl={(
|
|
@@ -42,10 +42,14 @@ import {
|
|
|
42
42
|
} from '../../../application/providers/LegendMarketplaceFieldSearchResultsStoreProvider.js';
|
|
43
43
|
import { FieldSearchFiltersPanel } from '../../../components/FieldSearchFiltersPanel/FieldSearchFiltersPanel.js';
|
|
44
44
|
import { FieldSearchResultListRow } from '../../../components/MarketplaceCard/FieldSearchResultListItem.js';
|
|
45
|
+
import { LegendMarketplaceOptionSelector } from '../../../components/OptionSelector/LegendMarketplaceOptionSelector.js';
|
|
45
46
|
import { LegendMarketplaceSearchBar } from '../../../components/SearchBar/LegendMarketplaceSearchBar.js';
|
|
46
47
|
import { PaginationControls } from '../../../components/Pagination/PaginationControls.js';
|
|
47
48
|
import { LegendMarketplacePage } from '../../LegendMarketplacePage.js';
|
|
48
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
DataProductTypeFilter,
|
|
51
|
+
SearchResultViewOption,
|
|
52
|
+
} from '../../../stores/lakehouse/LegendMarketplaceSearchResultsStore.js';
|
|
49
53
|
import type { LegendMarketplaceFieldSearchResultsStore } from '../../../stores/lakehouse/LegendMarketplaceFieldSearchResultsStore.js';
|
|
50
54
|
import { type FieldSearchDataProductEntry } from '../../../stores/lakehouse/fieldSearch/FieldSearchResultState.js';
|
|
51
55
|
|
|
@@ -371,6 +375,15 @@ const LegendMarketplaceFieldSearchResultsPage = observer(() => {
|
|
|
371
375
|
);
|
|
372
376
|
}, [applicationStore, fieldSearchResultsStore.searchQuery]);
|
|
373
377
|
|
|
378
|
+
const handleSearchResultViewChange = useCallback(
|
|
379
|
+
(value: SearchResultViewOption) => {
|
|
380
|
+
if (value === SearchResultViewOption.DATA_PRODUCTS) {
|
|
381
|
+
handleOpenDataProductsTab();
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
[handleOpenDataProductsTab],
|
|
385
|
+
);
|
|
386
|
+
|
|
374
387
|
return (
|
|
375
388
|
<LegendMarketplacePage className="marketplace-lakehouse-search-results marketplace-lakehouse-field-search-results">
|
|
376
389
|
<div ref={pageRef} />
|
|
@@ -394,30 +407,16 @@ const LegendMarketplaceFieldSearchResultsPage = observer(() => {
|
|
|
394
407
|
>
|
|
395
408
|
{fieldSearchResultsStore.totalFieldMatches} Fields
|
|
396
409
|
</Typography>
|
|
397
|
-
<div
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
className="legend-marketplace-search-results__search-type-tab"
|
|
408
|
-
onClick={handleOpenDataProductsTab}
|
|
409
|
-
>
|
|
410
|
-
Data Products
|
|
411
|
-
</button>
|
|
412
|
-
<button
|
|
413
|
-
type="button"
|
|
414
|
-
role="tab"
|
|
415
|
-
aria-selected={true}
|
|
416
|
-
tabIndex={0}
|
|
417
|
-
className="legend-marketplace-search-results__search-type-tab legend-marketplace-search-results__search-type-tab--active"
|
|
418
|
-
>
|
|
419
|
-
Data Fields
|
|
420
|
-
</button>
|
|
410
|
+
<div className="legend-marketplace-search-results__search-type-tabs">
|
|
411
|
+
<LegendMarketplaceOptionSelector
|
|
412
|
+
options={[
|
|
413
|
+
SearchResultViewOption.DATA_PRODUCTS,
|
|
414
|
+
SearchResultViewOption.DATA_FIELDS,
|
|
415
|
+
]}
|
|
416
|
+
selectedOption={SearchResultViewOption.DATA_FIELDS}
|
|
417
|
+
onChange={handleSearchResultViewChange}
|
|
418
|
+
ariaLabel="Search result type"
|
|
419
|
+
/>
|
|
421
420
|
</div>
|
|
422
421
|
</div>
|
|
423
422
|
</div>
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
} from '@mui/material';
|
|
43
43
|
import {
|
|
44
44
|
DataProductSort,
|
|
45
|
+
SearchResultViewOption,
|
|
45
46
|
SearchResultsViewMode,
|
|
46
47
|
type LegendMarketplaceSearchResultsStore,
|
|
47
48
|
} from '../../../stores/lakehouse/LegendMarketplaceSearchResultsStore.js';
|
|
@@ -66,6 +67,7 @@ import { useSearchParams } from '@finos/legend-application/browser';
|
|
|
66
67
|
import { isNonEmptyString } from '@finos/legend-shared';
|
|
67
68
|
import { PaginationControls } from '../../../components/Pagination/PaginationControls.js';
|
|
68
69
|
import { MarketplaceSearchFiltersPanel } from '../../../components/MarketplaceSearchFiltersPanel/MarketplaceSearchFiltersPanel.js';
|
|
70
|
+
import { LegendMarketplaceOptionSelector } from '../../../components/OptionSelector/LegendMarketplaceOptionSelector.js';
|
|
69
71
|
|
|
70
72
|
const SearchResultsContent = observer(
|
|
71
73
|
(props: {
|
|
@@ -383,36 +385,24 @@ export const LegendMarketplaceSearchResults =
|
|
|
383
385
|
: `${searchResultsStore.totalItems} Products`}
|
|
384
386
|
</Typography>
|
|
385
387
|
{isNonEmptyString(searchResultsStore.searchQuery) && (
|
|
386
|
-
<div
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
type="button"
|
|
402
|
-
role="tab"
|
|
403
|
-
aria-selected={false}
|
|
404
|
-
tabIndex={-1}
|
|
405
|
-
className="legend-marketplace-search-results__search-type-tab"
|
|
406
|
-
onClick={() => {
|
|
407
|
-
applicationStore.navigationService.navigator.goToLocation(
|
|
408
|
-
generateFieldSearchResultsRoute(
|
|
409
|
-
searchResultsStore.searchQuery,
|
|
410
|
-
),
|
|
411
|
-
);
|
|
388
|
+
<div className="legend-marketplace-search-results__search-type-tabs">
|
|
389
|
+
<LegendMarketplaceOptionSelector
|
|
390
|
+
options={[
|
|
391
|
+
SearchResultViewOption.DATA_PRODUCTS,
|
|
392
|
+
SearchResultViewOption.DATA_FIELDS,
|
|
393
|
+
]}
|
|
394
|
+
selectedOption={SearchResultViewOption.DATA_PRODUCTS}
|
|
395
|
+
onChange={(option) => {
|
|
396
|
+
if (option === SearchResultViewOption.DATA_FIELDS) {
|
|
397
|
+
applicationStore.navigationService.navigator.goToLocation(
|
|
398
|
+
generateFieldSearchResultsRoute(
|
|
399
|
+
searchResultsStore.searchQuery,
|
|
400
|
+
),
|
|
401
|
+
);
|
|
402
|
+
}
|
|
412
403
|
}}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
</button>
|
|
404
|
+
ariaLabel="Search result type"
|
|
405
|
+
/>
|
|
416
406
|
</div>
|
|
417
407
|
)}
|
|
418
408
|
<div className="legend-marketplace-search-results__sort-bar__controls">
|