- {loading ? (
+ {loading && !visibleColumns.length ? (
- {isBulkSelectionConfigured ?
: null}
+ {showIconOrExpandActionGutter ?
: null}
{SHIMMER_DUMMY_ARRAY.map((label) => (
))}
@@ -337,6 +538,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 &&
}
+
{visibleColumns.map(
(
{
@@ -346,6 +573,7 @@ const TableContent = <
size,
showTippyOnTruncate,
horizontallySticky: isStickyColumn,
+ infoTooltipText,
},
index,
) => {
@@ -353,7 +581,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 +612,7 @@ const TableContent = <
return (
@@ -392,6 +625,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..16bfa0e00 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,14 @@
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;
+ }
}
diff --git a/src/Shared/Components/Table/types.ts b/src/Shared/Components/Table/types.ts
index 0d729b82a..a45d2b7f8 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',
@@ -87,13 +90,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 {
@@ -117,6 +130,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<
@@ -222,7 +239,11 @@ export type ViewWrapperProps<
: {})
>
-type FilterConfig = {
+type FilterConfig<
+ FilterVariant extends FiltersTypeEnum,
+ RowData extends unknown,
+ AdditionalProps extends Record,
+> = {
filtersVariant: FilterVariant
/**
* Props for useUrlFilters/useStateFilters hooks
@@ -236,12 +257,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<
@@ -307,6 +330,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
} & (
| {
/**
@@ -337,7 +368,7 @@ export type InternalTableProps<
pageSizeOptions?: never
}
) &
- FilterConfig
+ FilterConfig
export type UseResizableTableConfigWrapperProps<
RowData extends unknown,
@@ -390,6 +421,9 @@ export type TableProps<
| 'ViewWrapper'
| 'pageSizeOptions'
| 'clearFilters'
+ | 'rowStartIconConfig'
+ | 'onRowClick'
+ | 'areFiltersApplied'
>
export type BulkActionStateType = string | null
@@ -440,6 +474,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/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/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/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..cff7ce452 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, 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,9 @@ export type MainContext = CommonMainContextProps &
aiAgentContext: AIAgentContextType
tempAppWindowConfig: TempAppWindowConfig
setTempAppWindowConfig: Dispatch>
+ AIRecommendations?: FunctionComponent
+ featureAskDevtronExpert: EnvironmentDataValuesDTO['featureAskDevtronExpert']
+ AskDevtronButton?: FunctionComponent
}
| {
isLicenseDashboard: true
@@ -170,6 +235,9 @@ export type MainContext = CommonMainContextProps &
aiAgentContext: null
tempAppWindowConfig: null
setTempAppWindowConfig: null
+ AIRecommendations?: null
+ featureAskDevtronExpert?: null
+ AskDevtronButton?: null
}
)
diff --git a/src/Shared/Services/types.ts b/src/Shared/Services/types.ts
index dd0ba0e8e..7a2fdf857 100644
--- a/src/Shared/Services/types.ts
+++ b/src/Shared/Services/types.ts
@@ -61,6 +61,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/index.ts b/src/index.ts
index afb611a11..6ccacbbaa 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -155,7 +155,6 @@ export interface customEnv {
GATEKEEPER_URL?: string
FEATURE_AI_INTEGRATION_ENABLE?: boolean
LOGIN_PAGE_IMAGE?: string
- FEATURE_ASK_DEVTRON_EXPERT?: boolean
/**
* If true, the manage traffic feature is enabled in apps & app groups.
*
@@ -188,6 +187,7 @@ export interface customEnv {
* @default false
*/
FEATURE_STORAGE_ENABLE?: boolean
+ FEATURE_ATHENA_DEBUG_MODE_ENABLE?: boolean
/** Org ID for grafana */
GRAFANA_ORG_ID?: number
}