- {loading ? (
+ {loading && !numberOfColumnsWithoutBulkActionGutter.length ? (
- {isBulkSelectionConfigured ?
: null}
+ {showIconOrExpandActionGutter || isBulkSelectionConfigured ? (
+
+ ) : null}
{SHIMMER_DUMMY_ARRAY.map((label) => (
))}
@@ -337,6 +552,32 @@ const TableContent = <
gridTemplateColumns,
}}
>
+ {isAnyRowExpandable ? (
+
+
+ }
+ ariaLabel="Expand/Collapse all rows"
+ showAriaLabelInTippy={false}
+ variant={ButtonVariantType.borderLess}
+ size={ComponentSizeType.xxs}
+ style={ButtonStyleType.neutral}
+ onClick={toggleExpandAll}
+ />
+
+ ) : null}
+
+ {!isAnyRowExpandable && rowStartIconConfig && !isBulkSelectionConfigured &&
}
+
{visibleColumns.map(
(
{
@@ -346,6 +587,7 @@ const TableContent = <
size,
showTippyOnTruncate,
horizontallySticky: isStickyColumn,
+ infoTooltipText,
},
index,
) => {
@@ -353,7 +595,12 @@ const TableContent = <
const isBulkActionGutter = field === BULK_ACTION_GUTTER_LABEL
const horizontallySticky = isStickyColumn || isBulkActionGutter
const { className: stickyClassName = '', left: stickyLeftValue = '' } =
- horizontallySticky ? getStickyColumnConfig(gridTemplateColumns, index) : {}
+ horizontallySticky
+ ? getStickyColumnConfig(
+ gridTemplateColumns,
+ index + (isAnyRowExpandable ? 1 : 0),
+ )
+ : {}
if (field === BULK_ACTION_GUTTER_LABEL) {
return (
@@ -379,7 +626,7 @@ const TableContent = <
return (
@@ -392,6 +639,7 @@ const TableContent = <
triggerSorting={getTriggerSortingHandler(field)}
showTippyOnTruncate={showTippyOnTruncate}
disabled={areFilteredRowsLoading}
+ infoTooltipText={infoTooltipText}
{...(isResizable
? { isResizable, handleResize, id: label }
: { isResizable: false })}
diff --git a/src/Shared/Components/Table/constants.ts b/src/Shared/Components/Table/constants.ts
index 1b7ed8b0f..63005f738 100644
--- a/src/Shared/Components/Table/constants.ts
+++ b/src/Shared/Components/Table/constants.ts
@@ -29,3 +29,5 @@ export const DRAG_SELECTOR_IDENTIFIER = 'table-drag-selector'
export const SHIMMER_DUMMY_ARRAY = [1, 2, 3]
export const NO_ROWS_OR_GET_ROWS_ERROR = new Error('Neither rows nor getRows function provided')
+
+export const ACTION_GUTTER_SIZE = 24
diff --git a/src/Shared/Components/Table/styles.scss b/src/Shared/Components/Table/styles.scss
index ed2fb7c1e..7921e401d 100644
--- a/src/Shared/Components/Table/styles.scss
+++ b/src/Shared/Components/Table/styles.scss
@@ -40,6 +40,13 @@
left: -20px;
width: 20px;
}
+
+ &.expand-row-btn::before,
+ &.row-start-icon::before,
+ &.expanded-tree-line::before {
+ left: -24px;
+ width: 24px;
+ }
}
&--scrolled {
@@ -69,6 +76,10 @@
outline: none;
}
+ &--expanded-row:has(+ .generic-table__row--expanded-row) {
+ border-bottom: 0px;
+ }
+
&:hover,
&:hover > *,
&--active,
@@ -94,6 +105,33 @@
display: inherit;
}
}
+
+ &.with-start-icon-and-bulk-or-expand-action {
+ .bulk-action-checkbox {
+ display: none;
+ }
+
+ .expand-row-btn {
+ display: none;
+ }
+
+ &:hover,
+ &.generic-table__row--active,
+ &.generic-table__row--bulk-selected,
+ &.generic-table__row--is-expanded {
+ .row-start-icon {
+ display: none;
+ }
+
+ .bulk-action-checkbox {
+ display: flex;
+ }
+
+ .expand-row-btn {
+ display: flex;
+ }
+ }
+ }
}
.sortable-table-header__resize-btn:hover,
@@ -105,4 +143,18 @@
transform: scaleY(var(--resize-btn-scale-multiplier));
}
}
+
+ .expanded-tree-line::after {
+ content: '';
+ width: 1px;
+ height: 100%;
+ background: var(--N200);
+ left: calc(50% - 1px); // offset to left by width for perfect centering
+ top: 0;
+ position: absolute;
+ }
+
+ .pagination__select-menu {
+ z-index: 1;
+ }
}
diff --git a/src/Shared/Components/Table/types.ts b/src/Shared/Components/Table/types.ts
index 6d312f7ea..1e357df0b 100644
--- a/src/Shared/Components/Table/types.ts
+++ b/src/Shared/Components/Table/types.ts
@@ -26,12 +26,15 @@ import {
import { GenericEmptyStateType } from '@Common/index'
import { PageSizeOption } from '@Common/Pagination/types'
import { SortableTableHeaderCellProps, useResizableTableConfig } from '@Common/SortableTableHeaderCell'
+import { IconsProps } from '@Shared/Components/Icon'
import { useBulkSelection, UseBulkSelectionProps } from '../BulkSelection'
export interface UseFiltersReturnType extends UseStateFiltersReturnType
{}
export enum SignalEnum {
+ COLLAPSE_ROW = 'collapse-row',
+ EXPAND_ROW = 'expand-row',
ENTER_PRESSED = 'enter-pressed',
DELETE_PRESSED = 'delete-pressed',
ESCAPE_PRESSED = 'escape-pressed',
@@ -89,13 +92,23 @@ type BaseColumnType = {
size: SizeType
horizontallySticky?: boolean
-}
+} & Pick
-export type RowType = {
+type CommonRowType = {
id: string
data: Data
}
+export type ExpandedRowPrefixType = 'expanded-row-'
+
+export type ExpandedRowType = CommonRowType & {
+ id: `${ExpandedRowPrefixType}${string}`
+}
+
+export type RowType = CommonRowType & {
+ expandableRows?: Array>
+}
+
export type RowsType = RowType[]
export enum FiltersTypeEnum {
@@ -119,6 +132,10 @@ export type CellComponentProps<
? UseFiltersReturnType
: UseUrlFiltersReturnType
isRowActive: boolean
+ isExpandedRow: boolean
+ isRowInExpandState: boolean
+ // NOTE: no action if the row is not expandable
+ expandRowCallback: (e: MouseEvent) => void
}
export type RowActionsOnHoverComponentProps<
@@ -226,7 +243,11 @@ export type ViewWrapperProps<
: {})
>
-type FilterConfig = {
+type FilterConfig<
+ FilterVariant extends FiltersTypeEnum,
+ RowData extends unknown,
+ AdditionalProps extends Record,
+> = {
filtersVariant: FilterVariant
/**
* Props for useUrlFilters/useStateFilters hooks
@@ -240,12 +261,14 @@ type FilterConfig, filterData: UseFiltersReturnType) => boolean
+ : (row: RowType, filterData: UseFiltersReturnType, additionalProps: AdditionalProps) => boolean
clearFilters?: FilterVariant extends FiltersTypeEnum.URL
? () => void
: FilterVariant extends FiltersTypeEnum.STATE
? never
: never
+
+ areFiltersApplied?: FilterVariant extends FiltersTypeEnum.NONE ? never : boolean
}
export type InternalTableProps<
@@ -272,7 +295,7 @@ export type InternalTableProps<
emptyStateConfig: {
noRowsConfig: Omit
- noRowsForFilterConfig?: Pick & {
+ noRowsForFilterConfig?: Pick & {
clearFilters?: () => void
}
}
@@ -311,6 +334,14 @@ export type InternalTableProps<
handleToggleBulkSelectionOnRow: (row: RowType) => void
ViewWrapper?: FunctionComponent>
+
+ /**
+ * An icon as the first element of the row, that hides actions like expand or bulk select icons
+ * until user hovers over the row or the row has focus from keyboard navigation
+ */
+ rowStartIconConfig?: Omit
+
+ onRowClick?: (row: RowType, isExpandedRow: boolean) => void
} & (
| {
/**
@@ -341,7 +372,7 @@ export type InternalTableProps<
pageSizeOptions?: never
}
) &
- FilterConfig
+ FilterConfig
export type UseResizableTableConfigWrapperProps<
RowData extends unknown,
@@ -394,6 +425,9 @@ export type TableProps<
| 'ViewWrapper'
| 'pageSizeOptions'
| 'clearFilters'
+ | 'rowStartIconConfig'
+ | 'onRowClick'
+ | 'areFiltersApplied'
>
export type BulkActionStateType = string | null
@@ -448,6 +482,8 @@ export interface TableContentProps<
| 'rowActionOnHoverConfig'
| 'pageSizeOptions'
| 'getRows'
+ | 'rowStartIconConfig'
+ | 'onRowClick'
>,
RowsResultType {
areFilteredRowsLoading: boolean
diff --git a/src/Shared/Components/Table/useTableWithKeyboardShortcuts.ts b/src/Shared/Components/Table/useTableWithKeyboardShortcuts.ts
index a7a67a14b..cb51044c9 100644
--- a/src/Shared/Components/Table/useTableWithKeyboardShortcuts.ts
+++ b/src/Shared/Components/Table/useTableWithKeyboardShortcuts.ts
@@ -101,6 +101,20 @@ const useTableWithKeyboardShortcuts = <
)
useEffect(() => {
+ registerShortcut({
+ keys: ['ArrowLeft'],
+ callback: () => {
+ dispatchEvent(SignalEnum.COLLAPSE_ROW)
+ },
+ })
+
+ registerShortcut({
+ keys: ['ArrowRight'],
+ callback: () => {
+ dispatchEvent(SignalEnum.EXPAND_ROW)
+ },
+ })
+
registerShortcut({
keys: ['ArrowDown'],
callback: () => {
@@ -142,6 +156,8 @@ const useTableWithKeyboardShortcuts = <
unregisterShortcut(['Enter'])
unregisterShortcut(['Backspace'])
unregisterShortcut(['.'])
+ unregisterShortcut(['ArrowLeft'])
+ unregisterShortcut(['ArrowRight'])
}
}, [getMoveFocusToNextRowHandler, getMoveFocusToPreviousRowHandler, dispatchEvent])
diff --git a/src/Shared/Components/Table/utils.ts b/src/Shared/Components/Table/utils.ts
index 8e9ff9580..5d0f4b46a 100644
--- a/src/Shared/Components/Table/utils.ts
+++ b/src/Shared/Components/Table/utils.ts
@@ -42,11 +42,12 @@ export const searchAndSortRows = <
rows: TableProps['rows'],
filter: TableProps['filter'],
filterData: UseFiltersReturnType,
+ additionalProps: AdditionalProps,
comparator?: Column['comparator'],
): Awaited['getRows']>> => {
const { sortBy, sortOrder } = filterData ?? {}
- const filteredRows = filter ? rows.filter((row) => filter(row, filterData)) : rows
+ const filteredRows = filter ? rows.filter((row) => filter(row, filterData, additionalProps)) : rows
return {
rows:
diff --git a/src/Shared/Components/TagsKeyValueTable/TagsContainer.tsx b/src/Shared/Components/TagsKeyValueTable/TagsContainer.tsx
index 661e30331..cb8bd64eb 100644
--- a/src/Shared/Components/TagsKeyValueTable/TagsContainer.tsx
+++ b/src/Shared/Components/TagsKeyValueTable/TagsContainer.tsx
@@ -16,7 +16,7 @@
import ReactGA from 'react-ga4'
-import { ReactComponent as ICPropagate } from '@Icons/inject-tag.svg'
+import ICPropagate from '@Icons/inject-tag.svg?react'
import { PropagateTagInfo, validateTagKeyValue } from '@Common/CustomTagSelector'
import { validateTagValue } from '@Common/CustomTagSelector/tags.utils'
import { Tooltip } from '@Common/Tooltip'
diff --git a/src/Shared/Components/TargetPlatforms/TargetPlatformBadgeList.tsx b/src/Shared/Components/TargetPlatforms/TargetPlatformBadgeList.tsx
index a94653b1f..c3fc5b4c4 100644
--- a/src/Shared/Components/TargetPlatforms/TargetPlatformBadgeList.tsx
+++ b/src/Shared/Components/TargetPlatforms/TargetPlatformBadgeList.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { ReactComponent as ICStack } from '@Icons/ic-stack.svg'
+import ICStack from '@Icons/ic-stack.svg?react'
import { Tooltip } from '@Common/Tooltip'
import { TargetPlatformBadgeListProps } from './types'
diff --git a/src/Shared/Components/Textarea/Textarea.component.tsx b/src/Shared/Components/Textarea/Textarea.component.tsx
index a50374557..8a6080be6 100644
--- a/src/Shared/Components/Textarea/Textarea.component.tsx
+++ b/src/Shared/Components/Textarea/Textarea.component.tsx
@@ -82,21 +82,22 @@ const Textarea = ({
}, [])
const reInitHeight = () => {
- const currentHeight = parseInt(textareaRef.current.style.height, 10)
- let nextHeight = textareaRef.current.scrollHeight || 0
-
- if (nextHeight < currentHeight || currentHeight > AUTO_EXPANSION_MAX_HEIGHT) {
+ const textarea = textareaRef.current
+ if (!textarea) {
return
}
- if (nextHeight < MIN_HEIGHT) {
- nextHeight = MIN_HEIGHT
- }
+ textarea.style.height = 'auto'
+ let nextHeight = textarea.scrollHeight
if (nextHeight > AUTO_EXPANSION_MAX_HEIGHT) {
nextHeight = AUTO_EXPANSION_MAX_HEIGHT
+ textarea.style.overflowY = 'auto'
+ } else {
+ textarea.style.overflowY = 'hidden'
}
+ nextHeight = Math.max(MIN_HEIGHT, nextHeight)
updateRefsHeight(nextHeight)
}
diff --git a/src/Shared/Components/TreeView/TreeView.component.tsx b/src/Shared/Components/TreeView/TreeView.component.tsx
index de02588e7..baefcf295 100644
--- a/src/Shared/Components/TreeView/TreeView.component.tsx
+++ b/src/Shared/Components/TreeView/TreeView.component.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { SyntheticEvent, useEffect, useMemo, useRef, useState } from 'react'
+import { type JSX, SyntheticEvent, useEffect, useMemo, useRef, useState } from 'react'
import { NavLink, useLocation } from 'react-router-dom'
import { AnimatePresence, motion } from 'framer-motion'
diff --git a/src/Shared/Components/TreeView/types.ts b/src/Shared/Components/TreeView/types.ts
index bf3903886..068ac6dcf 100644
--- a/src/Shared/Components/TreeView/types.ts
+++ b/src/Shared/Components/TreeView/types.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { SyntheticEvent } from 'react'
+import { type JSX, SyntheticEvent } from 'react'
import { TooltipProps } from '@Common/Tooltip'
import { DataAttributes, Never } from '@Shared/types'
diff --git a/src/Shared/Components/UnsavedChanges/UnsavedChanges.component.tsx b/src/Shared/Components/UnsavedChanges/UnsavedChanges.component.tsx
index 892d31523..ea38b21f0 100644
--- a/src/Shared/Components/UnsavedChanges/UnsavedChanges.component.tsx
+++ b/src/Shared/Components/UnsavedChanges/UnsavedChanges.component.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { ReactComponent as ICWarningY5 } from '@Icons/ic-warning-y5.svg'
+import ICWarningY5 from '@Icons/ic-warning-y5.svg?react'
const UnsavedChanges = () => (
diff --git a/src/Shared/Components/WorkflowOptionsModal/WorkflowOptionsModal.tsx b/src/Shared/Components/WorkflowOptionsModal/WorkflowOptionsModal.tsx
index b7519abeb..ba2af2e44 100644
--- a/src/Shared/Components/WorkflowOptionsModal/WorkflowOptionsModal.tsx
+++ b/src/Shared/Components/WorkflowOptionsModal/WorkflowOptionsModal.tsx
@@ -16,7 +16,7 @@
import { useEffect, useMemo, useState } from 'react'
-import { ReactComponent as ImgWorkflowOptionsModalHeader } from '@Images/workflow-options-modal-header.svg'
+import ImgWorkflowOptionsModalHeader from '@Images/workflow-options-modal-header.svg?react'
import { noop, showError } from '@Common/Helper'
import { PipelineType, WorkflowNodeType } from '@Common/Types'
import { ComponentSizeType } from '@Shared/constants'
diff --git a/src/Shared/Components/index.ts b/src/Shared/Components/index.ts
index 53f510883..4ace4fc27 100644
--- a/src/Shared/Components/index.ts
+++ b/src/Shared/Components/index.ts
@@ -39,7 +39,6 @@ export * from './CodeEditor'
export * from './Collapse'
export * from './CollapsibleList'
export * from './CommitChipCell'
-export * from './Confetti'
export * from './ConfirmationModal'
export * from './ContextSwitcher'
export * from './CountrySelect'
diff --git a/src/Shared/Helpers.tsx b/src/Shared/Helpers.tsx
index 39cb22c83..1ed9bc08d 100644
--- a/src/Shared/Helpers.tsx
+++ b/src/Shared/Helpers.tsx
@@ -25,14 +25,14 @@ import moment from 'moment'
import { nanoid } from 'nanoid'
import { Pair, parse } from 'yaml'
-import { ReactComponent as ICAWSCodeCommit } from '@Icons/ic-aws-codecommit.svg'
-import { ReactComponent as ICBitbucket } from '@Icons/ic-bitbucket.svg'
-import { ReactComponent as ICGit } from '@Icons/ic-git.svg'
-import { ReactComponent as ICGithub } from '@Icons/ic-github.svg'
-import { ReactComponent as ICGitlab } from '@Icons/ic-gitlab.svg'
-import { ReactComponent as ICPullRequest } from '@Icons/ic-pull-request.svg'
-import { ReactComponent as ICTag } from '@Icons/ic-tag.svg'
-import { ReactComponent as ICWebhook } from '@Icons/ic-webhook.svg'
+import ICAWSCodeCommit from '@Icons/ic-aws-codecommit.svg?react'
+import ICBitbucket from '@Icons/ic-bitbucket.svg?react'
+import ICGit from '@Icons/ic-git.svg?react'
+import ICGithub from '@Icons/ic-github.svg?react'
+import ICGitlab from '@Icons/ic-gitlab.svg?react'
+import ICPullRequest from '@Icons/ic-pull-request.svg?react'
+import ICTag from '@Icons/ic-tag.svg?react'
+import ICWebhook from '@Icons/ic-webhook.svg?react'
import { MaterialHistoryType } from '@Shared/Services/app.types'
import {
diff --git a/src/Shared/Hooks/useGetResourceKindsOptions/index.ts b/src/Shared/Hooks/useGetResourceKindsOptions/index.ts
index 86a41c642..0a1de1da2 100644
--- a/src/Shared/Hooks/useGetResourceKindsOptions/index.ts
+++ b/src/Shared/Hooks/useGetResourceKindsOptions/index.ts
@@ -14,5 +14,6 @@
* limitations under the License.
*/
+export { getProjectOptions } from './service'
export type { UseGetResourceKindOptionsReturnType, UseGetResourceKindsOptionsProps } from './types'
export { default as useGetResourceKindsOptions } from './useGetResourceKindsOptions'
diff --git a/src/Shared/Hooks/useUserPreferences/index.ts b/src/Shared/Hooks/useUserPreferences/index.ts
index 461674b2a..ccf3ce08b 100644
--- a/src/Shared/Hooks/useUserPreferences/index.ts
+++ b/src/Shared/Hooks/useUserPreferences/index.ts
@@ -15,6 +15,6 @@
*/
export * from './constants'
-export { getUserPreferences, updateAndPersistUserPreferences, updateUserPreferences } from './service'
+export { getUserPreferences, updateUserPreferences } from './service'
export * from './types'
export { useUserPreferences } from './useUserPrefrences'
diff --git a/src/Shared/Hooks/useUserPreferences/service.ts b/src/Shared/Hooks/useUserPreferences/service.ts
index f9fa139cb..b63b6b71b 100644
--- a/src/Shared/Hooks/useUserPreferences/service.ts
+++ b/src/Shared/Hooks/useUserPreferences/service.ts
@@ -16,6 +16,7 @@
import { ROUTES } from '@Common/Constants'
import { get, getUrlWithSearchParams, patch, showError } from '@Common/index'
+import { ResourceKindType } from '@Shared/index'
import { THEME_PREFERENCE_MAP } from '@Shared/Providers/ThemeProvider/types'
import { USER_PREFERENCES_ATTRIBUTE_KEY } from './constants'
@@ -177,12 +178,39 @@ export const updateUserPreferences = async ({
}
}
+const migrateUserPreferences = async () => {
+ try {
+ const userPreferences: UserPreferencesType = await JSON.parse(
+ localStorage.getItem(USER_PREFERENCES_ATTRIBUTE_KEY),
+ )
+ if (userPreferences && userPreferences.version !== 'v1') {
+ const migratedPreferences: UserPreferencesType = {
+ ...userPreferences,
+ resources: {
+ ...userPreferences.resources,
+ [ResourceKindType.devtronApplication]: {
+ [UserPreferenceResourceActions.RECENTLY_VISITED]: (
+ userPreferences.resources?.[ResourceKindType.devtronApplication]?.[
+ UserPreferenceResourceActions.RECENTLY_VISITED
+ ] || []
+ ).map(({ id, name }) => ({ id: +id, name })),
+ },
+ },
+ version: 'v1',
+ }
+ localStorage.setItem(USER_PREFERENCES_ATTRIBUTE_KEY, JSON.stringify(migratedPreferences))
+ }
+ } catch {
+ // do nothing
+ }
+}
+
/**
* Optimized function to get updated user preferences with resource filtering
* Can work with provided userPreferences or fetch from server/localStorage
* Centralizes all resource updating logic
*/
-export const getUpdatedUserPreferences = async ({
+const getUpdatedUserPreferences = async ({
id,
name,
resourceKind,
@@ -190,6 +218,8 @@ export const getUpdatedUserPreferences = async ({
}: UserPreferenceFilteredListTypes & {
userPreferencesResponse?: UserPreferencesType
}): Promise
=> {
+ await migrateUserPreferences()
+
// Get base user preferences from multiple sources (priority: provided > localStorage > server)
let baseUserPreferences: UserPreferencesType
@@ -197,7 +227,7 @@ export const getUpdatedUserPreferences = async ({
baseUserPreferences = userPreferencesResponse
} else {
try {
- const localStorageData = localStorage.getItem('userPreferences')
+ const localStorageData = localStorage.getItem(USER_PREFERENCES_ATTRIBUTE_KEY)
if (localStorageData) {
baseUserPreferences = JSON.parse(localStorageData)
} else {
@@ -257,7 +287,7 @@ export const updateAndPersistUserPreferences = async ({
// Update localStorage if requested
if (updateLocalStorage) {
- localStorage.setItem('userPreferences', JSON.stringify(updatedPreferences))
+ localStorage.setItem(USER_PREFERENCES_ATTRIBUTE_KEY, JSON.stringify(updatedPreferences))
}
// Update server with the new resource list if resourceKind is provided
diff --git a/src/Shared/Hooks/useUserPreferences/types.ts b/src/Shared/Hooks/useUserPreferences/types.ts
index a03c7eb10..ce2c24054 100644
--- a/src/Shared/Hooks/useUserPreferences/types.ts
+++ b/src/Shared/Hooks/useUserPreferences/types.ts
@@ -102,6 +102,10 @@ export interface UserPreferencesType extends Pick {}
diff --git a/src/Shared/Providers/MainContextProvider/index.ts b/src/Shared/Providers/MainContextProvider/index.ts
index 8c0dd4e1d..332cee39d 100644
--- a/src/Shared/Providers/MainContextProvider/index.ts
+++ b/src/Shared/Providers/MainContextProvider/index.ts
@@ -16,4 +16,4 @@
export * from './MainContextProvider'
export type { MainContext, ReloadVersionConfigTypes, SidePanelConfig, TempAppWindowConfig } from './types'
-export { SidePanelTab } from './types'
+export { AIAgentContextSourceType, type AIAgentContextType, SidePanelTab } from './types'
diff --git a/src/Shared/Providers/MainContextProvider/types.ts b/src/Shared/Providers/MainContextProvider/types.ts
index f04949c4d..49e5e32d3 100644
--- a/src/Shared/Providers/MainContextProvider/types.ts
+++ b/src/Shared/Providers/MainContextProvider/types.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { Dispatch, MutableRefObject, ReactNode, SetStateAction } from 'react'
+import { Dispatch, FunctionComponent, type JSX, MutableRefObject, ReactNode, SetStateAction } from 'react'
import { SERVER_MODE } from '../../../Common'
import {
@@ -48,11 +48,71 @@ export interface SidePanelConfig {
/** URL to documentation that should be displayed in the panel */
docLink: string | null
aiSessionId?: string
+ isExpandedView?: boolean
}
-type AIAgentContextType = {
- path: string
- context: Record
+export enum AIAgentContextSourceType {
+ APP_DETAILS = 'app-details',
+ RESOURCE_BROWSER_CLUSTER = 'resource-browser-cluster',
+}
+
+export type AIAgentAppType =
+ | 'devtronApp'
+ | 'devtronHelmChart'
+ | 'externalHelmChart'
+ | 'externalArgoApp'
+ | 'externalFluxApp'
+
+type AIAgentAppDataMasterType = {
+ appId: number | string
+ appName: string
+ envId: number
+ envName: string
+ clusterId: number
+ namespace: string
+ appType: AIAgentAppType
+ fluxAppDeploymentType: string
+}
+
+type AIAgentAppDataType = Pick<
+ AIAgentAppDataMasterType,
+ TRequiredFields
+> & {
+ [K in Exclude]?: never
+} & {
+ appType: TAppType
+}
+
+type CommonContextDataType = Record & {
+ uiMarkup?: string
+}
+
+export type AIAgentContextType =
+ | {
+ source: AIAgentContextSourceType.APP_DETAILS
+ data:
+ | AIAgentAppDataType<
+ 'devtronApp' | 'devtronHelmChart',
+ 'appId' | 'appName' | 'envId' | 'envName' | 'clusterId'
+ >
+ | AIAgentAppDataType<'externalHelmChart', 'appId' | 'appName' | 'clusterId' | 'namespace'>
+ | AIAgentAppDataType<'externalArgoApp', 'appName' | 'clusterId' | 'namespace'>
+ | (AIAgentAppDataType<
+ 'externalFluxApp',
+ 'appName' | 'clusterId' | 'namespace' | 'fluxAppDeploymentType'
+ > &
+ CommonContextDataType)
+ }
+ | {
+ source: AIAgentContextSourceType.RESOURCE_BROWSER_CLUSTER
+ data: {
+ clusterId: number
+ clusterName: string
+ } & CommonContextDataType
+ }
+
+export type DebugAgentContextType = AIAgentContextType & {
+ prompt?: string
}
export interface TempAppWindowConfig {
@@ -118,6 +178,8 @@ type CommonMainContextProps = {
setLicenseData: Dispatch>
canFetchHelmAppStatus: boolean
setIntelligenceConfig: Dispatch>
+ debugAgentContext: DebugAgentContextType | null
+ setDebugAgentContext: (aiAgentContext: DebugAgentContextType | null) => void
setAIAgentContext: (aiAgentContext: AIAgentContextType) => void
setSidePanelConfig: Dispatch>
} & Pick
@@ -152,6 +214,11 @@ export type MainContext = CommonMainContextProps &
aiAgentContext: AIAgentContextType
tempAppWindowConfig: TempAppWindowConfig
setTempAppWindowConfig: Dispatch>
+ AIRecommendations?: FunctionComponent
+ featureAskDevtronExpert: EnvironmentDataValuesDTO['featureAskDevtronExpert']
+ AskDevtronButton?: FunctionComponent
+ showUpgradeToOSSPlusDialog: boolean
+ setShowUpgradeToOSSPlusDialog: (show: boolean) => void
}
| {
isLicenseDashboard: true
@@ -170,6 +237,11 @@ export type MainContext = CommonMainContextProps &
aiAgentContext: null
tempAppWindowConfig: null
setTempAppWindowConfig: null
+ AIRecommendations?: null
+ featureAskDevtronExpert?: null
+ AskDevtronButton?: null
+ showUpgradeToOSSPlusDialog?: null
+ setShowUpgradeToOSSPlusDialog?: null
}
)
diff --git a/src/Shared/Services/ToastManager/constants.tsx b/src/Shared/Services/ToastManager/constants.tsx
index 0310c041e..932d1e75f 100644
--- a/src/Shared/Services/ToastManager/constants.tsx
+++ b/src/Shared/Services/ToastManager/constants.tsx
@@ -17,12 +17,12 @@
// eslint-disable-next-line no-restricted-imports
import { ToastContainerProps, ToastOptions } from 'react-toastify'
-import { ReactComponent as ICCross } from '@Icons/ic-cross.svg'
-import { ReactComponent as ICError } from '@Icons/ic-error.svg'
-import { ReactComponent as ICInfoFilled } from '@Icons/ic-info-filled.svg'
-import { ReactComponent as ICLocked } from '@Icons/ic-locked.svg'
-import { ReactComponent as ICSuccess } from '@Icons/ic-success.svg'
-import { ReactComponent as ICWarning } from '@Icons/ic-warning.svg'
+import ICCross from '@Icons/ic-cross.svg?react'
+import ICError from '@Icons/ic-error.svg?react'
+import ICInfoFilled from '@Icons/ic-info-filled.svg?react'
+import ICLocked from '@Icons/ic-locked.svg?react'
+import ICSuccess from '@Icons/ic-success.svg?react'
+import ICWarning from '@Icons/ic-warning.svg?react'
import { Button, ButtonStyleType, ButtonVariantType } from '@Shared/Components'
import { ALLOW_ACTION_OUTSIDE_FOCUS_TRAP, ComponentSizeType } from '@Shared/constants'
diff --git a/src/Shared/Services/ToastManager/toastManager.scss b/src/Shared/Services/ToastManager/toastManager.scss
index 8ad85df33..916390990 100644
--- a/src/Shared/Services/ToastManager/toastManager.scss
+++ b/src/Shared/Services/ToastManager/toastManager.scss
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@import 'react-toastify/dist/ReactToastify.css';
+@use 'react-toastify/dist/ReactToastify.css';
.Toastify {
&__toast {
diff --git a/src/Shared/Services/types.ts b/src/Shared/Services/types.ts
index f778daebf..7cadb0038 100644
--- a/src/Shared/Services/types.ts
+++ b/src/Shared/Services/types.ts
@@ -63,6 +63,7 @@ export interface EnvironmentDataValuesDTO extends Pick
+ metadata: Record
prompt: string
analyticsCategory: string
}
diff --git a/src/Shared/validations.tsx b/src/Shared/validations.tsx
index 1f3d936a0..ac5d9cb75 100644
--- a/src/Shared/validations.tsx
+++ b/src/Shared/validations.tsx
@@ -561,3 +561,31 @@ export const validateCronExpression = (expression: string): ValidationResponseTy
}
}
}
+
+export const validateAppName = (value: string): Required => {
+ const re = PATTERNS.APP_NAME
+ const regExp = new RegExp(re)
+ const test = regExp.test(value)
+
+ if (value.length === 0) {
+ return { isValid: false, message: 'Please provide app name' }
+ }
+
+ if (value.length < 3) {
+ return { isValid: false, message: MESSAGES.getMinCharMessage(3) }
+ }
+
+ if (value.length > 30) {
+ return { isValid: false, message: MESSAGES.getMaxCharMessage(30) }
+ }
+
+ if (!test) {
+ return {
+ isValid: false,
+ message:
+ "Min 3 chars; Start with alphabet; End with alphanumeric; Use only lowercase; Allowed:(-); Do not use 'spaces'",
+ }
+ }
+
+ return { isValid: true, message: '' }
+}
diff --git a/src/custom.d.ts b/src/custom.d.ts
index c1ad32e69..6cf437ed5 100644
--- a/src/custom.d.ts
+++ b/src/custom.d.ts
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-declare module '*.svg' {
+declare module '*.svg?react' {
import * as React from 'react'
- export const ReactComponent: React.FunctionComponent>
-
- const src: string
- export default src
+ const ReactComponent: React.FunctionComponent>
+ export default ReactComponent
}
declare module '*.png' {
diff --git a/src/Shared/Components/Confetti/Confetti.component.tsx b/src/globals.d.ts
similarity index 55%
rename from src/Shared/Components/Confetti/Confetti.component.tsx
rename to src/globals.d.ts
index 6a68f734e..2a9d72eab 100644
--- a/src/Shared/Components/Confetti/Confetti.component.tsx
+++ b/src/globals.d.ts
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-import { ComponentProps } from 'react'
-import Pride from 'react-canvas-confetti/dist/presets/pride'
-
-const Confetti = () => {
- const decorateOptions: ComponentProps['decorateOptions'] = (options) => ({
- ...options,
- colors: ['#a864fd', '#29cdff', '#78ff44', '#ff718d', '#fdff6a'],
- })
+/**
+ * @types/react@^19 removed the global JSX namespace.
+ * Third-party libraries (e.g., react-select@5) still reference JSX.LibraryManagedAttributes
+ * in their type definitions to handle defaultProps on class components.
+ * Without this shim, those types resolve to `any`, breaking SelectPicker type safety.
+ */
+import type React from 'react'
- return
+declare global {
+ namespace JSX {
+ type LibraryManagedAttributes = React.JSX.LibraryManagedAttributes
+ }
}
-
-export default Confetti
diff --git a/src/index.ts b/src/index.ts
index 71217b53c..f71c936f5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -27,19 +27,21 @@ declare global {
}
declare module '@tanstack/react-query' {
- export interface QueryMeta {
- /**
- * Optional flag indicating whether to display a toast notification for errors.
- * @default true
- */
- showToastError?: boolean
- }
- export interface MutationMeta {
- /**
- * Optional flag indicating whether to display a toast notification for errors.
- * @default true
- */
- showToastError?: boolean
+ export interface Register {
+ queryMeta: {
+ /**
+ * Optional flag indicating whether to display a toast notification for errors.
+ * @default true
+ */
+ showToastError?: boolean
+ }
+ mutationMeta: {
+ /**
+ * Optional flag indicating whether to display a toast notification for errors.
+ * @default true
+ */
+ showToastError?: boolean
+ }
}
}
diff --git a/tsconfig.node.json b/tsconfig.node.json
index 26063d857..bcac3f473 100644
--- a/tsconfig.node.json
+++ b/tsconfig.node.json
@@ -4,7 +4,9 @@
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
- "allowSyntheticDefaultImports": true
+ "allowSyntheticDefaultImports": true,
+ "lib": ["ES2021", "DOM"],
+ "types": ["vite/client", "node"]
},
"include": ["vite.config.ts"]
}
diff --git a/vite.config.ts b/vite.config.ts
index 5c782fa6b..e12eda6f9 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -15,24 +15,23 @@
*/
import { defineConfig } from 'vite'
-import { resolve } from 'path'
+import { resolve } from 'node:path'
import react from '@vitejs/plugin-react'
import dts from 'vite-plugin-dts'
import { libInjectCss } from 'vite-plugin-lib-inject-css'
import libAssetsPlugin from '@laynezh/vite-plugin-lib-assets'
import svgr from 'vite-plugin-svgr'
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill'
-import tsconfigPaths from 'vite-tsconfig-paths'
-// import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
import * as packageJson from './package.json'
+import { esmExternalRequirePlugin } from 'rolldown/plugins'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
libAssetsPlugin({
name: '[name].[contenthash:8].[ext]',
+ exclude: /\.svg(\?.*)?$/,
}),
- tsconfigPaths(),
react(),
libInjectCss(),
svgr({
@@ -51,56 +50,82 @@ export default defineConfig({
// }),
],
build: {
- target: 'ES2021',
+ target: 'es2021',
copyPublicDir: false,
lib: {
- entry: resolve(__dirname, 'lib/main.ts'),
+ entry: resolve('lib/main.ts'),
formats: ['es'],
},
- rollupOptions: {
- external: [...Object.keys(packageJson.peerDependencies)],
+ rolldownOptions: {
+ plugins: [
+ esmExternalRequirePlugin({
+ external: [...Object.keys(packageJson.peerDependencies)],
+ }),
+ ],
input: './src/index.ts',
output: {
assetFileNames: 'assets/[name][extname]',
entryFileNames: '[name].js',
- manualChunks(id: string) {
- if (id.includes('/node_modules/framer-motion')) {
- return '@framer-motion'
- }
+ codeSplitting: {
+ groups: [
+ {
+ name: (id) => {
+ if (id.includes('/node_modules/framer-motion')) {
+ return '@framer-motion'
+ }
- if (id.includes('/node_modules/moment')) {
- return '@moment'
- }
+ if (id.includes('/node_modules/moment')) {
+ return '@moment'
+ }
- if (id.includes('/node_modules/react-select')) {
- return '@react-select'
- }
+ if (id.includes('/node_modules/react-select')) {
+ return '@react-select'
+ }
- if (id.includes('/node_modules/react-virtualized-sticky-tree')) {
- return '@react-virtualized-sticky-tree'
- }
+ if (id.includes('/node_modules/react-virtualized-sticky-tree')) {
+ return '@react-virtualized-sticky-tree'
+ }
- if (id.includes('/node_modules/')) {
- return '@vendor'
- }
+ if (id.includes('/node_modules/')) {
+ return '@vendor'
+ }
- if (id.match('codemirror') || id.includes('src/Shared/Components/CodeEditor')) {
- return '@code-editor'
- }
+ if (id.match('codemirror') || id.includes('src/Shared/Components/CodeEditor')) {
+ return '@code-editor'
+ }
- if (id.includes('src/Common/RJSF')) {
- return '@common-rjsf'
- }
+ if (id.includes('src/Common/RJSF')) {
+ return '@common-rjsf'
+ }
- if (id.includes('src/Assets/Icons')) {
- return '@src-assets-icons'
- }
+ if (id.includes('src/Assets/Icons')) {
+ return '@src-assets-icons'
+ }
- if (id.includes('src/Assets/Img')) {
- return '@src-assets-images'
- }
+ if (id.includes('src/Assets/Img')) {
+ return '@src-assets-images'
+ }
+
+ return null
+ },
+ },
+ ],
},
},
},
},
+ resolve: {
+ alias: {
+ '@Icons': resolve('./src/Assets/Icon'),
+ '@IconsV2': resolve('./src/Assets/IconV2'),
+ '@Illustrations': resolve('./src/Assets/Illustration'),
+ '@Sounds': resolve('./src/Assets/Sounds'),
+ '@Images': resolve('./src/Assets/Img'),
+ '@Common': resolve('./src/Common'),
+ '@Shared': resolve('./src/Shared'),
+ '@Pages': resolve('./src/Pages'),
+ '@PagesDevtron2.0': resolve('./src/Pages-Devtron-2.0'),
+ 'codemirror-json-schema/yaml': resolve('./node_modules/codemirror-json-schema/dist/yaml'),
+ },
+ },
})