Skip to content

Commit 595bd11

Browse files
committed
feat(content-sidebar): enhance Box AI sidebar integration and custom panel handling
1 parent 46ddd87 commit 595bd11

6 files changed

Lines changed: 336 additions & 105 deletions

File tree

src/elements/content-sidebar/Sidebar.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,17 +321,20 @@ class Sidebar extends React.Component<Props, State> {
321321
versionsSidebarProps,
322322
}: Props = this.props;
323323
const isOpen = this.isOpen();
324+
325+
const hasCustomBoxAISidebar = customSidebarPanels
326+
? customSidebarPanels.some(panel => panel.id === SIDEBAR_VIEW_BOXAI)
327+
: false;
328+
const isBoxAIEnabled = SidebarUtils.canHaveBoxAISidebar(this.props);
329+
const hasNativeBoxAISidebar = isBoxAIEnabled && !hasCustomBoxAISidebar;
324330
const hasActivity = SidebarUtils.canHaveActivitySidebar(this.props);
325331
const hasDetails = SidebarUtils.canHaveDetailsSidebar(this.props);
326332
const hasMetadata = SidebarUtils.shouldRenderMetadataSidebar(this.props, metadataEditors);
327333
const hasSkills = SidebarUtils.shouldRenderSkillsSidebar(this.props, file);
328334
const onVersionHistoryClick = hasVersions ? this.handleVersionHistoryClick : this.props.onVersionHistoryClick;
329-
const hasBoxAI = customSidebarPanels
330-
? !!customSidebarPanels.find(panel => panel.id === SIDEBAR_VIEW_BOXAI)
331-
: false;
332335
const styleClassName = classNames('be bcs', className, {
333336
'bcs-is-open': isOpen,
334-
'bcs-is-wider': hasBoxAI,
337+
'bcs-is-wider': hasNativeBoxAISidebar || hasCustomBoxAISidebar,
335338
});
336339
const defaultPanel = this.getDefaultPanel();
337340

@@ -352,6 +355,7 @@ class Sidebar extends React.Component<Props, State> {
352355
fileId={fileId}
353356
hasActivity={hasActivity}
354357
hasAdditionalTabs={hasAdditionalTabs}
358+
hasNativeBoxAISidebar={hasNativeBoxAISidebar}
355359
hasDetails={hasDetails}
356360
hasMetadata={hasMetadata}
357361
hasSkills={hasSkills}
@@ -377,6 +381,7 @@ class Sidebar extends React.Component<Props, State> {
377381
getPreview={getPreview}
378382
getViewer={getViewer}
379383
hasActivity={hasActivity}
384+
hasNativeBoxAISidebar={hasNativeBoxAISidebar}
380385
hasDetails={hasDetails}
381386
hasDocGen={docGenSidebarProps.isDocGenTemplate}
382387
hasMetadata={hasMetadata}

src/elements/content-sidebar/SidebarNav.js

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
SIDEBAR_VIEW_METADATA,
4646
SIDEBAR_VIEW_SKILLS,
4747
} from '../../constants';
48+
import { useFeatureConfig } from '../common/feature-checking';
4849
import type { NavigateOptions, AdditionalSidebarTab, CustomSidebarPanel } from './flowTypes';
4950
import type { InternalSidebarNavigation, InternalSidebarNavigationHandler } from '../common/types/SidebarNavigation';
5051
import './SidebarNav.scss';
@@ -123,6 +124,7 @@ type Props = {
123124
fileId: string,
124125
hasActivity: boolean,
125126
hasAdditionalTabs: boolean,
127+
hasNativeBoxAISidebar: boolean,
126128
hasDetails: boolean,
127129
hasDocGen?: boolean,
128130
hasMetadata: boolean,
@@ -144,6 +146,7 @@ const SidebarNav = ({
144146
fileId,
145147
hasActivity,
146148
hasAdditionalTabs,
149+
hasNativeBoxAISidebar,
147150
hasDetails,
148151
hasMetadata,
149152
hasSkills,
@@ -177,11 +180,30 @@ const SidebarNav = ({
177180
const hasOtherCustomTabs = otherCustomTabs.length > 0;
178181

179182
const sidebarTabs = [
180-
boxAiTab && (
183+
hasNativeBoxAISidebar && (
184+
<SidebarNavButton
185+
key={SIDEBAR_VIEW_BOXAI}
186+
isPreviewModernizationEnabled={isPreviewModernizationEnabled}
187+
data-resin-target={SIDEBAR_NAV_TARGETS.BOXAI}
188+
data-target-id="SidebarNavButton-boxAI"
189+
data-testid="sidebarboxai"
190+
isDisabled={showOnlyBoxAINavButton}
191+
onClick={handleSidebarNavButtonClick}
192+
sidebarView={SIDEBAR_VIEW_BOXAI}
193+
tooltip={showOnlyBoxAINavButton ? boxAIDisabledTooltip : intl.formatMessage(messages.sidebarBoxAITitle)}
194+
>
195+
{isPreviewModernizationEnabled ? (
196+
<BoxAiLogo24 {...SIDEBAR_TAB_ICON_PROPS} />
197+
) : (
198+
<BoxAiLogo height={Size6} width={Size6} />
199+
)}
200+
</SidebarNavButton>
201+
),
202+
!hasNativeBoxAISidebar && boxAiTab && (
181203
<SidebarNavButton
182204
key={boxAiTab.id}
183205
isPreviewModernizationEnabled={isPreviewModernizationEnabled}
184-
data-target-id={`SidebarNavButton-${boxAiTab.id}`}
206+
data-target-id={`SidebarNavButton-$boxAI`}
185207
data-testid={`sidebar${boxAiTab.id}`}
186208
{...boxAiTab.navButtonProps}
187209
data-resin-target={SIDEBAR_NAV_TARGETS.BOXAI}
@@ -271,12 +293,9 @@ const SidebarNav = ({
271293
),
272294
];
273295

274-
// Filter out falsy values first
275296
const visibleTabs = sidebarTabs.filter(Boolean);
276297

277-
// Insert custom tabs - box-ai goes at the top, others at the end
278298
if (hasOtherCustomTabs) {
279-
// Add other custom tabs at the end
280299
otherCustomTabs.forEach(customTab => {
281300
const {
282301
id: customTabId,

src/elements/content-sidebar/SidebarPanels.js

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,21 @@ import { getFeatureConfig, withFeatureConsumer, isFeatureEnabled } from '../comm
1616
import { withRouterAndRef } from '../common/routing';
1717
import {
1818
ORIGIN_ACTIVITY_SIDEBAR,
19+
ORIGIN_BOXAI_SIDEBAR,
1920
ORIGIN_DETAILS_SIDEBAR,
2021
ORIGIN_DOCGEN_SIDEBAR,
2122
ORIGIN_METADATA_SIDEBAR,
2223
ORIGIN_METADATA_SIDEBAR_REDESIGN,
2324
ORIGIN_SKILLS_SIDEBAR,
2425
ORIGIN_VERSIONS_SIDEBAR,
2526
SIDEBAR_VIEW_ACTIVITY,
27+
SIDEBAR_VIEW_BOXAI,
2628
SIDEBAR_VIEW_DETAILS,
2729
SIDEBAR_VIEW_METADATA,
2830
SIDEBAR_VIEW_SKILLS,
2931
SIDEBAR_VIEW_VERSIONS,
3032
SIDEBAR_VIEW_DOCGEN,
3133
SIDEBAR_VIEW_METADATA_REDESIGN,
32-
SIDEBAR_VIEW_BOXAI,
3334
} from '../../constants';
3435
import type { DetailsSidebarProps } from './DetailsSidebar';
3536
import type { DocGenSidebarProps } from './DocGenSidebar/DocGenSidebar';
@@ -59,7 +60,7 @@ type Props = {
5960
getPreview: Function,
6061
getViewer: Function,
6162
hasActivity: boolean,
62-
hasBoxAI: boolean,
63+
hasNativeBoxAISidebar: boolean,
6364
hasDetails: boolean,
6465
hasDocGen: boolean,
6566
hasMetadata: boolean,
@@ -87,12 +88,13 @@ type ElementRefType = {
8788

8889
// TODO: place into code splitting logic
8990
const BASE_EVENT_NAME = '_JS_LOADING';
90-
const MARK_NAME_JS_LOADING_DETAILS = `${ORIGIN_DETAILS_SIDEBAR}${BASE_EVENT_NAME}`;
9191
const MARK_NAME_JS_LOADING_ACTIVITY = `${ORIGIN_ACTIVITY_SIDEBAR}${BASE_EVENT_NAME}`;
92-
const MARK_NAME_JS_LOADING_SKILLS = `${ORIGIN_SKILLS_SIDEBAR}${BASE_EVENT_NAME}`;
92+
const MARK_NAME_JS_LOADING_BOXAI = `${ORIGIN_BOXAI_SIDEBAR}${BASE_EVENT_NAME}`;
93+
const MARK_NAME_JS_LOADING_DETAILS = `${ORIGIN_DETAILS_SIDEBAR}${BASE_EVENT_NAME}`;
94+
const MARK_NAME_JS_LOADING_DOCGEN = `${ORIGIN_DOCGEN_SIDEBAR}${BASE_EVENT_NAME}`;
9395
const MARK_NAME_JS_LOADING_METADATA = `${ORIGIN_METADATA_SIDEBAR}${BASE_EVENT_NAME}`;
9496
const MARK_NAME_JS_LOADING_METADATA_REDESIGNED = `${ORIGIN_METADATA_SIDEBAR_REDESIGN}${BASE_EVENT_NAME}`;
95-
const MARK_NAME_JS_LOADING_DOCGEN = `${ORIGIN_DOCGEN_SIDEBAR}${BASE_EVENT_NAME}`;
97+
const MARK_NAME_JS_LOADING_SKILLS = `${ORIGIN_SKILLS_SIDEBAR}${BASE_EVENT_NAME}`;
9698
const MARK_NAME_JS_LOADING_VERSIONS = `${ORIGIN_VERSIONS_SIDEBAR}${BASE_EVENT_NAME}`;
9799

98100
const URL_TO_FEED_ITEM_TYPE = { annotations: 'annotation', comments: 'comment', tasks: 'task' };
@@ -106,12 +108,13 @@ const DEFAULT_SIDEBAR_VIEWS = [
106108
SIDEBAR_VIEW_METADATA,
107109
];
108110

109-
const LoadableDetailsSidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_DETAILS, MARK_NAME_JS_LOADING_DETAILS);
110111
const LoadableActivitySidebar = SidebarUtils.getAsyncSidebarContent(
111112
SIDEBAR_VIEW_ACTIVITY,
112113
MARK_NAME_JS_LOADING_ACTIVITY,
113114
);
114-
const LoadableSkillsSidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_SKILLS, MARK_NAME_JS_LOADING_SKILLS);
115+
const LoadableBoxAISidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_BOXAI, MARK_NAME_JS_LOADING_BOXAI);
116+
const LoadableDetailsSidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_DETAILS, MARK_NAME_JS_LOADING_DETAILS);
117+
const LoadableDocGenSidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_DOCGEN, MARK_NAME_JS_LOADING_DOCGEN);
115118
const LoadableMetadataSidebar = SidebarUtils.getAsyncSidebarContent(
116119
SIDEBAR_VIEW_METADATA,
117120
MARK_NAME_JS_LOADING_METADATA,
@@ -120,7 +123,7 @@ const LoadableMetadataSidebarRedesigned = SidebarUtils.getAsyncSidebarContent(
120123
SIDEBAR_VIEW_METADATA_REDESIGN,
121124
MARK_NAME_JS_LOADING_METADATA,
122125
);
123-
const LoadableDocGenSidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_DOCGEN, MARK_NAME_JS_LOADING_DOCGEN);
126+
const LoadableSkillsSidebar = SidebarUtils.getAsyncSidebarContent(SIDEBAR_VIEW_SKILLS, MARK_NAME_JS_LOADING_SKILLS);
124127
const LoadableVersionsSidebar = SidebarUtils.getAsyncSidebarContent(
125128
SIDEBAR_VIEW_VERSIONS,
126129
MARK_NAME_JS_LOADING_VERSIONS,
@@ -185,6 +188,13 @@ class SidebarPanels extends React.Component<Props, State> {
185188
}
186189
};
187190

191+
setBoxAiSidebarCacheValue = (
192+
key: 'agents' | 'encodedSession' | 'questions' | 'shouldShowLandingPage' | 'suggestedQuestions',
193+
value: any,
194+
) => {
195+
this.boxAiSidebarCache[key] = value;
196+
};
197+
188198
getCustomSidebarRef = (panelId: string): ElementRefType => {
189199
if (!this.customSidebars.has(panelId)) {
190200
this.customSidebars.set(panelId, React.createRef());
@@ -236,27 +246,33 @@ class SidebarPanels extends React.Component<Props, State> {
236246
}
237247
}
238248

239-
getPanelOrder = (customPanels?: Array<CustomSidebarPanel>, shouldBoxAIBeDefaultPanel: boolean): string[] => {
240-
// No custom panels - return default panels
241-
if (!customPanels || customPanels.length === 0) {
242-
return DEFAULT_SIDEBAR_VIEWS;
249+
getPanelOrder = (
250+
customPanels?: Array<CustomSidebarPanel>,
251+
shouldBoxAIBeDefaultPanel: boolean,
252+
hasNativeBoxAISidebar: boolean,
253+
): string[] => {
254+
const customPanelPaths = customPanels ? customPanels.map(panel => panel.path) : [];
255+
const boxAiCustomPanel = customPanels ? customPanels.find(panel => panel.id === SIDEBAR_VIEW_BOXAI) : undefined;
256+
const boxAiPath = boxAiCustomPanel ? boxAiCustomPanel.path : null;
257+
const nonBoxAIPaths = customPanelPaths.filter(path => path !== boxAiPath);
258+
259+
if (hasNativeBoxAISidebar) {
260+
return shouldBoxAIBeDefaultPanel
261+
? [SIDEBAR_VIEW_BOXAI, ...DEFAULT_SIDEBAR_VIEWS, ...nonBoxAIPaths]
262+
: [...DEFAULT_SIDEBAR_VIEWS, SIDEBAR_VIEW_BOXAI, ...nonBoxAIPaths];
243263
}
244264

245-
// Separate box-ai custom panel from other custom panels
246-
const boxAiCustomPanel = customPanels.find(panel => panel.id === SIDEBAR_VIEW_BOXAI);
247-
const otherCustomPanels = customPanels.filter(panel => panel.id !== SIDEBAR_VIEW_BOXAI);
248-
const otherCustomPanelPaths = otherCustomPanels.map(panel => panel.path);
249-
250265
if (boxAiCustomPanel && shouldBoxAIBeDefaultPanel) {
251-
return [boxAiCustomPanel.path, ...DEFAULT_SIDEBAR_VIEWS, ...otherCustomPanelPaths];
266+
return [boxAiCustomPanel.path, ...DEFAULT_SIDEBAR_VIEWS, ...nonBoxAIPaths];
252267
}
253268

254-
return [...DEFAULT_SIDEBAR_VIEWS, ...customPanels.map(panel => panel.path)];
269+
return [...DEFAULT_SIDEBAR_VIEWS, ...customPanelPaths];
255270
};
256271

257272
render() {
258273
const {
259274
activitySidebarProps,
275+
boxAISidebarProps,
260276
customPanels,
261277
currentUser,
262278
currentUserError,
@@ -271,6 +287,7 @@ class SidebarPanels extends React.Component<Props, State> {
271287
getPreview,
272288
getViewer,
273289
hasActivity,
290+
hasNativeBoxAISidebar,
274291
hasDetails,
275292
hasDocGen,
276293
hasMetadata,
@@ -288,20 +305,25 @@ class SidebarPanels extends React.Component<Props, State> {
288305

289306
const isMetadataSidebarRedesignEnabled = isFeatureEnabled(features, 'metadata.redesign.enabled');
290307
const isMetadataAiSuggestionsEnabled = isFeatureEnabled(features, 'metadata.aiSuggestions.enabled');
291-
const { shouldBeDefaultPanel: shouldBoxAIBeDefaultPanel } = getFeatureConfig(features, 'boxai.sidebar');
308+
const { shouldBeDefaultPanel: shouldBoxAIBeDefaultPanel, showOnlyNavButton: showOnlyBoxAINavButton } =
309+
getFeatureConfig(features, 'boxai.sidebar');
310+
311+
const canShowBoxAISidebarPanel = hasNativeBoxAISidebar && !showOnlyBoxAINavButton;
292312

293313
const hasCustomPanels = customPanels && customPanels.length > 0;
294314

295-
// Build eligibility for custom panels
296315
const customPanelEligibility = {};
297316
if (hasCustomPanels) {
298317
// $FlowFixMe: customPanels is checked for existence in hasCustomPanels
299-
customPanels.forEach(({ path, isDisabled }) => {
300-
customPanelEligibility[path] = !isDisabled;
318+
customPanels.forEach(({ id, path, isDisabled }) => {
319+
const isBoxAICustomPanel = id === SIDEBAR_VIEW_BOXAI;
320+
const isEligible = isBoxAICustomPanel ? !hasNativeBoxAISidebar && !isDisabled : !isDisabled;
321+
customPanelEligibility[path] = isEligible;
301322
});
302323
}
303324

304325
const panelsEligibility = {
326+
[SIDEBAR_VIEW_BOXAI]: hasNativeBoxAISidebar,
305327
[SIDEBAR_VIEW_DOCGEN]: hasDocGen,
306328
[SIDEBAR_VIEW_SKILLS]: hasSkills,
307329
[SIDEBAR_VIEW_ACTIVITY]: hasActivity,
@@ -315,6 +337,7 @@ class SidebarPanels extends React.Component<Props, State> {
315337
if (
316338
!isOpen ||
317339
(!hasActivity &&
340+
!hasNativeBoxAISidebar &&
318341
!hasDetails &&
319342
!hasMetadata &&
320343
!hasSkills &&
@@ -327,6 +350,30 @@ class SidebarPanels extends React.Component<Props, State> {
327350

328351
return (
329352
<Switch>
353+
{/* Native Box AI route - takes precedence when hasNativeBoxAISidebar is true */}
354+
{canShowBoxAISidebarPanel && (
355+
<Route
356+
exact
357+
path={`/${SIDEBAR_VIEW_BOXAI}`}
358+
render={() => {
359+
this.handlePanelRender(SIDEBAR_VIEW_BOXAI);
360+
return (
361+
<LoadableBoxAISidebar
362+
contentName={file.name}
363+
elementId={elementId}
364+
fileExtension={file.extension}
365+
fileID={file.id}
366+
hasSidebarInitialized={isInitialized}
367+
ref={this.boxAISidebar}
368+
startMarkName={MARK_NAME_JS_LOADING_BOXAI}
369+
cache={this.boxAiSidebarCache}
370+
setCacheValue={this.setBoxAiSidebarCacheValue}
371+
{...boxAISidebarProps}
372+
/>
373+
);
374+
}}
375+
/>
376+
)}
330377
{hasCustomPanels &&
331378
// $FlowFixMe: customPanels is checked for existence in hasCustomPanels
332379
customPanels.map(customPanel => {
@@ -337,6 +384,11 @@ class SidebarPanels extends React.Component<Props, State> {
337384
isDisabled,
338385
} = customPanel;
339386

387+
const isBoxAICustomPanel = customPanelId === SIDEBAR_VIEW_BOXAI;
388+
if (isBoxAICustomPanel && hasNativeBoxAISidebar) {
389+
return null;
390+
}
391+
340392
if (isDisabled || !CustomPanelComponent) {
341393
return null;
342394
}
@@ -351,7 +403,8 @@ class SidebarPanels extends React.Component<Props, State> {
351403
return CustomPanelComponent ? (
352404
<CustomPanelComponent
353405
elementId={elementId}
354-
key={file.id}
406+
file={file}
407+
fileId={fileId}
355408
fileExtension={file.extension}
356409
hasSidebarInitialized={isInitialized}
357410
ref={this.getCustomSidebarRef(customPanelId)}
@@ -524,7 +577,11 @@ class SidebarPanels extends React.Component<Props, State> {
524577
redirect = defaultPanel;
525578
} else {
526579
// Use panel order to determine redirect
527-
const panelOrder = this.getPanelOrder(customPanels, shouldBoxAIBeDefaultPanel);
580+
const panelOrder = this.getPanelOrder(
581+
customPanels,
582+
shouldBoxAIBeDefaultPanel,
583+
hasNativeBoxAISidebar,
584+
);
528585
const firstEligiblePanel = panelOrder.find(panel => panelsEligibility[panel]);
529586
if (firstEligiblePanel) {
530587
redirect = firstEligiblePanel;

0 commit comments

Comments
 (0)