@finos/legend-extension-dsl-data-space 10.4.258 → 10.4.259

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/lib/components/DataSpaceDescription.d.ts +5 -0
  2. package/lib/components/DataSpaceDescription.d.ts.map +1 -1
  3. package/lib/components/DataSpaceDescription.js +39 -0
  4. package/lib/components/DataSpaceDescription.js.map +1 -1
  5. package/lib/components/DataSpaceExecutionContextViewer.d.ts +1 -6
  6. package/lib/components/DataSpaceExecutionContextViewer.d.ts.map +1 -1
  7. package/lib/components/DataSpaceExecutionContextViewer.js +12 -16
  8. package/lib/components/DataSpaceExecutionContextViewer.js.map +1 -1
  9. package/lib/components/DataSpaceLakehouseAccess.d.ts +22 -0
  10. package/lib/components/DataSpaceLakehouseAccess.d.ts.map +1 -0
  11. package/lib/components/DataSpaceLakehouseAccess.js +127 -0
  12. package/lib/components/DataSpaceLakehouseAccess.js.map +1 -0
  13. package/lib/components/DataSpaceQuickStart.d.ts.map +1 -1
  14. package/lib/components/DataSpaceQuickStart.js +3 -2
  15. package/lib/components/DataSpaceQuickStart.js.map +1 -1
  16. package/lib/components/DataSpaceViewer.d.ts.map +1 -1
  17. package/lib/components/DataSpaceViewer.js +7 -13
  18. package/lib/components/DataSpaceViewer.js.map +1 -1
  19. package/lib/components/DataSpaceWiki.d.ts.map +1 -1
  20. package/lib/components/DataSpaceWiki.js +3 -2
  21. package/lib/components/DataSpaceWiki.js.map +1 -1
  22. package/lib/index.css +2 -2
  23. package/lib/index.css.map +1 -1
  24. package/lib/package.json +1 -1
  25. package/lib/stores/DataSpaceLayoutState.d.ts.map +1 -1
  26. package/lib/stores/DataSpaceLayoutState.js +3 -3
  27. package/lib/stores/DataSpaceLayoutState.js.map +1 -1
  28. package/lib/stores/DataSpaceViewerNavigation.d.ts +1 -0
  29. package/lib/stores/DataSpaceViewerNavigation.d.ts.map +1 -1
  30. package/lib/stores/DataSpaceViewerNavigation.js +1 -0
  31. package/lib/stores/DataSpaceViewerNavigation.js.map +1 -1
  32. package/lib/stores/DataSpaceViewerState.d.ts +3 -0
  33. package/lib/stores/DataSpaceViewerState.d.ts.map +1 -1
  34. package/lib/stores/DataSpaceViewerState.js +28 -2
  35. package/lib/stores/DataSpaceViewerState.js.map +1 -1
  36. package/package.json +8 -8
  37. package/src/components/DataSpaceDescription.tsx +86 -0
  38. package/src/components/DataSpaceExecutionContextViewer.tsx +41 -44
  39. package/src/components/DataSpaceLakehouseAccess.tsx +449 -0
  40. package/src/components/DataSpaceQuickStart.tsx +22 -11
  41. package/src/components/DataSpaceViewer.tsx +2 -55
  42. package/src/components/DataSpaceWiki.tsx +7 -1
  43. package/src/stores/DataSpaceLayoutState.ts +5 -6
  44. package/src/stores/DataSpaceViewerNavigation.ts +1 -0
  45. package/src/stores/DataSpaceViewerState.ts +36 -2
  46. package/tsconfig.json +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finos/legend-extension-dsl-data-space",
3
- "version": "10.4.258",
3
+ "version": "10.4.259",
4
4
  "description": "Legend extension for Data Space DSL",
5
5
  "keywords": [
6
6
  "legend",
@@ -45,14 +45,14 @@
45
45
  "dependencies": {
46
46
  "@finos/legend-application": "16.0.125",
47
47
  "@finos/legend-art": "7.1.165",
48
- "@finos/legend-code-editor": "2.0.205",
49
- "@finos/legend-extension-dsl-data-product": "0.1.1",
50
- "@finos/legend-extension-dsl-diagram": "8.1.269",
51
- "@finos/legend-graph": "32.7.1",
52
- "@finos/legend-lego": "2.0.225",
53
- "@finos/legend-query-builder": "4.19.14",
48
+ "@finos/legend-code-editor": "2.0.206",
49
+ "@finos/legend-extension-dsl-data-product": "0.1.2",
50
+ "@finos/legend-extension-dsl-diagram": "8.1.270",
51
+ "@finos/legend-graph": "32.7.2",
52
+ "@finos/legend-lego": "2.0.226",
53
+ "@finos/legend-query-builder": "4.19.15",
54
54
  "@finos/legend-server-depot": "6.1.22",
55
- "@finos/legend-server-lakehouse": "0.3.85",
55
+ "@finos/legend-server-lakehouse": "0.3.86",
56
56
  "@finos/legend-server-sdlc": "5.5.0",
57
57
  "@finos/legend-shared": "11.0.30",
58
58
  "@finos/legend-storage": "3.0.152",
@@ -20,10 +20,96 @@ import { observer } from 'mobx-react-lite';
20
20
  import { DataSpaceWikiPlaceholder } from './DataSpacePlaceholder.js';
21
21
  import { DataSpaceMarkdownTextViewer } from './DataSpaceMarkdownTextViewer.js';
22
22
  import { useEffect, useRef } from 'react';
23
+ import { ExternalLinkIcon, VerifiedIcon, WrenchIcon } from '@finos/legend-art';
24
+ import { Tooltip } from '@mui/material';
23
25
  import {
24
26
  DATA_SPACE_VIEWER_ACTIVITY_MODE,
25
27
  generateAnchorForActivity,
26
28
  } from '../stores/DataSpaceViewerNavigation.js';
29
+ import type { DataSpaceInfoAnalysisResult } from '../graph-manager/action/analytics/DataSpaceAnalysis.js';
30
+
31
+ enum DATA_SPACE_WIKI_BADGE {
32
+ VERIFIED = 'VERIFIED',
33
+ IN_DEVELOPMENT = 'IN_DEVELOPMENT',
34
+ EXTERNAL = 'EXTERNAL',
35
+ }
36
+
37
+ interface DataSpaceWikiBadgeDescriptor {
38
+ classModifier: string;
39
+ icon: React.ReactNode;
40
+ label: string;
41
+ tooltipText?: string | undefined;
42
+ isActive: (info: DataSpaceInfoAnalysisResult | undefined) => boolean;
43
+ }
44
+
45
+ const DATA_SPACE_WIKI_BADGE_DESCRIPTORS: Record<
46
+ DATA_SPACE_WIKI_BADGE,
47
+ DataSpaceWikiBadgeDescriptor
48
+ > = {
49
+ [DATA_SPACE_WIKI_BADGE.VERIFIED]: {
50
+ classModifier: 'verified',
51
+ icon: <VerifiedIcon />,
52
+ label: 'Verified',
53
+ isActive: (info) => info?.isVerified ?? false,
54
+ },
55
+ [DATA_SPACE_WIKI_BADGE.IN_DEVELOPMENT]: {
56
+ classModifier: 'in-development',
57
+ icon: <WrenchIcon />,
58
+ label: 'In Development',
59
+ tooltipText: 'This data space has not been finalized yet',
60
+ isActive: (info) => info?.isInDevelopment ?? false,
61
+ },
62
+ [DATA_SPACE_WIKI_BADGE.EXTERNAL]: {
63
+ classModifier: 'external',
64
+ icon: <ExternalLinkIcon />,
65
+ label: 'External',
66
+ tooltipText: 'This data originates from a third-party vendor',
67
+ isActive: (info) => info?.isExternal ?? false,
68
+ },
69
+ };
70
+
71
+ export const DataSpaceWikiBadges = observer(
72
+ (props: { dataSpaceViewerState: DataSpaceViewerState }) => {
73
+ const { dataSpaceViewerState } = props;
74
+ const info = dataSpaceViewerState.dataSpaceAnalysisResult.info;
75
+ const activeBadges = Object.values(
76
+ DATA_SPACE_WIKI_BADGE_DESCRIPTORS,
77
+ ).filter((badge) => badge.isActive(info));
78
+
79
+ return (
80
+ activeBadges.length > 0 && (
81
+ <div
82
+ className={`data-space__viewer__wiki__badges data-space__viewer__wiki__badges--count-${activeBadges.length}`}
83
+ >
84
+ {activeBadges.map((badge) => {
85
+ const badgeContent = (
86
+ <div
87
+ key={badge.classModifier}
88
+ className={`data-space__viewer__wiki__badge data-space__viewer__wiki__badge--${badge.classModifier}`}
89
+ >
90
+ {badge.icon}
91
+ <span className="data-space__viewer__wiki__badge__label">
92
+ {badge.label}
93
+ </span>
94
+ </div>
95
+ );
96
+ return badge.tooltipText ? (
97
+ <Tooltip
98
+ key={badge.classModifier}
99
+ title={badge.tooltipText}
100
+ placement="bottom"
101
+ >
102
+ {badgeContent}
103
+ </Tooltip>
104
+ ) : (
105
+ badgeContent
106
+ );
107
+ })}
108
+ </div>
109
+ )
110
+ );
111
+ },
112
+ );
27
113
 
28
114
  export const DataSpaceDescription = observer(
29
115
  (props: { dataSpaceViewerState: DataSpaceViewerState }) => {
@@ -92,42 +92,26 @@ const DataSpaceMappingProviderAccessControl = observer(
92
92
  },
93
93
  );
94
94
 
95
- export const DataSpaceOpenDataProductButton = observer(
96
- (props: {
97
- dataSpaceViewerState: DataSpaceViewerState;
98
- dataProductPath: string;
99
- }) => {
100
- const { dataSpaceViewerState, dataProductPath } = props;
101
- const deploymentId =
102
- dataSpaceViewerState.resolveDeploymentIdForDataProduct(dataProductPath);
103
- const isUnresolved = Boolean(
104
- dataSpaceViewerState.getDataProductAccessState(dataProductPath)
105
- ?.isDataProductUnresolved,
106
- );
107
- const onOpenDataProduct = (): void => {
108
- if (dataSpaceViewerState.viewDataProduct && deploymentId !== undefined) {
109
- dataSpaceViewerState.viewDataProduct(dataProductPath, deploymentId);
110
- }
111
- };
112
- return (
113
- <Button
114
- variant="contained"
115
- color="primary"
116
- onClick={onOpenDataProduct}
117
- sx={{
118
- display:
119
- !dataSpaceViewerState.viewDataProduct ||
120
- deploymentId === undefined ||
121
- isUnresolved
122
- ? 'none'
123
- : undefined,
124
- }}
125
- >
126
- Open DataProduct
127
- </Button>
128
- );
129
- },
130
- );
95
+ export const resolveOpenDataProductAction = (
96
+ dataSpaceViewerState: DataSpaceViewerState,
97
+ dataProductPath: string,
98
+ ): (() => void) | undefined => {
99
+ const deploymentId =
100
+ dataSpaceViewerState.resolveDeploymentIdForDataProduct(dataProductPath);
101
+ const isUnresolved = Boolean(
102
+ dataSpaceViewerState.getDataProductAccessState(dataProductPath)
103
+ ?.isDataProductUnresolved,
104
+ );
105
+ if (
106
+ !dataSpaceViewerState.viewDataProduct ||
107
+ deploymentId === undefined ||
108
+ isUnresolved
109
+ ) {
110
+ return undefined;
111
+ }
112
+ const viewDataProduct = dataSpaceViewerState.viewDataProduct;
113
+ return (): void => viewDataProduct(dataProductPath, deploymentId);
114
+ };
131
115
 
132
116
  export const DataSpaceMappingProviderEntry = observer(
133
117
  (props: {
@@ -147,16 +131,33 @@ export const DataSpaceMappingProviderEntry = observer(
147
131
  const tokenProvider =
148
132
  dataSpaceViewerState.mappingProviderAccessConfig?.tokenProvider ??
149
133
  ((): undefined => undefined);
134
+ const onOpenDataProduct = resolveOpenDataProductAction(
135
+ dataSpaceViewerState,
136
+ mappingProvider.element,
137
+ );
138
+ const mappingProviderText = `${mappingProvider.element}${
139
+ mappingProvider.keys[0] ? `.${mappingProvider.keys[0]}` : ''
140
+ }`;
150
141
  return (
151
142
  <>
152
143
  <div className="data-space__viewer__execution-context__entry__icon">
153
144
  <PURE_DataProductIcon />
154
145
  </div>
155
146
  <div className="data-space__viewer__execution-context__entry__content data-space__viewer__execution-context__entry__content__text data-space__viewer__execution-context__mapping-provider__content">
156
- <span>
157
- {mappingProvider.element}
158
- {mappingProvider.keys[0] ? `.${mappingProvider.keys[0]}` : ''}
159
- </span>
147
+ {onOpenDataProduct ? (
148
+ <button
149
+ type="button"
150
+ className="data-space__viewer__execution-context__mapping-provider__label data-space__viewer__execution-context__mapping-provider__label--clickable"
151
+ title="Open Data Product"
152
+ onClick={onOpenDataProduct}
153
+ >
154
+ {mappingProviderText}
155
+ </button>
156
+ ) : (
157
+ <span className="data-space__viewer__execution-context__mapping-provider__label">
158
+ {mappingProviderText}
159
+ </span>
160
+ )}
160
161
  <div className="data-space__viewer__execution-context__mapping-provider__actions">
161
162
  {mappingProviderAccessState && (
162
163
  <DataSpaceMappingProviderAccessControl
@@ -164,10 +165,6 @@ export const DataSpaceMappingProviderEntry = observer(
164
165
  tokenProvider={tokenProvider}
165
166
  />
166
167
  )}
167
- <DataSpaceOpenDataProductButton
168
- dataSpaceViewerState={dataSpaceViewerState}
169
- dataProductPath={mappingProvider.element}
170
- />
171
168
  {mappingProviderAccessState && (
172
169
  <IconButton
173
170
  className="data-space__viewer__execution-context__refresh-btn"
@@ -0,0 +1,449 @@
1
+ /**
2
+ * Copyright (c) 2026-present, Goldman Sachs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { observer } from 'mobx-react-lite';
18
+ import { useEffect, useMemo, useRef, useState } from 'react';
19
+ import {
20
+ Button,
21
+ ButtonGroup,
22
+ IconButton,
23
+ Menu,
24
+ MenuItem,
25
+ Tooltip,
26
+ } from '@mui/material';
27
+ import {
28
+ CaretDownIcon,
29
+ ExpandMoreIcon,
30
+ RefreshIcon,
31
+ clsx,
32
+ } from '@finos/legend-art';
33
+ import {
34
+ extractElementNameFromPath,
35
+ type V1_AccessPointGroup,
36
+ } from '@finos/legend-graph';
37
+ import {
38
+ AccessPointGroupAccess,
39
+ CollapsibleWikiSection,
40
+ DataProductAPGAccessRequestControl,
41
+ EntitlementsDataContractCreator,
42
+ type DataProductAPGState,
43
+ } from '@finos/legend-extension-dsl-data-product';
44
+ import { type DataSpaceViewerState } from '../stores/DataSpaceViewerState.js';
45
+ import type { DataSpaceDataProductAccessState } from '../stores/DataSpaceDataProductAccessState.js';
46
+ import { DataSpaceMarkdownTextViewer } from './DataSpaceMarkdownTextViewer.js';
47
+ import { resolveOpenDataProductAction } from './DataSpaceExecutionContextViewer.js';
48
+ import {
49
+ DATA_SPACE_VIEWER_ACTIVITY_MODE,
50
+ generateAnchorForActivity,
51
+ } from '../stores/DataSpaceViewerNavigation.js';
52
+
53
+ const isEntitled = (access: AccessPointGroupAccess): boolean =>
54
+ access === AccessPointGroupAccess.APPROVED ||
55
+ access === AccessPointGroupAccess.ENTERPRISE;
56
+
57
+ const getBulkRequestTargetApgStates = (
58
+ accessState: DataSpaceDataProductAccessState,
59
+ ): DataProductAPGState[] => {
60
+ const apgStates = accessState.dataProductViewerState?.apgStates ?? [];
61
+ return apgStates.filter((state) => !isEntitled(state.access));
62
+ };
63
+
64
+ const getContractableApgStates = (
65
+ accessState: DataSpaceDataProductAccessState,
66
+ ): DataProductAPGState[] => {
67
+ const apgStates = accessState.dataProductViewerState?.apgStates ?? [];
68
+ return apgStates.filter(
69
+ (state) => state.access !== AccessPointGroupAccess.ENTERPRISE,
70
+ );
71
+ };
72
+
73
+ const DataSpaceLakehouseAccessRequestButton = observer(
74
+ (props: {
75
+ accessState: DataSpaceDataProductAccessState;
76
+ onRequestForSelf: () => void;
77
+ onRequestForOthers: () => void;
78
+ }) => {
79
+ const { accessState, onRequestForSelf, onRequestForOthers } = props;
80
+ const buttonGroupRef = useRef<HTMLDivElement>(null);
81
+ const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
82
+ const apgStates = accessState.dataProductViewerState?.apgStates;
83
+ if (!apgStates || apgStates.length === 0) {
84
+ return null;
85
+ }
86
+ const total = apgStates.length;
87
+ const entitled = apgStates.filter((state) =>
88
+ isEntitled(state.access),
89
+ ).length;
90
+ const pending = apgStates.filter(
91
+ (state) =>
92
+ state.access === AccessPointGroupAccess.SUBMITTED_FOR_APPROVALS ||
93
+ state.access === AccessPointGroupAccess.PENDING_MANAGER_APPROVAL ||
94
+ state.access === AccessPointGroupAccess.PENDING_DATA_OWNER_APPROVAL,
95
+ ).length;
96
+ const hasAllAccess = entitled >= total;
97
+ const requestableTargets = getBulkRequestTargetApgStates(accessState);
98
+ const primaryDisabled = hasAllAccess || requestableTargets.length === 0;
99
+ const forOthersTargets = getContractableApgStates(accessState);
100
+ const showForOthers = forOthersTargets.length > 0;
101
+ const primaryTooltip = hasAllAccess
102
+ ? `You have access to all ${total} Access Point Group${total === 1 ? '' : 's'} under this Data Product required to access this DataSpace.`
103
+ : `Request access to the Access Point Groups under this Data Product required to access this DataSpace. ${entitled} of ${total} granted${pending > 0 ? `, ${pending} pending approval` : ''}.`;
104
+ return (
105
+ <>
106
+ <ButtonGroup
107
+ variant="contained"
108
+ color={hasAllAccess ? 'success' : 'primary'}
109
+ ref={buttonGroupRef}
110
+ className="data-space__viewer__lakehouse-access__group__request-btn"
111
+ >
112
+ <Tooltip title={primaryTooltip} arrow={true}>
113
+ <span>
114
+ <Button
115
+ disabled={!hasAllAccess && primaryDisabled}
116
+ onClick={hasAllAccess ? undefined : onRequestForSelf}
117
+ sx={{
118
+ height: '100%',
119
+ cursor: hasAllAccess ? 'default' : undefined,
120
+ borderTopRightRadius: showForOthers ? 0 : undefined,
121
+ borderBottomRightRadius: showForOthers ? 0 : undefined,
122
+ }}
123
+ >
124
+ {hasAllAccess
125
+ ? 'Access Granted'
126
+ : `Request Access (${entitled} / ${total})`}
127
+ </Button>
128
+ </span>
129
+ </Tooltip>
130
+ {showForOthers && (
131
+ <Button
132
+ size="small"
133
+ onClick={() => setIsMenuOpen((prev) => !prev)}
134
+ title="More options"
135
+ >
136
+ <CaretDownIcon />
137
+ </Button>
138
+ )}
139
+ </ButtonGroup>
140
+ {showForOthers && (
141
+ <Menu
142
+ anchorEl={buttonGroupRef.current}
143
+ open={isMenuOpen}
144
+ onClose={() => setIsMenuOpen(false)}
145
+ >
146
+ <MenuItem
147
+ onClick={() => {
148
+ onRequestForOthers();
149
+ setIsMenuOpen(false);
150
+ }}
151
+ >
152
+ Request Access for Others
153
+ </MenuItem>
154
+ </Menu>
155
+ )}
156
+ </>
157
+ );
158
+ },
159
+ );
160
+
161
+ const DataSpaceLakehouseAccessGroup = observer(
162
+ (props: {
163
+ viewerState: DataSpaceViewerState;
164
+ dataProductPath: string;
165
+ initiallyCollapsed: boolean;
166
+ }) => {
167
+ const { viewerState, dataProductPath, initiallyCollapsed } = props;
168
+ const [isCollapsed, setIsCollapsed] = useState<boolean>(initiallyCollapsed);
169
+ const [bulkRequestMode, setBulkRequestMode] = useState<
170
+ 'self' | 'others' | undefined
171
+ >(undefined);
172
+ const accessState = viewerState.getDataProductAccessState(dataProductPath);
173
+ const isReadOnly = accessState === undefined;
174
+ const readOnlyApgIds: string[] = useMemo(() => {
175
+ if (!isReadOnly) {
176
+ return [];
177
+ }
178
+ const dp =
179
+ viewerState.graphManagerState.graph.getOwnNullableDataProduct(
180
+ dataProductPath,
181
+ );
182
+ return dp?.accessPointGroups.map((group) => group.id) ?? [];
183
+ }, [dataProductPath, isReadOnly, viewerState.graphManagerState.graph]);
184
+ const tokenProvider =
185
+ viewerState.mappingProviderAccessConfig?.tokenProvider ??
186
+ ((): undefined => undefined);
187
+ const apgStates = accessState?.dataProductViewerState?.apgStates ?? [];
188
+ const isLoading = accessState?.initializingState.isInProgress ?? false;
189
+ const hasError =
190
+ accessState?.errorMessage !== undefined && apgStates.length === 0;
191
+ const displayedCount = isReadOnly
192
+ ? readOnlyApgIds.length
193
+ : apgStates.length;
194
+
195
+ const bulkTargetApgStates = accessState
196
+ ? bulkRequestMode === 'others'
197
+ ? getContractableApgStates(accessState)
198
+ : getBulkRequestTargetApgStates(accessState)
199
+ : [];
200
+ const bulkTargetApgs: V1_AccessPointGroup[] = bulkTargetApgStates.map(
201
+ (state) => state.apg,
202
+ );
203
+
204
+ const representativeApgState: DataProductAPGState | undefined =
205
+ bulkTargetApgStates[0];
206
+
207
+ const onOpenDataProduct = resolveOpenDataProductAction(
208
+ viewerState,
209
+ dataProductPath,
210
+ );
211
+
212
+ return (
213
+ <div className="data-space__viewer__lakehouse-access__group">
214
+ <div className="data-space__viewer__lakehouse-access__group__header-row">
215
+ <button
216
+ className="data-space__viewer__lakehouse-access__group__caret-btn"
217
+ tabIndex={-1}
218
+ onClick={(): void => setIsCollapsed((prev) => !prev)}
219
+ title={isCollapsed ? 'Expand' : 'Collapse'}
220
+ >
221
+ <ExpandMoreIcon
222
+ className={clsx(
223
+ 'data-space__viewer__lakehouse-access__group__caret',
224
+ {
225
+ 'data-space__viewer__lakehouse-access__group__caret--collapsed':
226
+ isCollapsed,
227
+ },
228
+ )}
229
+ />
230
+ </button>
231
+ <div className="data-space__viewer__lakehouse-access__group__header">
232
+ {onOpenDataProduct ? (
233
+ <button
234
+ type="button"
235
+ className="data-space__viewer__lakehouse-access__group__label data-space__viewer__lakehouse-access__group__label--clickable"
236
+ title={`Open Data Product: ${dataProductPath}`}
237
+ onClick={onOpenDataProduct}
238
+ >
239
+ {extractElementNameFromPath(dataProductPath)}
240
+ </button>
241
+ ) : (
242
+ <span
243
+ className="data-space__viewer__lakehouse-access__group__label"
244
+ title={dataProductPath}
245
+ >
246
+ {extractElementNameFromPath(dataProductPath)}
247
+ </span>
248
+ )}
249
+ <span className="data-space__viewer__lakehouse-access__group__count">
250
+ {displayedCount}
251
+ </span>
252
+ </div>
253
+ {!isReadOnly && (
254
+ <div className="data-space__viewer__lakehouse-access__group__actions">
255
+ {!hasError && (
256
+ <DataSpaceLakehouseAccessRequestButton
257
+ accessState={accessState}
258
+ onRequestForSelf={(): void => setBulkRequestMode('self')}
259
+ onRequestForOthers={(): void => setBulkRequestMode('others')}
260
+ />
261
+ )}
262
+ <IconButton
263
+ className="data-space__viewer__lakehouse-access__group__refresh-btn"
264
+ size="small"
265
+ color="primary"
266
+ title="Refresh Data Product access"
267
+ disabled={accessState.initializingState.isInProgress}
268
+ onClick={(): void =>
269
+ viewerState.refreshDataProductAccessState(dataProductPath)
270
+ }
271
+ >
272
+ <RefreshIcon />
273
+ </IconButton>
274
+ </div>
275
+ )}
276
+ </div>
277
+ {!isCollapsed && (
278
+ <div className="data-space__viewer__lakehouse-access__group__rows">
279
+ {isReadOnly ? (
280
+ readOnlyApgIds.length === 0 ? (
281
+ <div className="data-space__viewer__lakehouse-access__row">
282
+ <span className="data-space__viewer__lakehouse-access__row__unavailable">
283
+ Data Product is not available in the local project — Access
284
+ Point Groups cannot be listed in preview.
285
+ </span>
286
+ </div>
287
+ ) : (
288
+ readOnlyApgIds.map((apgId) => (
289
+ <div
290
+ key={apgId}
291
+ className="data-space__viewer__lakehouse-access__row"
292
+ >
293
+ <span
294
+ className="data-space__viewer__lakehouse-access__row__apg"
295
+ title={apgId}
296
+ >
297
+ {apgId}
298
+ </span>
299
+ </div>
300
+ ))
301
+ )
302
+ ) : hasError ? (
303
+ <div
304
+ className="data-space__viewer__lakehouse-access__row"
305
+ title={accessState.errorMessage}
306
+ >
307
+ <span className="data-space__viewer__lakehouse-access__row__unavailable">
308
+ Unavailable
309
+ </span>
310
+ </div>
311
+ ) : isLoading && apgStates.length === 0 ? (
312
+ <div className="data-space__viewer__lakehouse-access__row">
313
+ <span className="data-space__viewer__lakehouse-access__row__loading">
314
+ Loading...
315
+ </span>
316
+ </div>
317
+ ) : (
318
+ apgStates.map((apgState) => (
319
+ <div
320
+ key={apgState.apg.id}
321
+ className="data-space__viewer__lakehouse-access__row"
322
+ >
323
+ <span
324
+ className="data-space__viewer__lakehouse-access__row__apg"
325
+ title={apgState.apg.id}
326
+ >
327
+ {apgState.apg.id}
328
+ </span>
329
+ <div className="data-space__viewer__lakehouse-access__row__actions">
330
+ {accessState.dataAccessState && (
331
+ <DataProductAPGAccessRequestControl
332
+ apgState={apgState}
333
+ dataAccessState={accessState.dataAccessState}
334
+ tokenProvider={tokenProvider}
335
+ />
336
+ )}
337
+ </div>
338
+ </div>
339
+ ))
340
+ )}
341
+ </div>
342
+ )}
343
+ {bulkRequestMode !== undefined &&
344
+ accessState?.dataAccessState &&
345
+ representativeApgState &&
346
+ bulkTargetApgs.length > 0 && (
347
+ <EntitlementsDataContractCreator
348
+ open={true}
349
+ onClose={(): void => setBulkRequestMode(undefined)}
350
+ tokenProvider={tokenProvider}
351
+ apgState={representativeApgState}
352
+ dataAccessState={accessState.dataAccessState}
353
+ overrideTargetApgs={bulkTargetApgs}
354
+ headerContent={
355
+ <>
356
+ Submit access request for{' '}
357
+ <span className="marketplace-lakehouse-text__emphasis">
358
+ {bulkTargetApgs.length}
359
+ </span>{' '}
360
+ Access Point Group
361
+ {bulkTargetApgs.length === 1 ? '' : 's'} in{' '}
362
+ <span className="marketplace-lakehouse-text__emphasis">
363
+ {accessState.dataAccessState.dataProductViewerState.product
364
+ .title ?? extractElementNameFromPath(dataProductPath)}
365
+ </span>{' '}
366
+ Data Product
367
+ </>
368
+ }
369
+ />
370
+ )}
371
+ </div>
372
+ );
373
+ },
374
+ );
375
+
376
+ const DataSpaceLakehouseAccessPanel = observer(
377
+ (props: {
378
+ viewerState: DataSpaceViewerState;
379
+ dataProductPaths: string[];
380
+ }) => {
381
+ const { viewerState, dataProductPaths } = props;
382
+ return (
383
+ <>
384
+ <div className="data-space__viewer__lakehouse-access__description">
385
+ <DataSpaceMarkdownTextViewer
386
+ value={
387
+ "The following Data Products are required to access this DataSpace. Products you don't have access to can be requested below."
388
+ }
389
+ />
390
+ </div>
391
+ <div className="data-space__viewer__lakehouse-access">
392
+ <div className="data-space__viewer__lakehouse-access__header">
393
+ Data Product Access
394
+ </div>
395
+ {dataProductPaths.map((dataProductPath) => (
396
+ <DataSpaceLakehouseAccessGroup
397
+ key={dataProductPath}
398
+ viewerState={viewerState}
399
+ dataProductPath={dataProductPath}
400
+ initiallyCollapsed={true}
401
+ />
402
+ ))}
403
+ </div>
404
+ </>
405
+ );
406
+ },
407
+ );
408
+
409
+ export const DataSpaceLakehouseAccess = observer(
410
+ (props: { dataSpaceViewerState: DataSpaceViewerState }) => {
411
+ const { dataSpaceViewerState } = props;
412
+ const sectionRef = useRef<HTMLDivElement>(null);
413
+ const anchor = generateAnchorForActivity(
414
+ DATA_SPACE_VIEWER_ACTIVITY_MODE.DATASPACE_LAKEHOUSE_ACCESS,
415
+ );
416
+
417
+ useEffect(() => {
418
+ if (sectionRef.current) {
419
+ dataSpaceViewerState.layoutState.setWikiPageAnchor(
420
+ anchor,
421
+ sectionRef.current,
422
+ );
423
+ }
424
+ return () => dataSpaceViewerState.layoutState.unsetWikiPageAnchor(anchor);
425
+ }, [dataSpaceViewerState, anchor]);
426
+
427
+ const dataProductPaths = dataSpaceViewerState.referencedDataProductPaths;
428
+
429
+ if (dataProductPaths.length === 0) {
430
+ return null;
431
+ }
432
+
433
+ return (
434
+ <div ref={sectionRef} className="viewer__wiki__section">
435
+ <CollapsibleWikiSection
436
+ viewerState={dataSpaceViewerState}
437
+ section={DATA_SPACE_VIEWER_ACTIVITY_MODE.DATASPACE_LAKEHOUSE_ACCESS}
438
+ >
439
+ <div className="viewer__wiki__section__content">
440
+ <DataSpaceLakehouseAccessPanel
441
+ viewerState={dataSpaceViewerState}
442
+ dataProductPaths={dataProductPaths}
443
+ />
444
+ </div>
445
+ </CollapsibleWikiSection>
446
+ </div>
447
+ );
448
+ },
449
+ );