diff --git a/.rancher-pipeline.yml b/.rancher-pipeline.yml new file mode 100644 index 00000000..0bb92da6 --- /dev/null +++ b/.rancher-pipeline.yml @@ -0,0 +1,26 @@ +stages: +- name: Build + steps: + - runScriptConfig: + image: maven:3-openjdk-11 + shellScript: mvn package -DskipTests +- name: Build Docker with DIND + steps: + - publishImageConfig: + dockerfilePath: ./Dockerfile + buildContext: . + tag: docker.dev.folio.org/mod-notes:spitfire-latest + pushRemote: true + registry: docker.dev.folio.org +- name: Deploy + steps: + - applyAppConfig: + catalogTemplate: p-9tp2k:spitfire-helmcharts-mod-notes + version: 0.1.32 + answers: + image.repository: docker.dev.folio.org/mod-notes + image.tag: spitfire-latest + name: mod-notes + targetNamespace: spitfire +timeout: 60 +notification: {} diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 2e7fcd9d..12553cf8 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -14,7 +14,7 @@ "provides": [ { "id": "notes", - "version": "1.0", + "version": "2.0", "handlers": [ { "methods": ["GET"], diff --git a/ramls/link.raml b/ramls/link.raml index 039dbe6b..fe869691 100644 --- a/ramls/link.raml +++ b/ramls/link.raml @@ -59,11 +59,10 @@ traits: ] description: Return a list of notes by status queryParameters: - title: - displayName: Note title + search: + displayName: Search term type: string - description: Search string for note title. Note is returned only if it contains - specified word or sequence of words anywhere in the title. Search is case-insensitive. + description: Partial match case-insensitive search term for note title and note details. example: important required: false noteType: diff --git a/src/main/java/org/folio/links/NoteLinksConstants.java b/src/main/java/org/folio/links/NoteLinksConstants.java index ecdfdac5..479dc036 100644 --- a/src/main/java/org/folio/links/NoteLinksConstants.java +++ b/src/main/java/org/folio/links/NoteLinksConstants.java @@ -43,7 +43,7 @@ private NoteLinksConstants() { static final String ORDER_BY_TITLE_CLAUSE = "ORDER BY data.jsonb->>'title' %s "; - static final String ORDER_BY_CONTENT_CLAUSE = "ORDER BY data.jsonb->>'title' || regexp_replace(data.jsonb->>'content', E'<[^>]+>', '', 'gi') %s "; + static final String ORDER_BY_CONTENT_CLAUSE = "ORDER BY search_content %s "; static final String ORDER_BY_LINKS_NUMBER = "ORDER BY json_array_length((data.jsonb->>'links')::json) %s "; @@ -55,17 +55,17 @@ private NoteLinksConstants() { private static final String JOIN_NOTE_TYPE_TABLE = "LEFT JOIN %s as type on (data.jsonb -> 'typeId' = type.jsonb -> 'id') "; - private static final String WHERE_CLAUSE_BY_DOMAIN_AND_TITLE = - "WHERE (data.jsonb->>'domain' = ?) AND (f_unaccent(data.jsonb->>'title') ~* f_unaccent(?)) "; + private static final String WHERE_CLAUSE_BY_DOMAIN_AND_CONTENT = + "WHERE (data.jsonb->>'domain' = ?) AND search_content ~* f_unaccent(?) "; static final String WHERE_CLAUSE_BY_NOTE_TYPE = " AND (type.jsonb ->> 'name' IN (%s)) "; - static final String SELECT_NOTES_BY_DOMAIN_AND_TITLE = - "SELECT data.id, data.jsonb FROM %s as data " + JOIN_NOTE_TYPE_TABLE + WHERE_CLAUSE_BY_DOMAIN_AND_TITLE; + static final String SELECT_NOTES_BY_DOMAIN_AND_CONTENT = + "SELECT data.id, data.jsonb FROM %s as data " + JOIN_NOTE_TYPE_TABLE + WHERE_CLAUSE_BY_DOMAIN_AND_CONTENT; - static final String COUNT_NOTES_BY_DOMAIN_AND_TITLE = - "SELECT COUNT(data.id) as count FROM %s as data " + JOIN_NOTE_TYPE_TABLE + WHERE_CLAUSE_BY_DOMAIN_AND_TITLE; + static final String COUNT_NOTES_BY_DOMAIN_AND_CONTENT = + "SELECT COUNT(data.id) as count FROM %s as data " + JOIN_NOTE_TYPE_TABLE + WHERE_CLAUSE_BY_DOMAIN_AND_CONTENT; static final String ANY_STRING_PATTERN = ".*"; - static final String WORD_PATTERN = "\\m%s\\M"; + static final String WORD_PATTERN = ".*%s.*"; } diff --git a/src/main/java/org/folio/links/NoteLinksRepository.java b/src/main/java/org/folio/links/NoteLinksRepository.java index 38907b56..0d37c194 100644 --- a/src/main/java/org/folio/links/NoteLinksRepository.java +++ b/src/main/java/org/folio/links/NoteLinksRepository.java @@ -16,10 +16,10 @@ public interface NoteLinksRepository { Future update(Link link, List assignNotes, List unAssignNotes, String tenantId); - Future findNotesByTitleAndNoteTypeAndStatus(EntityLink link, String title, List noteTypes, + Future findNotesByTitleAndNoteTypeAndStatus(EntityLink link, String search, List noteTypes, Status status, OrderBy orderBy, Order order, RowPortion rowPortion, String tenantId); - Future countNotesByTitleAndNoteTypeAndStatus(EntityLink link, String title, List noteTypes, Status status, + Future countNotesByTitleAndNoteTypeAndStatus(EntityLink link, String search, List noteTypes, Status status, String tenantId); } diff --git a/src/main/java/org/folio/links/NoteLinksRepositoryImpl.java b/src/main/java/org/folio/links/NoteLinksRepositoryImpl.java index 24c3b131..888db8aa 100644 --- a/src/main/java/org/folio/links/NoteLinksRepositoryImpl.java +++ b/src/main/java/org/folio/links/NoteLinksRepositoryImpl.java @@ -3,7 +3,7 @@ import static io.vertx.core.Future.succeededFuture; import static org.folio.links.NoteLinksConstants.ANY_STRING_PATTERN; -import static org.folio.links.NoteLinksConstants.COUNT_NOTES_BY_DOMAIN_AND_TITLE; +import static org.folio.links.NoteLinksConstants.COUNT_NOTES_BY_DOMAIN_AND_CONTENT; import static org.folio.links.NoteLinksConstants.DELETE_NOTES_WITHOUT_LINKS; import static org.folio.links.NoteLinksConstants.HAS_LINK_CONDITION; import static org.folio.links.NoteLinksConstants.INSERT_LINKS; @@ -17,7 +17,7 @@ import static org.folio.links.NoteLinksConstants.ORDER_BY_TITLE_CLAUSE; import static org.folio.links.NoteLinksConstants.ORDER_BY_UPDATED_DATE; import static org.folio.links.NoteLinksConstants.REMOVE_LINKS; -import static org.folio.links.NoteLinksConstants.SELECT_NOTES_BY_DOMAIN_AND_TITLE; +import static org.folio.links.NoteLinksConstants.SELECT_NOTES_BY_DOMAIN_AND_CONTENT; import static org.folio.links.NoteLinksConstants.WHERE_CLAUSE_BY_NOTE_TYPE; import static org.folio.links.NoteLinksConstants.WORD_PATTERN; @@ -81,14 +81,14 @@ public Future update(Link link, List assignNotes, List unA } @Override - public Future findNotesByTitleAndNoteTypeAndStatus(EntityLink link, String title, List noteTypes, + public Future findNotesByTitleAndNoteTypeAndStatus(EntityLink link, String search, List noteTypes, Status status, OrderBy orderBy, Order order, RowPortion rowPortion, String tenantId) { Tuple parameters = Tuple.tuple(); StringBuilder queryBuilder = new StringBuilder(); - addSelectClause(parameters, queryBuilder, link.getDomain(), title, tenantId); + addSelectClause(parameters, queryBuilder, link.getDomain(), search, tenantId); addWhereNoteTypeClause(parameters, queryBuilder, noteTypes); @@ -107,13 +107,13 @@ public Future findNotesByTitleAndNoteTypeAndStatus(EntityLink li } @Override - public Future countNotesByTitleAndNoteTypeAndStatus(EntityLink link, String title, List noteTypes, + public Future countNotesByTitleAndNoteTypeAndStatus(EntityLink link, String search, List noteTypes, Status status, String tenantId) { Tuple parameters = Tuple.tuple(); StringBuilder queryBuilder = new StringBuilder(); - addSelectCountClause(parameters, queryBuilder, link.getDomain(), title, tenantId); + addSelectCountClause(parameters, queryBuilder, link.getDomain(), search, tenantId); addWhereNoteTypeClause(parameters, queryBuilder, noteTypes); @@ -288,19 +288,19 @@ private void addLimitOffset(Tuple parameters, StringBuilder query, RowPortion ro .addInteger(rowPortion.getOffset()); } - private void addSelectClause(Tuple parameters, StringBuilder query, String domain, String title, String tenantId) { + private void addSelectClause(Tuple parameters, StringBuilder query, String domain, String search, String tenantId) { query - .append(String.format(SELECT_NOTES_BY_DOMAIN_AND_TITLE, getNoteTableName(tenantId), getNoteTypeTableName(tenantId))); + .append(String.format(SELECT_NOTES_BY_DOMAIN_AND_CONTENT, getNoteTableName(tenantId), getNoteTypeTableName(tenantId))); parameters .addString(domain) - .addString(getTitleRegexp(title)); + .addString(getContentRegexp(search)); } - private void addSelectCountClause(Tuple parameters, StringBuilder query, String domain, String title, String tenantId) { - query.append(String.format(COUNT_NOTES_BY_DOMAIN_AND_TITLE, getNoteTableName(tenantId), getNoteTypeTableName(tenantId))); + private void addSelectCountClause(Tuple parameters, StringBuilder query, String domain, String search, String tenantId) { + query.append(String.format(COUNT_NOTES_BY_DOMAIN_AND_CONTENT, getNoteTableName(tenantId), getNoteTypeTableName(tenantId))); parameters .addString(domain) - .addString(getTitleRegexp(title)); + .addString(getContentRegexp(search)); } private void addOrderByClause(Tuple parameters, StringBuilder query, Order order, OrderBy orderBy, JsonObject jsonLink) { @@ -338,11 +338,11 @@ private void addWhereClause(Tuple parameters, StringBuilder query, Status status } } - private String getTitleRegexp(String title) { - if (StringUtils.isEmpty(title)) { + private String getContentRegexp(String str) { + if (StringUtils.isEmpty(str)) { return ANY_STRING_PATTERN; } else { - String regex = escapeRegex(title) + String regex = escapeRegex(str) .replace(ESCAPED_ANY_STRING_WILDCARD, ".*"); return String.format(WORD_PATTERN, regex); } diff --git a/src/main/java/org/folio/rest/impl/NoteLinksImpl.java b/src/main/java/org/folio/rest/impl/NoteLinksImpl.java index 976971e5..6460f449 100644 --- a/src/main/java/org/folio/rest/impl/NoteLinksImpl.java +++ b/src/main/java/org/folio/rest/impl/NoteLinksImpl.java @@ -61,7 +61,7 @@ public void putNoteLinksTypeIdByTypeAndId(String type, String id, NoteLinksPut e @Validate @Override - public void getNoteLinksDomainTypeIdByDomainAndTypeAndId(String domain, String type, String id, String title, + public void getNoteLinksDomainTypeIdByDomainAndTypeAndId(String domain, String type, String id, String search, List noteTypes, String status, String orderBy, String order, int offset, int limit, Map okapiHeaders, @@ -75,7 +75,7 @@ public void getNoteLinksDomainTypeIdByDomainAndTypeAndId(String domain, String t .validate(); Future notes = validated.compose( - v -> noteLinksService.findNotesByTitleAndNoteTypeAndStatus(new EntityLink(domain, type, id), title, noteTypes, + v -> noteLinksService.findNotesByTitleAndNoteTypeAndStatus(new EntityLink(domain, type, id), search, noteTypes, Status.enumOf(status), OrderBy.enumOf(orderBy), Order.enumOf(order), new RowPortion(offset, limit), tenantId(okapiHeaders))); diff --git a/src/main/resources/templates/db_scripts/create_note_search_column.sql b/src/main/resources/templates/db_scripts/create_note_search_column.sql new file mode 100644 index 00000000..275ce157 --- /dev/null +++ b/src/main/resources/templates/db_scripts/create_note_search_column.sql @@ -0,0 +1,26 @@ +ALTER TABLE note_data ADD COLUMN IF NOT EXISTS search_content text; + +CREATE INDEX search_content_idx_gin ON note_data USING gin (search_content public.gin_trgm_ops); + +CREATE OR REPLACE FUNCTION update_search_content() +RETURNS TRIGGER AS $$ +BEGIN + NEW.search_content = f_unaccent(coalesce(NEW.jsonb->>'title','') || ' ' + || regexp_replace( + regexp_replace( + coalesce(NEW.jsonb->>'content',''), + E'<[^>]+>', '', 'gi' + ), + '\n+', ' ', 'gi' + )); + RETURN NEW; +END; +$$ language 'plpgsql'; + +DROP TRIGGER IF EXISTS update_search_content ON note_data; + +CREATE TRIGGER update_search_content + BEFORE INSERT OR UPDATE ON note_data + FOR EACH ROW EXECUTE PROCEDURE update_search_content(); + +UPDATE note_data SET jsonb = jsonb; diff --git a/src/main/resources/templates/db_scripts/schema.json b/src/main/resources/templates/db_scripts/schema.json index c6aa1756..822f349a 100644 --- a/src/main/resources/templates/db_scripts/schema.json +++ b/src/main/resources/templates/db_scripts/schema.json @@ -54,6 +54,11 @@ "run": "after", "snippetPath": "create_note_type_view.sql", "fromModuleVersion": "mod-notes-2.7.0" + }, + { + "run": "after", + "snippetPath": "create_note_search_column.sql", + "fromModuleVersion": "mod-notes-2.11.0" } ] } diff --git a/src/test/java/org/folio/rest/impl/NoteLinksImplTest.java b/src/test/java/org/folio/rest/impl/NoteLinksImplTest.java index c61b6480..333dda7b 100644 --- a/src/test/java/org/folio/rest/impl/NoteLinksImplTest.java +++ b/src/test/java/org/folio/rest/impl/NoteLinksImplTest.java @@ -542,21 +542,25 @@ public void shouldReturn400WhenOrderByUpdatedDateParameterIsInvalid() { } @Test - public void shouldReturnListOfNotesSearchedByTitle() { - Note firstNote = getNote().withTitle("Title ABC"); + public void shouldReturnListOfNotesSearchedByContent() { + Note firstNote = getNote().withTitle("Title ABC").withContent("

test content

zztest

"); Note secondNote = getNote().withTitle("Title ZZZ ABC"); + Note thirdNote = getNote().withTitle("Title TTT"); postNoteWithOk(Json.encode(firstNote), USER8); postNoteWithOk(Json.encode(secondNote), USER8); + postNoteWithOk(Json.encode(thirdNote), USER8); createLinks(firstNote.getId()); createLinks(secondNote.getId()); + createLinks(thirdNote.getId()); List notes = getWithOk("/note-links/domain/" + DOMAIN + "/type/" + PACKAGE_TYPE + "/id/" + PACKAGE_ID - + "?title=ZZZ ") + + "?search=ZZ&orderBy=content") .as(NoteCollection.class) .getNotes(); - assertEquals(1, notes.size()); - assertEquals(secondNote.getTitle(), notes.get(0).getTitle()); + assertEquals(2, notes.size()); + assertEquals(firstNote.getTitle(), notes.get(0).getTitle()); + assertEquals(secondNote.getTitle(), notes.get(1).getTitle()); } @Test @@ -564,7 +568,7 @@ public void shouldReturnListOfNotesSearchedByTitleWithWildcard() { Note firstNote = getNote().withTitle("Title ZZZ ABC"); postNoteWithOk(Json.encode(firstNote), USER8); List notes = getWithOk("/note-links/domain/" + DOMAIN + "/type/" + PACKAGE_TYPE + "/id/" + PACKAGE_ID - + "?title=Z*") + + "?search=Z*") .as(NoteCollection.class) .getNotes(); @@ -577,7 +581,7 @@ public void shouldInterpretSpecialRegexCharactersLiterally() { Note firstNote = getNote().withTitle("a[abc1}{]z"); postNoteWithOk(Json.encode(firstNote), USER8); List notes = getWithOk("/note-links/domain/" + DOMAIN + "/type/" + PACKAGE_TYPE + "/id/" + PACKAGE_ID - + "?title=a[abc1}{]z") + + "?search=a[abc1}{]z") .as(NoteCollection.class) .getNotes(); @@ -791,7 +795,7 @@ public void shouldReturnNoteListWhenSearchByTitleAndNoteType() { List notes = getWithOk( "/note-links/domain/" + DOMAIN + "/type/" + PACKAGE_TYPE + "/id/" + PACKAGE_ID2 + - "?title=" + noteTitle + "¬eType=" + NOTE_TYPE2_NAME + " ¬eType= " + NOTE_TYPE_NAME + "?search=" + noteTitle + "¬eType=" + NOTE_TYPE2_NAME + " ¬eType= " + NOTE_TYPE_NAME + "&order=ASC&orderBy=status") .as(NoteCollection.class) .getNotes();