Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Costum columns in Explorer #1499

Merged
merged 31 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a5773e1
init
hassanad94 May 4, 2023
1107377
open a dialog
hassanad94 May 4, 2023
cb7c641
Monaco editor
hassanad94 May 4, 2023
5718764
before api
hassanad94 May 11, 2023
2e63d72
with cache
hassanad94 May 12, 2023
65ea091
with cache and references
hassanad94 May 16, 2023
f365d2d
Merge branch 'develop' of https://github.com/SenseNet/sn-client into …
hassanad94 May 16, 2023
d1de707
type fixes
hassanad94 May 16, 2023
4dd6ad3
type checkbox as extends
hassanad94 May 16, 2023
1346547
sn-app type errors
hassanad94 May 16, 2023
27c151a
example fix
hassanad94 May 16, 2023
ef6f4c1
example changes
hassanad94 May 16, 2023
a01cad1
virtualized table test update
hassanad94 May 16, 2023
8c06208
prototype with working api
hassanad94 May 17, 2023
04558f3
remove unused var
hassanad94 May 17, 2023
db1b025
story book
hassanad94 May 17, 2023
d939330
test fix
hassanad94 May 18, 2023
85c3834
more test to fix
hassanad94 May 18, 2023
4c14ece
Adding Actions to Everything
hassanad94 May 18, 2023
232d451
e2e tests
hassanad94 May 22, 2023
29d699d
remove unsued var
hassanad94 May 22, 2023
efe5b06
Merge branch 'develop' into 1492/feature/Custom-columns-in-explorer
hassanad94 May 22, 2023
ab94b1c
bug fix
hassanad94 May 23, 2023
9160d35
removce console log
hassanad94 May 23, 2023
f944328
json fixes
hassanad94 May 24, 2023
93bc157
readable json
hassanad94 May 24, 2023
89fe2ae
error fix
hassanad94 May 25, 2023
1d05695
webhooks
hassanad94 May 26, 2023
302fb55
disable column Settings Prototype
hassanad94 May 26, 2023
bc75259
disable columnsetting at localization
hassanad94 May 26, 2023
3975e7f
change button position
hassanad94 Jun 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions apps/sensenet/cypress/e2e/content/explorer.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { pathWithQueryParams } from '../../../src/services/query-string-builder'

const newColumnSettings = {
settings: [
{ field: 'DisplayName', title: 'Test Display' },
{ field: 'AvailableContentTypeFields', title: 'Test' },
],
}

const originalColumnSettings = {
settings: [
{ field: 'DisplayName', title: 'Display Name' },
{ field: 'AvailableContentTypeFields', title: 'Available Content Type Fields' },
],
}

describe('Add new permission entry', () => {
before(() => {
cy.login('superAdmin')
cy.visit(pathWithQueryParams({ path: '/', newParams: { repoUrl: Cypress.env('repoUrl') } }))
cy.viewport(1340, 890)
})

it('It should open Content Explorer and change the Columns', () => {
cy.get('[data-test="drawer-menu-item-content"]').click()
cy.get('[data-test="column-settings"]').click()

cy.get('.react-monaco-editor-container textarea')
.type('{ctrl}a', { force: true })
.clear({ force: true })
.type(JSON.stringify(newColumnSettings), {
parseSpecialCharSequences: false,
})

cy.get('[data-test="monaco-editor-submit"]').click()

cy.get('[data-test="table-header-actions"]').should('be.visible').find('.MuiButtonBase-root').contains('Action')
cy.get('[data-test="table-header-availablecontenttypefields"]')
.should('be.visible')
.find('.MuiButtonBase-root')
.contains('Test')
cy.get('[data-test="table-header-displayname"]')
.should('be.visible')
.find('.MuiButtonBase-root')
.contains('Test Display')

cy.get('[data-test="column-settings"]').click()

cy.get('.react-monaco-editor-container textarea')
.type('{ctrl}a', { force: true })
.clear({ force: true })
.type(JSON.stringify(originalColumnSettings), {
parseSpecialCharSequences: false,
})
cy.get('[data-test="monaco-editor-submit"]').click()
})
})
10 changes: 5 additions & 5 deletions apps/sensenet/src/components/MainRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ export const MainRouter = () => {
<ContentComponent
rootPath={PATHS.contentTypes.snPath}
fieldsToDisplay={[
'DisplayName',
'Description',
'ParentTypeName' as any,
'ModificationDate',
'ModifiedBy',
{ field: 'DisplayName' },
{ field: 'Description' },
{ field: 'ParentTypeName' as any },
{ field: 'ModificationDate' },
{ field: 'ModifiedBy' },
]}
loadChildrenSettings={{
select: ['DisplayName', 'Description', 'ParentTypeName' as any, 'ModificationDate', 'ModifiedBy'],
Expand Down
163 changes: 147 additions & 16 deletions apps/sensenet/src/components/content-list/content-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useRepository,
} from '@sensenet/hooks-react'
import { VirtualCellProps, VirtualDefaultCell, VirtualizedTable } from '@sensenet/list-controls-react'
import { ColumnSetting } from '@sensenet/list-controls-react/src/ContentList/content-list-base-props'
import { clsx } from 'clsx'
import React, {
CSSProperties,
Expand Down Expand Up @@ -65,7 +66,6 @@ const useStyles = makeStyles(() => {
},
})
})

export interface ContentListProps<T extends GenericContent> {
enableBreadcrumbs?: boolean
hideHeader?: boolean
Expand All @@ -77,11 +77,12 @@ export interface ContentListProps<T extends GenericContent> {
onActivateItem: (item: T) => void
style?: CSSProperties
containerRef?: (r: HTMLDivElement | null) => void
fieldsToDisplay?: Array<Extract<keyof T, string>>
fieldsToDisplay?: Array<ColumnSetting<GenericContent>>
schema?: string
onSelectionChange?: (sel: T[]) => void
onFocus?: () => void
containerProps?: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
disableColumnSettings?: boolean
}

export const isReferenceField = (fieldName: string, repo: Repository, schema = 'GenericContent') => {
Expand All @@ -93,6 +94,29 @@ export const isReferenceField = (fieldName: string, repo: Repository, schema = '
const rowHeightConst = 57
const headerHeightConst = 48

/**
* Compare passed minutes with
* @param value The base value.
* @param timeDifference The minimum elapsed time time in minutes in Miliseconds.( Dafault 5 minutes )
* @returns Returns true if the base value is greater than the compared value.
*/

// a function which expect a Date and it will compare with the current time and if the passed interval is greater than the current time it will return true
const isExpired = (value: Date, timeDifference = 300000) => {
const currentTime = new Date().getTime()
const valueTime = value.getTime()
return currentTime - valueTime > timeDifference
}

interface ColumnSettingsContainerType {
[key: string]: {
columns: Array<ColumnSetting<GenericContent>>
lastValidation: Date
}
}

const ColumnSettingsContainer: ColumnSettingsContainerType = {}

export const ContentList = <T extends GenericContent = GenericContent>(props: ContentListProps<T>) => {
const selectionService = useSelectionService()
const parentContent = useContext(CurrentContentContext)
Expand All @@ -104,7 +128,7 @@ export const ContentList = <T extends GenericContent = GenericContent>(props: Co
const repo = useRepository()
const classes = useStyles()
const globalClasses = useGlobalStyles()
const { openDialog } = useDialog()
const { openDialog, closeLastDialog } = useDialog()
const logger = useLogger('ContentList')
const localization = useLocalization()
const [selected, setSelected] = useState<T[]>([])
Expand All @@ -124,6 +148,74 @@ export const ContentList = <T extends GenericContent = GenericContent>(props: Co
(loadChildrenSettingsOrderBy?.[0][1] as 'asc' | 'desc') || 'asc',
)

const [columnSettings, setColumnSettings] = useState<Array<ColumnSetting<GenericContent>>>(
personalSettings.content.fields,
)

const fetchUrl = PathHelper.joinPaths(
repo.configuration.repositoryUrl,
repo.configuration.oDataToken,
PathHelper.getContentUrl(props.parentIdOrPath),
)

/* Handle Column Settings */
useEffect(() => {
const ac = new AbortController()

const getColumnSettings = async () => {
const currentPathSettingCache = ColumnSettingsContainer[props.parentIdOrPath]
if (
!currentPathSettingCache ||
!currentPathSettingCache?.columns?.length ||
isExpired(new Date(currentPathSettingCache.lastValidation))
) {
const endpoint = 'GetSettings'
const queryParameters = { name: 'ColumnSettings' }
const search = new URLSearchParams(queryParameters).toString()

const requestUrl = `${fetchUrl}/${endpoint}?${search}`

let data: { columns: Array<ColumnSetting<GenericContent>> } | undefined

try {
const response = await repo.fetch(requestUrl, {
method: 'GET',
credentials: 'include',
signal: ac.signal,
})

data = await response.json()
// Continue processing data...
} catch (error) {
/*empty*/
}

if (!data?.columns) {
return
}

ColumnSettingsContainer[props.parentIdOrPath] = { columns: data.columns, lastValidation: new Date() }
}

/* Add Actions if field Settings Does not contain it. */
if (!ColumnSettingsContainer[props.parentIdOrPath]?.columns?.find((f) => f.field === 'Actions')) {
ColumnSettingsContainer[props.parentIdOrPath].columns.push({ field: 'Actions', title: 'Actions' })
}

setColumnSettings(ColumnSettingsContainer[props.parentIdOrPath].columns)
}

if (!props.fieldsToDisplay) {
getColumnSettings()

return () => {
ac.abort()
}
}

setColumnSettings(props.fieldsToDisplay)
}, [props.fieldsToDisplay, props.parentIdOrPath, repo, fetchUrl])

useEffect(() => {
setSelected([])
}, [children])
Expand All @@ -144,37 +236,38 @@ export const ContentList = <T extends GenericContent = GenericContent>(props: Co
}, [selected])

useEffect(() => {
const fields = props.fieldsToDisplay || personalSettings.content.fields
const fields = columnSettings || personalSettings.content.fields

loadSettings.setLoadChildrenSettings({
...loadSettings.loadChildrenSettings,
expand: [
'CheckedOutTo',
...(fields as string[]).reduce<any[]>((referenceFields, fieldName) => {
if (fieldName.includes('/')) {
const splittedFieldName = fieldName.split('/')
...fields.reduce<Array<keyof GenericContent>>((referenceFields, fieldName) => {
if (fieldName.field.includes('/')) {
const splittedFieldName = fieldName.field.split('/')
if (splittedFieldName.length === 2 && splittedFieldName[1] === '') {
if (isReferenceField(splittedFieldName[0], repo, props.schema)) {
referenceFields.push(splittedFieldName[0])
referenceFields.push(splittedFieldName[0] as keyof GenericContent)
}
} else if (
repo.schemas.getFieldTypeByName(splittedFieldName[splittedFieldName.length - 1]) ===
'ReferenceFieldSetting'
) {
!referenceFields.includes(fieldName) && referenceFields.push(fieldName)
!referenceFields.includes(fieldName.field) && referenceFields.push(fieldName.field)
} else {
!referenceFields.includes(PathHelper.getParentPath(fieldName)) &&
referenceFields.push(PathHelper.getParentPath(fieldName))
!referenceFields.includes(PathHelper.getParentPath(fieldName.field) as keyof GenericContent) &&
referenceFields.push(PathHelper.getParentPath(fieldName.field) as keyof GenericContent)
}
} else if (repo.schemas.getFieldTypeByName(fieldName) === 'ReferenceFieldSetting') {
referenceFields.push(fieldName)
} else if (repo.schemas.getFieldTypeByName(fieldName.field) === 'ReferenceFieldSetting') {
referenceFields.push(fieldName.field)
}
return referenceFields
}, []),
],
orderby: [[currentOrder as any, currentDirection as any]],
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentDirection, currentOrder, personalSettings.content.fields, props.fieldsToDisplay, repo])
}, [currentDirection, currentOrder, personalSettings.content.fields, props.fieldsToDisplay, repo, columnSettings])

useEffect(() => {
setSelected([])
Expand Down Expand Up @@ -517,6 +610,42 @@ export const ContentList = <T extends GenericContent = GenericContent>(props: Co
})
}

const setCostumColumnSettings = async (newSettings: { columns: Array<ColumnSetting<GenericContent>> }) => {
ColumnSettingsContainer[props.parentIdOrPath] = { columns: newSettings.columns, lastValidation: new Date() }

const endpoint = 'WriteSettings'

const requestUrl = `${fetchUrl}/${endpoint}`

const data = {
name: 'ColumnSettings',
settingsData: newSettings,
}

try {
await repo.fetch(requestUrl, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(data),
})
} catch (error) {
console.error(error)
}
setColumnSettings(newSettings.columns)
closeLastDialog()
}

const columnSettingsDialog = () => {
openDialog({
name: 'column-settings',
props: {
columnSettings: ColumnSettingsContainer[props.parentIdOrPath]?.columns,
setColumnSettings: setCostumColumnSettings,
},
dialogProps: { maxWidth: 'sm', classes: { container: globalClasses.centeredRight } },
})
}

const menuPropsObj = {
disablePortal: true,
anchorReference: 'anchorPosition' as const,
Expand Down Expand Up @@ -552,14 +681,16 @@ export const ContentList = <T extends GenericContent = GenericContent>(props: Co
ref={props.containerRef}
onKeyDown={handleKeyDown}>
<VirtualizedTable
disableColumnSettings={props.disableColumnSettings}
handleColumnSettingsClick={columnSettingsDialog}
active={activeContent}
checkboxProps={{ color: 'primary' }}
cellRenderer={fieldComponentFunc}
referenceCellRenderer={fieldReferenceFunc}
displayRowCheckbox={!props.disableSelection}
fieldsToDisplay={
(props.fieldsToDisplay?.map((field) => {
const splittedField = field.split('/')
(columnSettings?.map((field) => {
const splittedField = field?.field?.split('/')
if (splittedField.length === 2 && splittedField[1] === '') {
return splittedField[0]
} else {
Expand Down
6 changes: 5 additions & 1 deletion apps/sensenet/src/components/content/Explore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
LoadSettingsContextProvider,
useRepository,
} from '@sensenet/hooks-react'
import { ColumnSetting } from '@sensenet/list-controls-react/src/ContentList/content-list-base-props'
import { clsx } from 'clsx'
import React, { useCallback, useContext, useState } from 'react'
import { useHistory } from 'react-router'
Expand Down Expand Up @@ -58,14 +59,15 @@ export type ExploreProps = {
currentPath: string
rootPath: string
onNavigate: (content: GenericContent) => void
fieldsToDisplay?: Array<keyof GenericContent>
fieldsToDisplay?: Array<ColumnSetting<GenericContent>>
schema?: string
loadTreeSettings?: ODataParams<GenericContent>
loadChildrenSettings?: ODataParams<GenericContent>
renderBeforeGrid?: () => JSX.Element
hasTree?: boolean
alwaysRefreshChildren?: boolean
showPageTitle?: boolean
disableColumnSettings?: boolean
}

export function Explore({
Expand All @@ -79,6 +81,7 @@ export function Explore({
renderBeforeGrid,
hasTree = true,
alwaysRefreshChildren,
disableColumnSettings,
}: ExploreProps) {
const selectionService = useSelectionService()
const classes = useStyles()
Expand Down Expand Up @@ -167,6 +170,7 @@ export function Explore({
<>
{renderBeforeGrid?.()}
<ContentList
disableColumnSettings={disableColumnSettings}
style={{ flexGrow: 7, flexShrink: 0, maxHeight: '100%' }}
enableBreadcrumbs={false}
fieldsToDisplay={fieldsToDisplay}
Expand Down
Loading