-
Notifications
You must be signed in to change notification settings - Fork 0
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
AfO Register #398
AfO Register #398
Changes from 18 commits
6b090e8
bc7e1fe
fd947c1
2749a66
ced19e9
5d3aacd
526e661
029b34d
104f699
81b8fce
162e4fd
89b0fdf
b556376
2810b19
bf4a4b2
71a947a
669b57a
5d30646
48f9a91
3a26c4b
75d2217
b16cc16
6b06553
c5832c5
8f01369
fa10ed7
0d95895
599ad57
7a929a3
93adcf7
dcee39c
43c19d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { testDelegation, TestData } from 'test-support/utils' | ||
import AfoRegisterRepository from 'afo-register/infrastructure/AfoRegisterRepository' | ||
import AfoRegisterRecord from 'afo-register/domain/Record' | ||
import { stringify } from 'query-string' | ||
import ApiClient from 'http/ApiClient' | ||
|
||
jest.mock('http/ApiClient') | ||
const apiClient = new (ApiClient as jest.Mock<jest.Mocked<ApiClient>>)() | ||
|
||
const afoRegisterRepository = new AfoRegisterRepository(apiClient) | ||
|
||
const resultStub = { | ||
afoNumber: 'AfO 1', | ||
page: '2', | ||
text: 'some text', | ||
textNumber: '5', | ||
discussedBy: '', | ||
discussedByNotes: '', | ||
linesDiscussed: '', | ||
} | ||
|
||
const query = { afoNumber: resultStub.afoNumber, page: resultStub.page } | ||
const entry = new AfoRegisterRecord(resultStub) | ||
|
||
const testData: TestData<AfoRegisterRepository>[] = [ | ||
new TestData( | ||
'search', | ||
[ | ||
stringify({ | ||
afoNumber: 'AfO 1', | ||
page: '2', | ||
}), | ||
], | ||
apiClient.fetchJson, | ||
[entry], | ||
[`/afo-register?${stringify(query)}`, false], | ||
Promise.resolve([resultStub]) | ||
), | ||
] | ||
describe('afoRegisterService', () => | ||
testDelegation(afoRegisterRepository, testData)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import Promise from 'bluebird' | ||
import AfoRegisterRecord, { | ||
AfoRegisterRecordSuggestion, | ||
} from 'afo-register/domain/Record' | ||
import AfoRegisterRepository from 'afo-register/infrastructure/AfoRegisterRepository' | ||
import FragmentService from 'fragmentarium/application/FragmentService' | ||
|
||
export interface afoRegisterSearch { | ||
search(query: string): Promise<readonly AfoRegisterRecord[]> | ||
searchSuggestions( | ||
query: string | ||
): Promise<readonly AfoRegisterRecordSuggestion[]> | ||
} | ||
|
||
export default class AfoRegisterService implements afoRegisterSearch { | ||
private readonly afoRegisterRepository: AfoRegisterRepository | ||
|
||
constructor(afoRegisterRepository: AfoRegisterRepository) { | ||
this.afoRegisterRepository = afoRegisterRepository | ||
} | ||
|
||
search( | ||
query: string, | ||
fragmentService?: FragmentService | ||
): Promise<readonly AfoRegisterRecord[]> { | ||
return this.afoRegisterRepository.search(query, fragmentService) | ||
} | ||
|
||
searchTextsAndNumbers( | ||
query: readonly string[] | ||
): Promise<readonly AfoRegisterRecord[]> { | ||
return this.afoRegisterRepository.searchTextsAndNumbers(query) | ||
} | ||
|
||
searchSuggestions( | ||
query: string | ||
): Promise<readonly AfoRegisterRecordSuggestion[]> { | ||
return this.afoRegisterRepository.searchSuggestions(query) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import AfoRegisterRecord from 'afo-register/domain/Record' | ||
|
||
describe('AfoRegisterRecord', () => { | ||
const mockRecord = { | ||
afoNumber: '123', | ||
page: '456', | ||
text: 'Sample text', | ||
textNumber: '789', | ||
linesDiscussed: 'Some lines', | ||
discussedBy: 'John Doe', | ||
discussedByNotes: 'Notes by John', | ||
fragmentNumbers: ['BM777'], | ||
} | ||
|
||
describe('constructor', () => { | ||
describe('constructor', () => { | ||
it('should initialize properties correctly', () => { | ||
const record = new AfoRegisterRecord(mockRecord) | ||
expect(record.afoNumber).toEqual('123') | ||
expect(record.page).toEqual('456') | ||
expect(record.text).toEqual('Sample text') | ||
expect(record.textNumber).toEqual('789') | ||
expect(record.linesDiscussed).toEqual('Some lines') | ||
expect(record.discussedBy).toEqual('John Doe') | ||
expect(record.discussedByNotes).toEqual('Notes by John') | ||
}) | ||
}) | ||
}) | ||
|
||
describe('Converts to Markdown string', () => { | ||
it('Returns the correct markdown string with fragments', () => { | ||
const record = new AfoRegisterRecord(mockRecord) | ||
const result = record.toMarkdownString() | ||
expect(result).toEqual( | ||
'Sample text 789 ([BM777](/fragmentarium/BM777)), Some lines: John Doe Notes by John<small class="text-black-50 ml-3">[123, 456]</small>' | ||
) | ||
}) | ||
|
||
it('Handles sup tags correctly', () => { | ||
const recordWithSup = new AfoRegisterRecord({ | ||
...mockRecord, | ||
text: 'Sample^text^', | ||
}) | ||
const result = recordWithSup.toMarkdownString() | ||
expect(result).toContain('<sup>text</sup>') | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import produce, { Draft, immerable } from 'immer' | ||
|
||
interface RecordData { | ||
readonly afoNumber: string | ||
readonly page: string | ||
readonly text: string | ||
readonly textNumber: string | ||
readonly linesDiscussed?: string | ||
readonly discussedBy?: string | ||
readonly discussedByNotes?: string | ||
readonly fragmentNumbers?: string[] | ||
} | ||
|
||
export class AfoRegisterRecordSuggestion { | ||
[immerable] = true | ||
|
||
readonly text: string | ||
readonly textNumbers: string[] | ||
|
||
constructor({ | ||
text, | ||
textNumbers, | ||
}: { | ||
readonly text: string | ||
readonly textNumbers: string[] | ||
}) { | ||
this.text = text | ||
this.textNumbers = textNumbers | ||
} | ||
} | ||
|
||
export default class AfoRegisterRecord { | ||
[immerable] = true | ||
|
||
readonly afoNumber: string | ||
readonly page: string | ||
readonly text: string | ||
readonly textNumber: string | ||
readonly linesDiscussed?: string | ||
readonly discussedBy?: string | ||
readonly discussedByNotes?: string | ||
readonly fragmentNumbers?: string[] | ||
|
||
constructor({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar blocks of code found in 3 locations. Consider refactoring. |
||
afoNumber, | ||
page, | ||
text, | ||
textNumber, | ||
linesDiscussed, | ||
discussedBy, | ||
discussedByNotes, | ||
fragmentNumbers, | ||
}: RecordData) { | ||
this.afoNumber = afoNumber | ||
this.page = page | ||
this.text = text | ||
this.textNumber = textNumber | ||
this.linesDiscussed = linesDiscussed | ||
this.discussedBy = discussedBy | ||
this.discussedByNotes = discussedByNotes | ||
this.fragmentNumbers = fragmentNumbers | ||
} | ||
|
||
toMarkdownString(): string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Function |
||
let result = this.text + (this.textNumber ? ' ' + this.textNumber : '') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I recently made a little helper function |
||
if (this.fragmentNumbers && this.fragmentNumbers.length > 0) { | ||
result += ` (${this.fragmentsToMarkdownString()})` | ||
} | ||
if (this.linesDiscussed) result += ', ' + this.linesDiscussed | ||
if (this.discussedBy) result += ': ' + this.discussedBy | ||
if (this.discussedByNotes) result += ' ' + this.discussedByNotes | ||
result += `<small class="text-black-50 ml-3">${`[${this.afoNumber}, ${this.page}]`}</small>` | ||
result = result.replace(/\^([^^]+)\^/g, '<sup>$1</sup>') | ||
return result | ||
} | ||
|
||
private fragmentsToMarkdownString(): string { | ||
if (!this.fragmentNumbers) { | ||
return '' | ||
} | ||
return this.fragmentNumbers | ||
.map( | ||
(fragmentNumber) => | ||
`[${fragmentNumber}](/fragmentarium/${fragmentNumber})` | ||
) | ||
.join(', ') | ||
} | ||
|
||
setFragmentNumbers(fragmentNumbers: string[]): AfoRegisterRecord { | ||
return produce(this, (draft: Draft<AfoRegisterRecord>) => { | ||
draft.fragmentNumbers = fragmentNumbers | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import AfoRegisterRecord from 'afo-register/domain/Record' | ||
import ApiClient from 'http/ApiClient' | ||
import AfoRegisterRepository from './AfoRegisterRepository' | ||
import Promise from 'bluebird' | ||
import { testDelegation, TestData } from 'test-support/utils' | ||
|
||
jest.mock('http/ApiClient') | ||
|
||
const apiClient = new (ApiClient as jest.Mock<jest.Mocked<ApiClient>>)() | ||
const afoRegisterRepository = new AfoRegisterRepository(apiClient) | ||
const query = '{"afoNumber": "AfO 12", "page": "321"}' | ||
const resultStub = { | ||
afoNumber: 'AfO 12', | ||
page: '321', | ||
text: 'text', | ||
textNumber: 'text number', | ||
linesDiscussed: '', | ||
discussedBy: '', | ||
discussedByNotes: '', | ||
} | ||
const entry = new AfoRegisterRecord(resultStub) | ||
|
||
const testData: TestData<AfoRegisterRepository>[] = [ | ||
new TestData( | ||
'search', | ||
[query], | ||
apiClient.fetchJson, | ||
[entry], | ||
[`/afo-register?${query}`, false], | ||
Promise.resolve([resultStub]) | ||
), | ||
] | ||
|
||
describe('AfoRegisterRepository', () => | ||
testDelegation(afoRegisterRepository, testData)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import AfoRegisterRecord, { | ||
AfoRegisterRecordSuggestion, | ||
} from 'afo-register/domain/Record' | ||
import Promise from 'bluebird' | ||
import FragmentService from 'fragmentarium/application/FragmentService' | ||
import ApiClient from 'http/ApiClient' | ||
|
||
function createAfoRegisterRecord(data): AfoRegisterRecord { | ||
return new AfoRegisterRecord(data) | ||
} | ||
|
||
function createAfoRegisterRecordSuggestion(data): AfoRegisterRecordSuggestion { | ||
return new AfoRegisterRecordSuggestion(data) | ||
} | ||
|
||
function injectFragmentReferecesToRecord( | ||
record: AfoRegisterRecord, | ||
fragmentService: FragmentService | ||
): Promise<AfoRegisterRecord> { | ||
const { text, textNumber } = record | ||
return fragmentService | ||
.query({ traditionalReferences: text + ' ' + textNumber }) | ||
.then((queryResult) => { | ||
return record.setFragmentNumbers( | ||
queryResult.items.map((item) => item.museumNumber) | ||
) | ||
}) | ||
} | ||
|
||
export default class AfoRegisterRepository { | ||
private readonly apiClient: ApiClient | ||
|
||
constructor(apiClient: ApiClient) { | ||
this.apiClient = apiClient | ||
} | ||
|
||
search( | ||
query: string, | ||
fragmentService?: FragmentService | ||
): Promise<AfoRegisterRecord[]> { | ||
return this.apiClient | ||
.fetchJson(`/afo-register?${query}`, false) | ||
.then((result) => result.map(createAfoRegisterRecord)) | ||
.then((records) => { | ||
if (fragmentService) { | ||
return Promise.all( | ||
records.map((record) => | ||
injectFragmentReferecesToRecord(record, fragmentService) | ||
) | ||
) | ||
} else { | ||
return records | ||
} | ||
}) | ||
} | ||
|
||
searchTextsAndNumbers( | ||
query: readonly string[] | ||
): Promise<AfoRegisterRecord[]> { | ||
return this.apiClient | ||
.postJson(`/afo-register/texts-numbers`, query, false) | ||
.then((result) => result.map(createAfoRegisterRecord)) | ||
} | ||
|
||
searchSuggestions(query: string): Promise<AfoRegisterRecordSuggestion[]> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar blocks of code found in 2 locations. Consider refactoring. |
||
return this.apiClient | ||
.fetchJson(`/afo-register/suggestions?text_query=${query}`, false) | ||
.then((result) => result.map(createAfoRegisterRecordSuggestion)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React from 'react' | ||
import AfoRegisterRecord from 'afo-register/domain/Record' | ||
import MarkdownAndHtmlToHtml from 'common/MarkdownAndHtmlToHtml' | ||
|
||
export function AfoRegisterRecordDisplay({ | ||
record, | ||
index, | ||
}: { | ||
record: AfoRegisterRecord | ||
index: string | number | ||
}): JSX.Element { | ||
return ( | ||
<MarkdownAndHtmlToHtml | ||
key={`md-${index}`} | ||
markdownAndHtml={record.toMarkdownString()} | ||
/> | ||
) | ||
} | ||
|
||
export function AfoRegisterRecordsListDisplay({ | ||
records, | ||
...props | ||
}: { | ||
records: readonly AfoRegisterRecord[] | ||
} & React.OlHTMLAttributes<HTMLOListElement>): JSX.Element { | ||
if (records.length < 1) { | ||
return <p>No records found</p> | ||
} | ||
return ( | ||
<ol {...props}> | ||
{records.map((record, index) => ( | ||
<li key={`li-${index}`} className="afoRegisterRecordsList__record"> | ||
<AfoRegisterRecordDisplay record={record} index={index} /> | ||
</li> | ||
))} | ||
</ol> | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar blocks of code found in 2 locations. Consider refactoring.