Skip to content

Commit 3998907

Browse files
committed
Merge branch 'develop' of github.com:devtron-labs/dashboard into feat/revamp-chart-store-details
2 parents 6b8d8d0 + 5d2d8d4 commit 3998907

File tree

12 files changed

+164
-97
lines changed

12 files changed

+164
-97
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"private": true,
55
"homepage": "/dashboard",
66
"dependencies": {
7-
"@devtron-labs/devtron-fe-common-lib": "1.17.0-beta-9",
7+
"@devtron-labs/devtron-fe-common-lib": "1.17.0-pre-12",
88
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
99
"@rjsf/core": "^5.13.3",
1010
"@rjsf/utils": "^5.13.3",

src/assets/img/img-page-blocked.webp

19.2 KB
Binary file not shown.

src/components/ResourceBrowser/ResourceBrowser.service.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,15 @@ import {
3636
getClusterListMinWithInstalledClusters,
3737
getClusterListWithInstalledClusters,
3838
} from '@Components/ClusterNodes/clusterNodes.service'
39+
import { importComponentFromFELibrary } from '@Components/common'
3940

4041
import { Routes } from '../../config'
4142
import { SIDEBAR_KEYS } from './Constants'
4243
import { ClusterDetailBaseParams, GetResourceDataType, NodeRowDetail } from './Types'
4344
import { parseNodeList, removeDefaultForStorageClass } from './Utils'
4445

46+
const ExplainWithAIButton = importComponentFromFELibrary('ExplainWithAIButton', null, 'function')
47+
4548
export const namespaceListByClusterId = async (clusterId: string) => {
4649
const response = await get<string[]>(`${Routes.CLUSTER_NAMESPACE}/${clusterId}`)
4750

@@ -136,8 +139,11 @@ export const getResourceData = async ({
136139
? removeDefaultForStorageClass(response.result.data)
137140
: response.result.data
138141

142+
const isEventList = selectedResource.gvk.Kind === Nodes.Event
143+
139144
return {
140145
...response.result,
146+
headers: [...response.result.headers, ...(isEventList && ExplainWithAIButton ? ['explainButton'] : [])],
141147
data: data.map((entry, index) => ({
142148
...entry,
143149
id: `${idPrefix}${index}`,

src/components/ResourceBrowser/ResourceList/K8SResourceList.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
abortPreviousRequests,
2222
ErrorScreenManager,
2323
FiltersTypeEnum,
24+
GenericFilterEmptyState,
2425
getAIAnalyticsEvents,
2526
getIsRequestAborted,
2627
K8sResourceDetailDataType,
@@ -129,7 +130,11 @@ export const K8SResourceList = ({
129130
const { clusterId } = useParams<K8sResourceListURLParams>()
130131

131132
// STATES
132-
const { selectedNamespace = 'all', ...filters } = useUrlFilters<string, K8sResourceListFilterType>({
133+
const {
134+
selectedNamespace = 'all',
135+
clearFilters,
136+
...filters
137+
} = useUrlFilters<string, K8sResourceListFilterType>({
133138
parseSearchParams: parseK8sResourceListSearchParams,
134139
})
135140

@@ -186,11 +191,13 @@ export const K8SResourceList = ({
186191
(header) =>
187192
({
188193
field: isNodeListing ? NODE_LIST_HEADERS_TO_KEY_MAP[header] : header,
189-
label: header === 'type' && isEventListing ? '' : header,
194+
label: (header === 'type' || header === 'explainButton') && isEventListing ? '' : header,
190195
size: getColumnSize(header, isEventListing),
191196
CellComponent: K8sResourceListTableCellComponent,
192197
comparator: getColumnComparator(header, isEventListing),
193-
isSortable: !isEventListing || (header !== 'message' && header !== 'type'),
198+
isSortable:
199+
!isEventListing ||
200+
(header !== 'message' && header !== 'type' && header !== 'explainButton'),
194201
horizontallySticky:
195202
header === 'name' || (isEventListing && (header === 'message' || header === 'type')),
196203
}) as K8sResourceListTableProps['columns'][0],
@@ -246,11 +253,18 @@ export const K8SResourceList = ({
246253
if (resourceListError && !isResourceListLoadingWithoutNullState) {
247254
return (
248255
<div className="flexbox-col flex-grow-1 border__primary--left">
249-
<ErrorScreenManager
250-
code={(resourceListError as ServerErrors).code}
251-
reload={reloadResourceList}
252-
redirectURL={URLS.RESOURCE_BROWSER}
253-
/>
256+
{filters.areFiltersApplied ? (
257+
<GenericFilterEmptyState
258+
title={`No ${selectedResource?.gvk.Kind ?? 'Resource'} found`}
259+
handleClearFilters={clearFilters}
260+
/>
261+
) : (
262+
<ErrorScreenManager
263+
code={(resourceListError as ServerErrors).code}
264+
reload={reloadResourceList}
265+
redirectURL={URLS.RESOURCE_BROWSER}
266+
/>
267+
)}
254268
</div>
255269
)
256270
}

src/components/ResourceBrowser/ResourceList/K8sResourceListTableCellComponent.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { AddEnvironmentFormPrefilledInfoType } from '@Pages/GlobalConfigurations
2626
import { ClusterEnvironmentDrawer } from '@Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer'
2727
import { ADD_ENVIRONMENT_FORM_LOCAL_STORAGE_KEY } from '@Pages/GlobalConfigurations/ClustersAndEnvironments/constants'
2828

29-
import { AI_BUTTON_CONFIG_MAP, K8S_EMPTY_GROUP } from '../Constants'
29+
import { AI_BUTTON_CONFIG_MAP, EVENT_LIST, K8S_EMPTY_GROUP } from '../Constants'
3030
import { ClusterDetailBaseParams } from '../Types'
3131
import { getRenderInvolvedObjectButton, getRenderNodeButton, renderResourceValue } from '../Utils'
3232
import NodeActionsMenu from './NodeActionsMenu'
@@ -200,6 +200,16 @@ const K8sResourceListTableCellComponent = ({
200200
)
201201
}
202202

203+
const eventDetails = {
204+
message: resourceData.message as string,
205+
namespace: resourceData.namespace as string,
206+
object: resourceData[EVENT_LIST.dataKeys.involvedObject] as string,
207+
source: resourceData.source as string,
208+
count: resourceData.count as number,
209+
age: resourceData.age as string,
210+
lastSeen: resourceData[EVENT_LIST.dataKeys.lastSeen] as string,
211+
}
212+
203213
return (
204214
<>
205215
{columnName === 'name' ? (
@@ -295,6 +305,19 @@ const K8sResourceListTableCellComponent = ({
295305
</Tooltip>
296306
</>
297307
)}
308+
{ExplainWithAIButton &&
309+
columnName === 'explainButton' &&
310+
isEventListing &&
311+
resourceData.type === 'Warning' && (
312+
<ExplainWithAIButton
313+
intelligenceConfig={{
314+
clusterId,
315+
metadata: eventDetails,
316+
prompt: JSON.stringify(eventDetails),
317+
analyticsCategory: getAIAnalyticsEvents('RB_RESOURCE'),
318+
}}
319+
/>
320+
)}
298321
<span>
299322
{columnName === 'restarts' && Number(resourceData.restarts) !== 0 && PodRestartIcon && (
300323
<PodRestartIcon

src/components/ResourceBrowser/ResourceList/utils.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@ export const getColumnSize = (field: string, isEventListing: boolean) => {
354354
startWidth: 140,
355355
},
356356
}
357+
case 'explainButton':
358+
return { fixed: 80 }
357359
default:
358360
return {
359361
range: {

src/components/common/DynamicTabs/types.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,7 @@ export type ParsedTabsData = {
8383
export interface PopulateTabDataPropsType
8484
extends Pick<
8585
DynamicTabType,
86-
| 'tippyConfig'
87-
| 'lastActiveTabId'
88-
| 'type'
89-
| 'isSelected'
90-
| 'url'
91-
| 'name'
92-
| 'dynamicTitle'
93-
| 'isAlive'
94-
| 'hideName'
95-
| 'id'
86+
'tippyConfig' | 'type' | 'isSelected' | 'url' | 'name' | 'dynamicTitle' | 'isAlive' | 'hideName' | 'id'
9687
>,
9788
Required<
9889
Pick<DynamicTabType, 'shouldRemainMounted' | 'title' | 'showNameOnSelect' | 'isAlpha' | 'defaultUrl'>

src/components/common/DynamicTabs/useTabs.ts

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { useMemo, useState } from 'react'
17+
import { useMemo, useRef, useState } from 'react'
1818
import dayjs from 'dayjs'
1919

2020
import { DynamicTabType, InitTabType, noop } from '@devtron-labs/devtron-fe-common-lib'
@@ -27,6 +27,8 @@ import { AddTabParamsType, ParsedTabsData, PopulateTabDataPropsType, UseTabsRetu
2727
export function useTabs(persistenceKey: string, fallbackTabIndex = FALLBACK_TAB): UseTabsReturnType {
2828
const [tabs, setTabs] = useState<DynamicTabType[]>([])
2929

30+
const previousActiveTabIdRef = useRef<string>(null)
31+
3032
const tabIdToTabMap = useMemo(
3133
() =>
3234
tabs.reduce((acc, tab) => {
@@ -49,15 +51,6 @@ export function useTabs(persistenceKey: string, fallbackTabIndex = FALLBACK_TAB)
4951
const getTitleFromKindAndName = ({ kind, name }: Pick<InitTabType, 'kind' | 'name'>): string =>
5052
kind ? `${kind}/${name}` : name
5153

52-
const getLastActiveTabIdFromTabs = (
53-
_tabs: DynamicTabType[],
54-
id: DynamicTabType['id'],
55-
): DynamicTabType['lastActiveTabId'] | null => {
56-
const selectedTabId = _tabs.find((tab) => tab.isSelected)?.id ?? null
57-
58-
return selectedTabId !== id ? selectedTabId : null
59-
}
60-
6154
const populateTabData = ({
6255
id,
6356
name,
@@ -70,7 +63,6 @@ export function useTabs(persistenceKey: string, fallbackTabIndex = FALLBACK_TAB)
7063
isAlive = false,
7164
hideName = false,
7265
tippyConfig,
73-
lastActiveTabId,
7466
shouldRemainMounted,
7567
isAlpha,
7668
defaultUrl,
@@ -88,7 +80,6 @@ export function useTabs(persistenceKey: string, fallbackTabIndex = FALLBACK_TAB)
8880
lastSyncMoment: dayjs(),
8981
componentKey: getNewTabComponentKey(id),
9082
tippyConfig,
91-
lastActiveTabId,
9283
shouldRemainMounted,
9384
isAlpha: isAlpha || false,
9485
defaultUrl: defaultUrl || null,
@@ -161,7 +152,6 @@ export function useTabs(persistenceKey: string, fallbackTabIndex = FALLBACK_TAB)
161152
isAlive: !!_initTab.isAlive,
162153
hideName: _initTab.hideName,
163154
tippyConfig: _initTab.tippyConfig,
164-
lastActiveTabId: null,
165155
shouldRemainMounted: _initTab.shouldRemainMounted,
166156
isAlpha: _initTab.isAlpha,
167157
defaultUrl: _initTab.defaultUrl,
@@ -309,6 +299,11 @@ export function useTabs(persistenceKey: string, fallbackTabIndex = FALLBACK_TAB)
309299
const _tabs = prevTabs.map((tab) => {
310300
const matched = tab.id === _id
311301
found = found || matched
302+
303+
if (tab.isSelected && !matched) {
304+
previousActiveTabIdRef.current = tab.id
305+
}
306+
312307
return matched
313308
? {
314309
...tab,
@@ -335,7 +330,6 @@ export function useTabs(persistenceKey: string, fallbackTabIndex = FALLBACK_TAB)
335330
showNameOnSelect,
336331
dynamicTitle,
337332
tippyConfig,
338-
lastActiveTabId: getLastActiveTabIdFromTabs(prevTabs, _id),
339333
shouldRemainMounted: false,
340334
isAlpha: false,
341335
defaultUrl: null,
@@ -355,12 +349,12 @@ export function useTabs(persistenceKey: string, fallbackTabIndex = FALLBACK_TAB)
355349
const tabToBeRemoved = prevTabs.find((tab) => tab.id === id) ?? ({} as DynamicTabType)
356350

357351
if (tabToBeRemoved.isSelected) {
358-
const switchFromTabIndex = tabs.findIndex((tab) => tab.id === tabToBeRemoved.lastActiveTabId)
352+
const previousSelectedTabIndex = prevTabs.findIndex(
353+
(tab) => tab.id === previousActiveTabIdRef.current,
354+
)
355+
previousActiveTabIdRef.current = null
359356
const newSelectedTabIndex =
360-
// The id and lastActiveTabId can be same when the same tab is clicked again
361-
switchFromTabIndex > -1 && tabToBeRemoved.id !== tabToBeRemoved.lastActiveTabId
362-
? switchFromTabIndex
363-
: fallbackTabIndex
357+
previousSelectedTabIndex > -1 ? previousSelectedTabIndex : fallbackTabIndex
364358
// Cannot use structured clone since using it reloads the whole data
365359
// eslint-disable-next-line no-param-reassign
366360
prevTabs[newSelectedTabIndex].isSelected = true
@@ -415,22 +409,31 @@ export function useTabs(persistenceKey: string, fallbackTabIndex = FALLBACK_TAB)
415409

416410
setTabs((prevTabs) => {
417411
let isTabFound = false
412+
let previousActiveTab = null
413+
418414
const _tabs = prevTabs.map((tab) => {
419415
const isMatch = tab.id === id
420416
isTabFound = isMatch || isTabFound
421417

418+
if (tab.isSelected && !isMatch) {
419+
previousActiveTab = tab
420+
}
421+
422422
return {
423423
...tab,
424424
isSelected: isMatch,
425425
url: (isMatch && url) || tab.url,
426-
...(isMatch && {
427-
lastActiveTabId: getLastActiveTabIdFromTabs(prevTabs, tab.id),
428-
...(tab.showNameOnSelect && {
426+
...(isMatch &&
427+
tab.showNameOnSelect && {
429428
isAlive: true,
430429
}),
431-
}),
432430
}
433431
})
432+
433+
if (isTabFound && previousActiveTab) {
434+
previousActiveTabIdRef.current = previousActiveTab.id
435+
}
436+
434437
resolve(isTabFound)
435438
updateTabsInLocalStorage(_tabs)
436439
return _tabs

src/components/common/SidePanel/SidePanelContent.tsx

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,37 +21,36 @@ import { SidePanelHeaderActions } from './SidePanelHeaderActions'
2121
import { SidePanelContentProps } from './types'
2222
import { getContentWrapperClassNameForTab, renderOpenTicketButton } from './utils'
2323

24-
const AIChat = importComponentFromFELibrary(
25-
'AIChat',
26-
() => (
27-
<EnterpriseTrialDialog
28-
featureTitle="Ask Devtron Intelligence"
29-
featureDescription="Supercharge your troubleshooting! Instantly ask AI about your application or Kubernetes issues and get expert guidance at your fingertips."
30-
showBorder={false}
31-
/>
32-
),
33-
'function',
34-
)
24+
const AIChat = importComponentFromFELibrary('AIChat', null, 'function')
3525

3626
export const SidePanelContent = ({ onClose, setSidePanelConfig, sidePanelConfig }: SidePanelContentProps) => {
3727
const tab = sidePanelConfig.state as SidePanelTab
3828

3929
const renderAIChat = () => {
40-
// NOTE: even if flag is off, for oss clients need to show EnterpriseTrialDialog
41-
if (!AIChat || window._env_?.FEATURE_ASK_DEVTRON_EXPERT) {
42-
return <AIChat SidePanelHeaderActions={SidePanelHeaderActions} />
30+
if (!AIChat) {
31+
return (
32+
<EnterpriseTrialDialog
33+
featureTitle="Ask Devtron Intelligence"
34+
featureDescription="Supercharge your troubleshooting! Instantly ask AI about your application or Kubernetes issues and get expert guidance at your fingertips."
35+
showBorder={false}
36+
/>
37+
)
4338
}
4439

45-
return (
46-
<GenericEmptyState
47-
title="AI Integration not configured"
48-
subTitle="For AI-powered insights, please follow documentation or contact the Devtron team."
49-
SvgImage={ICMaintenance}
50-
imageType={ImageType.Medium}
51-
isButtonAvailable
52-
renderButton={renderOpenTicketButton}
53-
/>
54-
)
40+
if (!window._env_?.FEATURE_ASK_DEVTRON_EXPERT) {
41+
return (
42+
<GenericEmptyState
43+
title="AI Integration not configured"
44+
subTitle="For AI-powered insights, please follow documentation or contact the Devtron team."
45+
SvgImage={ICMaintenance}
46+
imageType={ImageType.Medium}
47+
isButtonAvailable
48+
renderButton={renderOpenTicketButton}
49+
/>
50+
)
51+
}
52+
53+
return <AIChat SidePanelHeaderActions={SidePanelHeaderActions} />
5554
}
5655

5756
return (

0 commit comments

Comments
 (0)