Skip to content

Commit

Permalink
feat(application-logs): pod name filter (#384)
Browse files Browse the repository at this point in the history
  • Loading branch information
RemiBonnet committed Nov 23, 2022
1 parent 60038bd commit 306b8a2
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 57 deletions.
41 changes: 34 additions & 7 deletions libs/pages/logs/application/src/lib/page-application-logs.tsx
Expand Up @@ -7,7 +7,7 @@ import { selectApplicationById, selectApplicationsEntitiesByEnvId } from '@qover
import { selectEnvironmentById } from '@qovery/domains/environment'
import { useAuth } from '@qovery/shared/auth'
import { ApplicationEntity, LoadingStatus } from '@qovery/shared/interfaces'
import { LayoutLogs, Table, TableFilterProps } from '@qovery/shared/ui'
import { Icon, IconAwesomeEnum, LayoutLogs, StatusChip, Table, TableFilterProps } from '@qovery/shared/ui'
import { useDocumentTitle } from '@qovery/shared/utils'
import { RootState } from '@qovery/store'
import Row from './ui/row/row'
Expand Down Expand Up @@ -75,12 +75,39 @@ export function PageApplicationLogs() {
title: 'Pod name',
className: 'px-4 py-2 h-full text-text-300 w-[198px]',
classNameTitle: 'text-text-300',
// filter: [
// {
// title: 'Filter by pod name',
// key: 'pod_name',
// },
// ],
menuWidth: 360,
filter: [
{
title: 'Filter by pod name',
key: 'pod_name',
itemContentCustom: (data: Log, currentFilter: string) => {
const isActive = data.pod_name === currentFilter
const currentPod = application?.running_status?.pods.filter((pod) => pod.name === data.pod_name)[0]
return (
<div
className={`group flex justify-between items-center w-[calc(100%+24px)] rounded-sm px-3 -mx-3 h-full ${
isActive ? 'bg-element-light-darker-600' : ''
}`}
>
<StatusChip status={currentPod?.state} className="mr-2.5" />
<p className="text-xs font-medium text-text-200 mr-5">{data.pod_name}</p>
<span className="block text-xxs text-text-400 mr-2">
<Icon name={IconAwesomeEnum.CODE_COMMIT} className="mr-2 text-text-100" />
{data.version?.substring(0, 6)}
</span>
{
<Icon
name={IconAwesomeEnum.FILTER}
className={`text-ssm group-hover:text-text-100 ${
isActive ? 'text-warning-500' : 'text-transparent'
}`}
/>
}
</div>
)
},
},
],
},
{
title: 'Version',
Expand Down
Expand Up @@ -89,5 +89,6 @@ export enum IconAwesomeEnum {
SQUARE_PLUS = 'icon-solid-square-plus',
SQUARE_MINUS = 'icon-solid-square-minus',
PAUSE = 'icon-solid-pause',
FILTER = 'icon-solid-filter',
// schema: favicons name
}
Expand Up @@ -200,7 +200,7 @@ export function LayoutLogs(props: LayoutLogsProps) {
lineNumbers
? 'before:bg-element-light-darker-300 before:absolute before:left-0 before:top-9 before:w-10 before:h-full'
: ''
} ${withLogsNavigation ? 'mt-[72px]' : 'mt-[36px]'}`}
} ${withLogsNavigation ? 'mt-[76px]' : 'mt-[36px]'}`}
>
<div className="relative z-10">{children}</div>
</div>
Expand Down
Expand Up @@ -18,6 +18,7 @@ export interface MenuItemProps {
isActive?: boolean
truncateLimit?: number
disabled?: boolean
itemContentCustom?: React.ReactNode
}

export function MenuItem(props: MenuItemProps) {
Expand All @@ -35,12 +36,15 @@ export function MenuItem(props: MenuItemProps) {
containerClassName = '',
truncateLimit = 34,
disabled = false,
itemContentCustom,
} = props
const navigate = useNavigate()

const navigate = useNavigate()
const disabledClassName = disabled ? 'opacity-50 cursor-not-allowed' : ''

const itemContent = (
const itemContent = itemContentCustom ? (
itemContentCustom
) : (
<>
<div className={`flex items-center truncate ${className}`}>
{copy && (
Expand Down
23 changes: 21 additions & 2 deletions libs/shared/ui/src/lib/components/menu/menu.spec.tsx
@@ -1,8 +1,8 @@
import { render } from '__tests__/utils/setup-jest'
import { screen } from '@testing-library/react'
import { Menu, MenuDirection, MenuProps } from './menu'
import { render } from '__tests__/utils/setup-jest'
import Button from '../buttons/button/button'
import Icon from '../icon/icon'
import { Menu, MenuDirection, MenuProps } from './menu'

let props: MenuProps

Expand Down Expand Up @@ -144,4 +144,23 @@ describe('Menu', () => {

expect(search).toBeTruthy()
})

it('should have a custom content on menu', () => {
props.menus = [
{
items: [
{
name: 'Test 1',
itemContentCustom: <p>hello</p>,
},
],
search: true,
},
]

render(<Menu {...props} />)

const menu = screen.getByTestId('menuItem')
expect(menu.textContent).toBe('hello')
})
})
Expand Up @@ -6,32 +6,72 @@ describe('TableHeadFilter', () => {
title: 'Environment',
currentFilter: 'ALL',
setCurrentFilter: jest.fn(),
dataHead: [
{
title: 'Environment',
className: 'px-4 py-2',
},
],
dataHead: {
title: 'Environment',
className: 'px-4 py-2',
},
defaultData: [],
setFilterData: jest.fn(),
setFilter: jest.fn(),
}

it('should render successfully', () => {
const { baseElement } = render(<TableHeadFilter {...props} />)
expect(baseElement).toBeTruthy()
})

it('should have function render correct list of menus', () => {
props.dataHead = [
props.dataHead = {
title: 'Title',
filter: [
{
key: 'mode',
},
],
}

props.defaultData = [
{
mode: 'DEVELOPMENT',
},
{
mode: 'PRODUCTION',
},
{
title: 'Title',
filter: [
{
key: 'mode',
},
],
mode: 'STAGING',
},
{
mode: 'STAGING',
},
]

const defaultValue = 'ALL'

const testCreateFilter = createFilter(
props.dataHead,
props.defaultData,
defaultValue,
'',
jest.fn(),
jest.fn(),
jest.fn(),
jest.fn()
)

const items = testCreateFilter[0].items.map((item) => item['name'])
expect(items.toString()).toContain(['All', 'Development', 'Production', 'Staging'].toString())
})

it('should have function render correct list of menus with custom item', () => {
props.dataHead = {
title: 'Title',
filter: [
{
key: 'mode',
itemContentCustom: (data) => <p>{data.mode}</p>,
},
],
}

props.defaultData = [
{
mode: 'DEVELOPMENT',
Expand Down Expand Up @@ -60,7 +100,8 @@ describe('TableHeadFilter', () => {
jest.fn()
)

const items = testCreateFilter[0].items.map((item) => item['name'])
expect(items.toString()).toContain(['All', 'Development', 'Production', 'Staging'].toString())
testCreateFilter[0].items.map((item) => {
expect(item.itemContentCustom?.props?.children).toBe(item.name.toUpperCase())
})
})
})
Expand Up @@ -2,13 +2,14 @@ import { Dispatch, MouseEvent, SetStateAction, useState } from 'react'
import { upperCaseFirstLetter } from '@qovery/shared/utils'
import Button, { ButtonSize, ButtonStyle } from '../../buttons/button/button'
import Icon from '../../icon/icon'
import { IconAwesomeEnum } from '../../icon/icon-awesome.enum'
import Menu from '../../menu/menu'
import { MenuItemProps } from '../../menu/menu-item/menu-item'
import { TableFilterProps, TableHeadProps } from '../table'
import { TableFilterProps, TableHeadCustomFilterProps, TableHeadProps } from '../table'

export interface TableHeadFilterProps {
title: string
dataHead: TableHeadProps[]
dataHead: TableHeadProps
currentFilter: string
setCurrentFilter: Dispatch<SetStateAction<string>>
defaultData: any[]
Expand All @@ -18,7 +19,7 @@ export interface TableHeadFilterProps {
// create multiple filter
// need to output the function for testing
export function createFilter(
dataHead: TableHeadProps[],
dataHead: TableHeadProps,
defaultData: any[] | undefined,
defaultValue = 'ALL',
currentFilter: string,
Expand All @@ -30,10 +31,7 @@ export function createFilter(
const keys: string[] = []
const menus = []
// get array of keys
for (let i = 0; i < dataHead.length; i++) {
const data = dataHead[i]
data.filter && data.filter.filter((currentData) => keys.push(currentData.key))
}
dataHead.filter && dataHead.filter.filter((currentData) => keys.push(currentData.key))

// get menu by group of key
for (let i = 0; i < keys.length; i++) {
Expand All @@ -48,13 +46,14 @@ export function createFilter(
setCurrentFilter,
setLocalFilter,
setDataFilterNumber,
setFilter
setFilter,
dataHead.filter && dataHead.filter[i]
)

if (menu)
menus.push({
title: (dataHead[0].filter && dataHead[0].filter[i].title) || undefined,
search: (dataHead[0].filter && dataHead[0].filter[i].search) || false,
title: (dataHead.filter && dataHead.filter[i].title) || undefined,
search: (dataHead.filter && dataHead.filter[i].search) || false,
items: menu,
})
}
Expand All @@ -71,7 +70,8 @@ export function groupBy(
setCurrentFilter: Function,
setLocalFilter: Function,
setDataFilterNumber: Function,
setFilter: Function
setFilter: Function,
dataHeadFilter?: TableHeadCustomFilterProps
) {
const dataByKeys = data.reduce((acc, obj) => {
// create global key for all objects
Expand Down Expand Up @@ -103,13 +103,17 @@ export function groupBy(
return acc
}, {})

if (dataHeadFilter?.itemContentCustom) {
delete dataByKeys[defaultValue]
}

// create menus by keys
const result: MenuItemProps[] = Object.keys(dataByKeys).map((key: string) => ({
name: upperCaseFirstLetter(key.toLowerCase())?.replace('_', ' ') || '',
truncateLimit: 20,
contentLeft: (
<Icon
name="icon-solid-check"
name={IconAwesomeEnum.CHECK}
className={`text-sm ${currentFilter === key ? 'text-success-400' : 'text-transparent'}`}
/>
),
Expand All @@ -118,6 +122,9 @@ export function groupBy(
{dataByKeys[key].length}
</span>
),
// set custom content hide name, contentLeft and contentRight (keep only the onClick)
itemContentCustom:
dataHeadFilter?.itemContentCustom && dataHeadFilter?.itemContentCustom(dataByKeys[key][0], currentFilter),
onClick: () => {
const currentFilterData = [...dataByKeys[key]]

Expand Down Expand Up @@ -173,29 +180,29 @@ export function TableHeadFilter(props: TableHeadFilterProps) {
)

return (
<div className="flex">
<div className="flex items-center">
<Menu
open={isOpen}
onOpen={setOpen}
menus={menus}
width={280}
width={dataHead.menuWidth || 280}
isFilter
trigger={
<div>
<div className="flex">
{localFilter === currentFilter && localFilter !== ALL ? (
<Button
className="whitespace-nowrap inline-block btn--active"
className="whitespace-nowrap flex btn--active !h-6"
size={ButtonSize.TINY}
style={ButtonStyle.TAB}
>
{title} ({dataFilterNumber})
</Button>
) : (
<Button
className="inline-block"
className="flex !h-6"
size={ButtonSize.TINY}
style={ButtonStyle.TAB}
iconRight="icon-solid-angle-down"
iconRight={IconAwesomeEnum.ANGLE_DOWN}
>
{title}
</Button>
Expand All @@ -204,9 +211,9 @@ export function TableHeadFilter(props: TableHeadFilterProps) {
}
/>
{localFilter === currentFilter && localFilter !== ALL && (
<div className="btn btn--tiny btn--tab btn--active relative left-[-9px]">
<div className="btn btn--tiny btn--tab btn--active !h-6 relative left-[-9px]">
<span onClick={(event) => cleanFilter(event)}>
<Icon name="icon-solid-circle-xmark" />
<Icon name={IconAwesomeEnum.CIRCLE_XMARK} />
</span>
</div>
)}
Expand Down
11 changes: 11 additions & 0 deletions libs/shared/ui/src/lib/components/table/table.stories.tsx
Expand Up @@ -3,6 +3,8 @@ import { useState } from 'react'
import { environmentFactoryMock } from '@qovery/domains/environment'
import { EnvironmentEntity } from '@qovery/shared/interfaces'
import Button from '../buttons/button/button'
import Icon from '../icon/icon'
import { IconAwesomeEnum } from '../icon/icon-awesome.enum'
import { Table, TableFilterProps, TableProps } from './table'
import { TableRow } from './table-row/table-row'

Expand Down Expand Up @@ -73,6 +75,15 @@ const dataHead = [
search: true,
title: 'Filter by environment type',
key: 'mode',
itemContentCustom: (data: any, currentFilter: string) => {
const isActive = currentFilter === data.mode
return (
<p>
{isActive ? <Icon name={IconAwesomeEnum.CHECK} /> : ''}
{data.status.state} {data.mode}
</p>
)
},
},
],
},
Expand Down

0 comments on commit 306b8a2

Please sign in to comment.