Skip to content

Commit

Permalink
Feature scope editor (#503)
Browse files Browse the repository at this point in the history
* scope editor base version

* Display session scopes

* Added authorizedScopes field to Fragment

* scopeEditor shows authorizedScopes of the fragment

* * Added tests to ScopeEditor
*Updated button for updating fragments

* Update test for FragmentService

* Removed li elements
Modified the namings of checkboxes
  • Loading branch information
Vas9ka authored Aug 6, 2024
1 parent a45b08f commit 18b3b4d
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/auth/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export default class MemorySession implements Session {
return false
}

private hasApplicationScope(applicationScope: string): boolean {
hasApplicationScope(applicationScope: string): boolean {
const scope = applicationScopes[applicationScope]
return this.scopes.has(scope)
}
Expand Down
3 changes: 2 additions & 1 deletion src/auth/applicationScopes.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@
"readCAICFragments": "read:CAIC-fragments",
"readItalianNinevehFragments": "read:ITALIANNINEVEH-fragments",
"readSipparLibraryFragments": "read:SIPPARLIBRARY-fragments",
"readUrukLBUFragments": "read:URUKLBU-fragments"
"readUrukLBUFragments": "read:URUKLBU-fragments",
"readSipparIstanbulFragments": "read:SIPPARISTANBUL-fragments"
}
1 change: 1 addition & 0 deletions src/fragmentarium/application/FragmentService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const fragmentRepository = {
queryLatest: jest.fn(),
listAllFragments: jest.fn(),
queryByTraditionalReferences: jest.fn(),
updateScopes: jest.fn(),
}

const imageRepository = {
Expand Down
6 changes: 6 additions & 0 deletions src/fragmentarium/application/FragmentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export interface FragmentRepository {
fetchPeriods(): Bluebird<string[]>
fetchColophonNames(query: string): Bluebird<string[]>
updateGenres(number: string, genres: Genres): Bluebird<Fragment>
updateScopes(number: string, scopes: string[]): Bluebird<Fragment>
updateScript(number: string, script: Script): Bluebird<Fragment>
updateDate(number: string, date: MesopotamianDate): Bluebird<Fragment>
updateDatesInText(
Expand Down Expand Up @@ -168,6 +169,11 @@ export class FragmentService {
.updateScript(number, script)
.then((fragment: Fragment) => this.injectReferences(fragment))
}
updateScopes(number: string, scopes: string[]): Bluebird<Fragment> {
return this.fragmentRepository
.updateScopes(number, scopes)
.then((fragment: Fragment) => this.injectReferences(fragment))
}

updateDate(number: string, date: MesopotamianDate): Bluebird<Fragment> {
return this.fragmentRepository
Expand Down
10 changes: 9 additions & 1 deletion src/fragmentarium/infrastructure/FragmentRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,15 @@ class ApiFragmentRepository
})
.then(createFragment)
}

updateScopes(number: string, scopes: string[]): Promise<Fragment> {
const path = createFragmentPath(number, 'scopes')
return (
this.apiClient
// eslint-disable-next-line camelcase
.postJson(path, { authorized_scopes: scopes })
.then(createFragment)
)
}
updateScript(number: string, script: Script): Promise<Fragment> {
const path = createFragmentPath(number, 'script')
return this.apiClient
Expand Down
22 changes: 22 additions & 0 deletions src/fragmentarium/ui/fragment/CuneiformFragmentEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { FindspotService } from 'fragmentarium/application/FindspotService'
import { Session } from 'auth/Session'
import ColophonEditor from 'fragmentarium/ui/fragment/ColophonEditor'
import { Colophon } from 'fragmentarium/domain/Colophon'
import ScopeEditor from './ScopeEditor'

const ContentSection: FunctionComponent = ({
children,
Expand Down Expand Up @@ -47,6 +48,7 @@ type TabName =
| 'references'
| 'archaeology'
| 'colophon'
| 'permissions'

const tabNames: TabName[] = [
'display',
Expand All @@ -55,6 +57,7 @@ const tabNames: TabName[] = [
'references',
'archaeology',
'colophon',
'permissions',
]

function EditorTab({
Expand All @@ -81,9 +84,11 @@ function EditorTab({
function TabContentsMatcher({
name,
props,
session,
}: {
name: TabName
props: TabsProps
session: Session
}): JSX.Element {
return {
display: () => DisplayContents(props),
Expand All @@ -92,6 +97,7 @@ function TabContentsMatcher({
references: () => ReferencesContents(props),
archaeology: () => ArchaeologyContents(props),
colophon: () => ColophonContents(props),
permissions: () => ScopeContents(props, session),
}[name]()
}

Expand All @@ -113,6 +119,7 @@ function isTabDisabled({
references: props.disabled,
archeology: props.disabled,
colophon: props.disabled,
scope: props.disabled,
}[name]
}

Expand All @@ -138,6 +145,7 @@ export const EditorTabs: FunctionComponent<TabsProps> = ({
const children = TabContentsMatcher({
name,
props: { disabled, ...props },
session,
})
return EditorTab({
children,
Expand Down Expand Up @@ -235,3 +243,17 @@ function ColophonContents(props: TabsProps): JSX.Element {

return <ColophonEditor updateColophon={updateColophon} {...props} />
}

function ScopeContents(props: TabsProps, session: Session): JSX.Element {
const updateScopes = async (scopes: string[]) => {
props.onSave(
props.fragmentService.updateScopes(props.fragment.number, scopes)
)
}

return (
<ScopeEditor session={session} updateScopes={updateScopes} {...props} />
)
}

export default ScopeContents
73 changes: 73 additions & 0 deletions src/fragmentarium/ui/fragment/ScopeEditor.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import ScopeEditor from './ScopeEditor'
import { Fragment } from 'fragmentarium/domain/fragment'
import MemorySession from 'auth/Session'

describe('ScopeEditor', () => {
const fragment: Fragment = {
authorizedScopes: ['read:CAIC-fragments'],
} as Fragment

const session = new MemorySession([
'read:CAIC-fragments',
'read:ITALIANNINEVEH-fragments',
])

const updateScopes = jest.fn().mockResolvedValue(undefined)

test('renders the component correctly', () => {
render(
<ScopeEditor
fragment={fragment}
session={session}
updateScopes={updateScopes}
/>
)

expect(screen.getByText('Permissions')).toBeInTheDocument()
expect(
screen.getByText(
'Records with added permissions are visible only to users who have those permissions.'
)
).toBeInTheDocument()
})

test('toggles scopes', () => {
render(
<ScopeEditor
fragment={fragment}
session={session}
updateScopes={updateScopes}
/>
)

const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes).toHaveLength(2)

expect(checkboxes[0]).toBeChecked()

fireEvent.click(checkboxes[0])
expect(checkboxes[0]).not.toBeChecked()

fireEvent.click(checkboxes[0])
expect(checkboxes[0]).toBeChecked()
})

test('submits selected scopes', async () => {
render(
<ScopeEditor
fragment={fragment}
session={session}
updateScopes={updateScopes}
/>
)

const button = screen.getByText('Update Permissions')

fireEvent.click(button)

expect(updateScopes).toHaveBeenCalledWith(['read:CAIC-fragments'])
})
})
80 changes: 80 additions & 0 deletions src/fragmentarium/ui/fragment/ScopeEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, { useState, useEffect } from 'react'
import { Fragment } from 'fragmentarium/domain/fragment'
import MemorySession, { Session } from 'auth/Session'
import { Button } from 'react-bootstrap'
interface ScopeEditorProps {
fragment: Fragment
session: Session
updateScopes: (scopes: string[]) => Promise<void>
}

function isMemorySession(session: Session): session is MemorySession {
return !session.isGuestSession()
}

const ScopeEditor: React.FC<ScopeEditorProps> = ({
fragment,
session,
updateScopes,
}) => {
const SCOPES = [
'CAIC',
'ItalianNineveh',
'SipparLibrary',
'UrukLBU',
'SipparIstanbul',
]
const [selectedScopes, setSelectedScopes] = useState<string[]>(
fragment.authorizedScopes || []
)
const [fragmentScopes, setFragmentScopes] = useState<string[]>([])
useEffect(() => {
if (isMemorySession(session)) {
const scopes = SCOPES.filter((scope) =>
session.hasApplicationScope('read' + scope + 'Fragments')
)
const upperCaseScopes = scopes.map((scope) => scope.toUpperCase())
const reformattedScopes = upperCaseScopes.map(
(scope) => 'read:' + scope + '-fragments'
)
setFragmentScopes(reformattedScopes)
}
}, [session])
const handleScopeChange = (scope: string) => {
setSelectedScopes((prevScopes) =>
prevScopes.includes(scope)
? prevScopes.filter((s) => s !== scope)
: [...prevScopes, scope]
)
}

const handleSubmit = async () => {
await updateScopes(selectedScopes)
}
const getDisplayScope = (scope: string) => {
return scope.replace('read:', '').replace('-fragments', '')
}
return (
<div>
<h3>Permissions</h3>
<p>
Records with added permissions are visible only to users who have those
permissions.
</p>
{fragmentScopes.map((scope) => (
<div key={scope}>
<label>
<input
type="checkbox"
checked={selectedScopes.includes(scope)}
onChange={() => handleScopeChange(scope)}
/>
Restrict it to users with {getDisplayScope(scope)} permissions
</label>
</div>
))}
<Button onClick={handleSubmit}>Update Permissions</Button>
</div>
)
}
export default ScopeEditor

0 comments on commit 18b3b4d

Please sign in to comment.