From 22af79f030d24bc783f3bc6c6a89c84e5a96345f Mon Sep 17 00:00:00 2001 From: GeReinhart Date: Fri, 28 Aug 2020 17:16:42 +0200 Subject: [PATCH] search page wip search page auto commit auto commit handle pages attached by the node.directoryPath no results search input breadcrumb breadcrumb breadcrumb breadcrumb remove code linked to kelkoo remove pom.xml use mysql 8 --- app/controllers/PageController.scala | 7 +- app/repositories/HierarchyRepository.scala | 6 + app/services/HierarchyService.scala | 55 +- app/services/PageService.scala | 21 +- app/services/SearchService.scala | 82 +- build.sbt | 2 +- conf/routes | 2 +- documentation/Changelog.md | 5 + frontend/package-lock.json | 46 +- .../page/header/header.component.html | 37 +- .../page/header/header.component.scss | 10 +- .../page/header/header.component.spec.ts | 2 + .../page/header/header.component.ts | 16 +- frontend/src/app/_models/page.ts | 1 + frontend/src/app/_models/search.ts | 27 + .../src/app/_services/page.service.spec.ts | 16 + frontend/src/app/_services/page.service.ts | 83 +- .../src/app/_services/route.service.spec.ts | 17 +- frontend/src/app/_services/route.service.ts | 36 +- frontend/src/app/_testUtils/test-data.spec.ts | 1096 +++++++++-------- frontend/src/app/app-routing.module.ts | 8 +- frontend/src/app/app.module.ts | 8 + .../search-page/search-page.component.html | 9 + .../search-page/search-page.component.scss | 31 + .../search-page/search-page.component.ts | 34 + .../search-query/search-query.component.html | 4 + .../search-query/search-query.component.scss | 16 + .../search-query/search-query.component.ts | 30 + .../search-result-item.component.html | 4 + .../search-result-item.component.scss | 6 + .../search-result-item.component.ts | 31 + .../search-results.component.html | 10 + .../search-results.component.scss | 0 .../search-results.component.ts | 14 + local-conf/application.dev.conf | 2 - pom.xml | 168 --- test/OnGoingBDDTest.scala | 2 - test/features/navigation/provide_menu.feature | 2 +- test/features/search/search_pages.feature | 297 +++++ .../search/search_through_name_pages.feature | 142 --- test/services/SearchServiceTest.scala | 68 +- test/steps/CommonSteps.scala | 4 +- 42 files changed, 1457 insertions(+), 1000 deletions(-) create mode 100644 frontend/src/app/_models/search.ts create mode 100644 frontend/src/app/output/search/search-page/search-page.component.html create mode 100644 frontend/src/app/output/search/search-page/search-page.component.scss create mode 100644 frontend/src/app/output/search/search-page/search-page.component.ts create mode 100644 frontend/src/app/output/search/search-query/search-query.component.html create mode 100644 frontend/src/app/output/search/search-query/search-query.component.scss create mode 100644 frontend/src/app/output/search/search-query/search-query.component.ts create mode 100644 frontend/src/app/output/search/search-result-item/search-result-item.component.html create mode 100644 frontend/src/app/output/search/search-result-item/search-result-item.component.scss create mode 100644 frontend/src/app/output/search/search-result-item/search-result-item.component.ts create mode 100644 frontend/src/app/output/search/search-results/search-results.component.html create mode 100644 frontend/src/app/output/search/search-results/search-results.component.scss create mode 100644 frontend/src/app/output/search/search-results/search-results.component.ts delete mode 100644 pom.xml create mode 100644 test/features/search/search_pages.feature delete mode 100644 test/features/search/search_through_name_pages.feature diff --git a/app/controllers/PageController.scala b/app/controllers/PageController.scala index 07829e39a..67e51bbf9 100644 --- a/app/controllers/PageController.scala +++ b/app/controllers/PageController.scala @@ -30,11 +30,10 @@ class PageController @Inject()(pageService: PageService, searchService: SearchSe } } - @ApiOperation(value = "Search pages through keywords", response = classOf[PageIndexDocument], responseContainer = "list") + @ApiOperation(value = "Search pages through a keyword", response = classOf[SearchResult]) @ApiResponses(Array(new ApiResponse(code = 404, message = "Page not found"))) - def searchPage(keywords: String): Action[AnyContent] = Action { - implicit val itemsFormat = Json.format[PageIndexDocument] - Ok(Json.toJson(searchService.searchForPage(keywords))) + def searchPage(keyword: String): Action[AnyContent] = Action { + Ok(Json.toJson(searchService.searchForPage(keyword))) } } diff --git a/app/repositories/HierarchyRepository.scala b/app/repositories/HierarchyRepository.scala index 657bd66cb..3bfd477f2 100644 --- a/app/repositories/HierarchyRepository.scala +++ b/app/repositories/HierarchyRepository.scala @@ -67,6 +67,12 @@ class HierarchyRepository @Inject()(db: Database) { } } + def findByDirectoryPath(directoryPath: String): Option[HierarchyNode] = { + db.withConnection { implicit connection => + SQL"SELECT * FROM hierarchyNode WHERE directoryPath = $directoryPath".as(parser.*).headOption + } + } + def findAllByProjectId(projectId: String): Seq[HierarchyNode] = { db.withConnection { implicit connection => SQL"SELECT * FROM project_hierarchyNode INNER JOIN hierarchyNode on (id = hierarchyId) WHERE projectId = $projectId".as(parser.*) diff --git a/app/services/HierarchyService.scala b/app/services/HierarchyService.scala index 2ecb53f45..37fdc956b 100644 --- a/app/services/HierarchyService.scala +++ b/app/services/HierarchyService.scala @@ -24,22 +24,47 @@ class HierarchyService @Inject()(hierarchyRepository: HierarchyRepository) { case Some(shortcut) => !hierarchy.id.startsWith(shortcut) } - def getHierarchyPath(pageJoinProject: PageJoinProject): String = { - val projectHierarchies = hierarchyRepository.findAllByProjectId(pageJoinProject.project.id) - if (projectHierarchies.nonEmpty) { - val hierarchyId = projectHierarchies.headOption.map(_.id).getOrElse("") - val splitedId = hierarchyId.split('.') - var hierarchyPath = "" - var currentHierarchyId = "." - for (id <- splitedId) { - if (id.nonEmpty) { - currentHierarchyId += id + "." - hierarchyPath += "/" + hierarchyRepository.findById(currentHierarchyId).map(_.name).getOrElse("Not Found") + def getHierarchyPath(pageJoinProject: PageJoinProject): Option[String] = { + val projectHierarchy = hierarchyRepository.findAllByProjectId(pageJoinProject.project.id).headOption match { + case Some(h) => Some(h) + case None => hierarchyRepository.findByDirectoryPath(pageJoinProject.directory.path) + } + projectHierarchy match { + case Some(h) => + val hierarchyId = h.id + val spitedId = hierarchyId.split('.') + var hierarchyPath = "" + var currentHierarchyId = "." + for (id <- spitedId) { + if (id.nonEmpty) { + currentHierarchyId += id + "." + hierarchyPath += "/" + hierarchyRepository.findById(currentHierarchyId).map(_.slugName).getOrElse("Not Found") + } + } + Some(hierarchyPath) + case None => None + } + } + + def getBreadcrumb(pageJoinProject: PageJoinProject): String = { + val projectHierarchy = hierarchyRepository.findAllByProjectId(pageJoinProject.project.id).headOption match { + case Some(h) => Some(h) + case None => hierarchyRepository.findByDirectoryPath(pageJoinProject.directory.path) + } + projectHierarchy match { + case Some(h) => + val hierarchyId = h.id + val spitedId = hierarchyId.split('.') + var hierarchyPath = "" + var currentHierarchyId = "." + for (id <- spitedId) { + if (id.nonEmpty) { + currentHierarchyId += id + "." + hierarchyPath += "/" + hierarchyRepository.findById(currentHierarchyId).map(_.name).getOrElse("Not Found") + } } - } - hierarchyPath + "/" + pageJoinProject.branch.name + pageJoinProject.page.relativePath - } else { - pageJoinProject.page.path + hierarchyPath.replaceFirst("/", "") + " / " + pageJoinProject.project.name + pageJoinProject.directory.relativePath.replaceAll("/", " / ") + pageJoinProject.page.label + case None => "" } } diff --git a/app/services/PageService.scala b/app/services/PageService.scala index 5a1dba665..3c5cfb036 100644 --- a/app/services/PageService.scala +++ b/app/services/PageService.scala @@ -245,7 +245,9 @@ class PageService @Inject()(config: Configuration, projectRepository: ProjectRep def computePageFromPathUsingDatabaseBis(pageJoinProjectOpt: Option[PageJoinProject], path: String, forceRefresh: Boolean = true): Future[Option[PageWithContent]] = { pageJoinProjectOpt match { case Some(pageJoinProject) => - indexService.addDocument(PageIndexDocument(hierarchyService.getHierarchyPath(pageJoinProject), pageJoinProject.page.path, pageJoinProject.branch.name, pageJoinProject.page.label, pageJoinProject.page.description, pageJoinProject.page.markdown.getOrElse(""))) + if (pageJoinProject.branch.isStable) { + insertOrUpdateIndex(pageJoinProject) + } val key = computePageCacheKey(path) if (cache.get(key).isEmpty || forceRefresh || pageJoinProject.page.dependOnOpenApi) { logger.debug(s"Page computed: $path") @@ -273,6 +275,23 @@ class PageService @Inject()(config: Configuration, projectRepository: ProjectRep } } + private def insertOrUpdateIndex(pageJoinProject: PageJoinProject) = { + + hierarchyService.getHierarchyPath(pageJoinProject).map{ hierarchy => + val document = PageIndexDocument(id = hierarchy + "/" + pageJoinProject.page.path, + hierarchy = hierarchy, + path = pageJoinProject.page.path, + breadcrumb = hierarchyService.getBreadcrumb(pageJoinProject), + project = pageJoinProject.project.name, + branch = pageJoinProject.branch.name, + label = pageJoinProject.page.label, + description = pageJoinProject.page.description, + pageContent = pageJoinProject.page.markdown.getOrElse("")) + indexService.insertOrUpdateDocument(document) + } + + } + def processPage(currentDirectory: Directory, localDirectoryPath: String, name: String, order: Int): Option[Page] = { val pageFile = new File(localDirectoryPath + "/" + name + ".md") if (pageFile.exists()) { diff --git a/app/services/SearchService.scala b/app/services/SearchService.scala index 3be24609f..d0fa535cb 100644 --- a/app/services/SearchService.scala +++ b/app/services/SearchService.scala @@ -1,33 +1,93 @@ package services +import com.outr.lucene4s.query.{Sort, TermSearchTerm} import com.outr.lucene4s.{DirectLucene, parse} -import com.outr.lucene4s.query.Sort import javax.inject.{Inject, Singleton} +import play.api.libs.json.Json +case class SearchResult(items: Seq[SearchResultItem]) + +case class SearchResultItem(page: PageIndexDocument, highlights: Seq[HighlightedFragment]) + +case class PageIndexDocument(id: String, hierarchy: String, path: String, breadcrumb: String, project: String, branch: String, label: String, description: String, pageContent: String) + +case class HighlightedFragment(fragment: String, word: String) + +object HighlightedFragment { + implicit val format = Json.format[HighlightedFragment] +} + +object PageIndexDocument { + implicit val format = Json.format[PageIndexDocument] +} + +object SearchResultItem { + implicit val format = Json.format[SearchResultItem] +} + +object SearchResult { + implicit val format = Json.format[SearchResult] +} -case class PageIndexDocument(hierarchy: String, path: String, branch: String, label: String, description: String, pageContent: String) @Singleton class IndexService { - val luceneSearchIndex = new DirectLucene(uniqueFields = List("path"), defaultFullTextSearchable = true) + val luceneSearchIndex = new DirectLucene(uniqueFields = List("id"), defaultFullTextSearchable = true, appendIfExists = false, autoCommit = true) + private val id = luceneSearchIndex.create.field[String]("id") private val hierarchy = luceneSearchIndex.create.field[String]("hierarchy") private val path = luceneSearchIndex.create.field[String]("path", fullTextSearchable = false) + private val breadcrumb = luceneSearchIndex.create.field[String]("breadcrumb") + private val project = luceneSearchIndex.create.field[String]("project") private val branch = luceneSearchIndex.create.field[String]("branch") private val label = luceneSearchIndex.create.field[String]("label") private val description = luceneSearchIndex.create.field[String]("description") private val pageContent = luceneSearchIndex.create.field[String]("pageContent", sortable = false) - def addDocument(document: PageIndexDocument): Unit = { - luceneSearchIndex.doc().fields(hierarchy(document.hierarchy), description(document.description), label(document.label), this.path(document.path), branch(document.branch), pageContent(document.pageContent)).index() - } + def insertOrUpdateDocument(document: PageIndexDocument): Unit = { - def query(keywords: String): Seq[PageIndexDocument] = { - val results = luceneSearchIndex.query().sort(Sort.Score).filter(parse("label:" + keywords + "*^100 | pageContent:" + keywords + "*^30 | description:" + keywords + "*^50 | branch:" + keywords + "*^30")).highlight().search() - results.results.map { - result => PageIndexDocument(result(hierarchy), result(path), result(branch), result(label), result(description), result(pageContent)) + var i = 0 + while (exists(document) && i < 10) { + luceneSearchIndex.delete(new TermSearchTerm(Some(id), document.id)) + luceneSearchIndex.commit() + i = i + 1 } + + luceneSearchIndex.doc().fields(id(document.id), + hierarchy(document.hierarchy), + path(document.path), + breadcrumb(document.breadcrumb), + project(document.project), + branch(document.branch), + label(document.label), + description(document.description), + pageContent(document.pageContent) + ).index() + luceneSearchIndex.commit() + } + + private def exists(document: PageIndexDocument) = { + luceneSearchIndex.query().filter(new TermSearchTerm(Some(id), document.id)).search().results.length > 0 + } + + def query(keywords: String): SearchResult = { + val results = luceneSearchIndex.query().sort(Sort.Score).filter(parse("id:" + keywords + "*^20 " + " | " + + "breadcrumb:" + keywords + "*^50 " + " | " + + "project:" + keywords + "*^30 " + " | " + + "branch:" + keywords + "*^30" + " | " + + "label:" + keywords + "*^100 " + " | " + + "description:" + keywords + "*^100 " + " | " + + "pageContent:" + keywords + "*^30" + )).highlight().limit(50).search() + SearchResult(results.results.map { + result => + val highlighting = result.highlighting(breadcrumb).map(frg => HighlightedFragment(frg.fragment, frg.word)) +: + result.highlighting(label).map(frg => HighlightedFragment(frg.fragment, frg.word)) +: + result.highlighting(description).map(frg => HighlightedFragment(frg.fragment, frg.word)) +: + result.highlighting(pageContent).map(frg => HighlightedFragment(frg.fragment, frg.word)) +: Nil + SearchResultItem(PageIndexDocument(result(id), result(hierarchy), result(path), result(breadcrumb), result(project), result(branch), result(label), result(description), result(pageContent)), highlighting.flatten) + }) } def reset(): Unit = { @@ -38,7 +98,7 @@ class IndexService { class SearchService @Inject()(pageIndex: IndexService) { - def searchForPage(keywords: String): Seq[PageIndexDocument] = { + def searchForPage(keywords: String): SearchResult = { pageIndex.query(keywords) } diff --git a/build.sbt b/build.sbt index 984c1397c..6004344e7 100644 --- a/build.sbt +++ b/build.sbt @@ -88,7 +88,7 @@ libraryDependencies ++= Seq( "org.julienrf" %% "play-json-derived-codecs" % "6.0.0", "io.cucumber" % "gherkin" % "5.2.0", "com.typesafe.play" %% "anorm" % "2.5.3", - "mysql" % "mysql-connector-java" % "5.1.48", + "mysql" % "mysql-connector-java" % "8.0.20", "org.eclipse.jgit" % "org.eclipse.jgit" % "5.6.1.202002131546-r", "io.swagger" %% "swagger-play2" % "1.7.1", "com.h2database" % "h2" % "1.4.199", diff --git a/conf/routes b/conf/routes index 4f54b315c..311af701d 100644 --- a/conf/routes +++ b/conf/routes @@ -41,7 +41,7 @@ GET /api/config controllers.ConfigCon GET /api/directories controllers.DirectoryController.getDirectoryFromPath(path: String) GET /api/pages controllers.PageController.getPageFromPath(path: String) -GET /api/pages/search controllers.PageController.searchPage(keywords: String) +GET /api/pages/search controllers.PageController.searchPage(keyword: String) POST /api/admin/menu/refreshFromDatabase controllers.AdminController.refreshMenu() POST /api/admin/projects/refreshFromDatabase controllers.AdminController.refreshAllProjectsFromDatabase() diff --git a/documentation/Changelog.md b/documentation/Changelog.md index 218186a9a..1f195ff8a 100644 --- a/documentation/Changelog.md +++ b/documentation/Changelog.md @@ -8,6 +8,11 @@ } ``` +### [2020-09][1.4.0] + +The main features is: +- [0655 - Search](https://github.com/KelkooGroup/theGardener/issues/655) + ### [2020-07][1.3.2] The main features is: diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 64f537a36..ae9a1bc13 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -5556,14 +5556,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5582,7 +5580,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5762,8 +5759,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -5869,8 +5865,7 @@ "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } } @@ -16623,8 +16618,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -16652,7 +16646,6 @@ "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -16667,8 +16660,7 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", @@ -16679,8 +16671,7 @@ "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -16797,8 +16788,7 @@ "inherits": { "version": "2.0.4", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -16810,7 +16800,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -16825,7 +16814,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -16833,14 +16821,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.9.0", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -16859,7 +16845,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -16949,8 +16934,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -16962,7 +16946,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -17048,8 +17031,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -17085,7 +17067,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -17105,7 +17086,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -17149,14 +17129,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/frontend/src/app/_components/page/header/header.component.html b/frontend/src/app/_components/page/header/header.component.html index 7ecd7c313..79399e1bf 100644 --- a/frontend/src/app/_components/page/header/header.component.html +++ b/frontend/src/app/_components/page/header/header.component.html @@ -1,26 +1,29 @@
-
diff --git a/frontend/src/app/_components/page/header/header.component.scss b/frontend/src/app/_components/page/header/header.component.scss index fdf75a66e..364f7034f 100644 --- a/frontend/src/app/_components/page/header/header.component.scss +++ b/frontend/src/app/_components/page/header/header.component.scss @@ -19,7 +19,7 @@ flex-wrap: wrap; } -.header-logo, .header-navigation, .header-logo-the-gardener { +.header-logo, .header-navigation, .header-search, .header-logo-the-gardener { flex: 1; margin-top: 0; margin-bottom: 0; @@ -81,6 +81,10 @@ } +.header-navigation{ + padding-bottom: 8px; +} + .header-navigation a { transition: all 0.3s ease; /* Add transition for hover effects */ text-decoration: none; @@ -93,6 +97,10 @@ } } +.header-search{ + padding-right: 8px; +} + .header-navigation-responsive { list-style: none; } diff --git a/frontend/src/app/_components/page/header/header.component.spec.ts b/frontend/src/app/_components/page/header/header.component.spec.ts index af2105dc2..7365d8b45 100644 --- a/frontend/src/app/_components/page/header/header.component.spec.ts +++ b/frontend/src/app/_components/page/header/header.component.spec.ts @@ -17,6 +17,7 @@ import {MatIconModule} from '@angular/material/icon'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; import {FormsModule} from '@angular/forms'; import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {SearchQueryComponent} from "../../../output/search/search-query/search-query.component"; describe('HeaderComponent', () => { let component: HeaderComponent; @@ -31,6 +32,7 @@ describe('HeaderComponent', () => { HeaderComponent, NavigateMobileMenuComponent, NavigateMenuItemComponent, + SearchQueryComponent ], imports: [ MatTabsModule, diff --git a/frontend/src/app/_components/page/header/header.component.ts b/frontend/src/app/_components/page/header/header.component.ts index 58d4ab299..b79f3335f 100644 --- a/frontend/src/app/_components/page/header/header.component.ts +++ b/frontend/src/app/_components/page/header/header.component.ts @@ -18,6 +18,7 @@ export class HeaderComponent implements OnInit { @Input() appTitle?: string; items: Array; + url: string; constructor(private menuService: MenuService, private routeService: RouteService, @@ -33,9 +34,10 @@ export class HeaderComponent implements OnInit { this.items = result; if (this.items.length > 0) { const currentRoute = this.getCurrentRoute(); - - if ( !currentRoute.nodes || currentRoute.nodes?.length === 0 || currentRoute.page === undefined) { - this.navigateTo(this.items[0]); + if ( this.url== "" || this.url== undefined || this.routeService.isNavigationUrl(this.url)) { + if (!currentRoute.nodes || currentRoute.nodes?.length === 0 || currentRoute.page === undefined) { + this.navigateTo(this.items[0]); + } } } }, error => { @@ -47,9 +49,15 @@ export class HeaderComponent implements OnInit { let params : NavigationParams; if (this.activatedRoute.firstChild && this.activatedRoute.firstChild.snapshot){ params = this.activatedRoute.firstChild.snapshot.params; + if (this.activatedRoute.firstChild.snapshot.url){ + this.url = this.activatedRoute.firstChild.snapshot.url.join('/'); + } } if (!params && this.activatedRoute && this.activatedRoute.snapshot){ params = this.activatedRoute.snapshot.params; + if (this.activatedRoute.firstChild.snapshot.url){ + this.url = this.activatedRoute.firstChild.snapshot.url.join('/'); + } } return this.routeService.navigationParamsToNavigationRoute(params); } @@ -60,6 +68,6 @@ export class HeaderComponent implements OnInit { } isItemInActivatedRoute(item: MenuHierarchy) { - return this.getCurrentRoute().nodes[0] === item.name; + return this.getCurrentRoute().nodes[0] === item.name; } } diff --git a/frontend/src/app/_models/page.ts b/frontend/src/app/_models/page.ts index f0d0c58ea..967f86b53 100644 --- a/frontend/src/app/_models/page.ts +++ b/frontend/src/app/_models/page.ts @@ -1,6 +1,7 @@ import {Scenario} from './gherkin'; import {OpenApiModel, OpenApiPath} from './open-api'; + export interface Page { title: string; path: string; diff --git a/frontend/src/app/_models/search.ts b/frontend/src/app/_models/search.ts new file mode 100644 index 000000000..345ab12c0 --- /dev/null +++ b/frontend/src/app/_models/search.ts @@ -0,0 +1,27 @@ + + +export interface SearchResult { + items: Array; +} + +export interface SearchResultItem { + page: PageIndexDocument; + highlights: Array; +} + +export interface PageIndexDocument { + id: string; + hierarchy: string; + path: string; + breadcrumb: string; + project: string; + branch: string; + label: string; + description: string; + pageContent: string; +} + +export interface HighlightedFragment { + fragment: string; + word: string; +} diff --git a/frontend/src/app/_services/page.service.spec.ts b/frontend/src/app/_services/page.service.spec.ts index 5ba9e66db..2ae5c91ff 100644 --- a/frontend/src/app/_services/page.service.spec.ts +++ b/frontend/src/app/_services/page.service.spec.ts @@ -7,6 +7,7 @@ import { PAGE_SERVICE_RESPONSE, PAGE_WITH_EXTERNAL_LINK_SERVICE_RESPONSE, PAGE_WITH_SCENARIO, + SEARCH_RESULTS } from '../_testUtils/test-data.spec'; import {IncludeExternalPagePart, MarkdownPart} from '../_models/page'; @@ -54,6 +55,21 @@ describe('PageService', () => { req.flush([DIRECTORIES_SERVICE_RESPONSE]); })); + xit('should search pages for keyword', async(() => { + pageService.searchPages('suggestions') + .subscribe(result => { + expect(result.items).toBeDefined(); + expect(result.items.length).toBe(1); + expect(result.items[0].page.label).toBe("The context"); + }); + + + const req = httpMock.expectOne(mockRequest => + mockRequest.method === 'GET' && mockRequest.url === 'api/pages/search' && mockRequest.params.get('keyword') === 'suggestions'); + req.flush([SEARCH_RESULTS]); + })); + + it('should get page for path', async(() => { pageService.getPage('publisherManagementWS>qa>_constraints_overview') .subscribe(page => { diff --git a/frontend/src/app/_services/page.service.ts b/frontend/src/app/_services/page.service.ts index 93ba02834..1c1bfd371 100644 --- a/frontend/src/app/_services/page.service.ts +++ b/frontend/src/app/_services/page.service.ts @@ -5,48 +5,59 @@ import {DirectoryApi, PageApi} from '../_models/hierarchy'; import {map} from 'rxjs/operators'; import {Page} from '../_models/page'; import {RouteService} from "./route.service"; +import {SearchResult} from "../_models/search"; @Injectable({ - providedIn: 'root' + providedIn: 'root' }) export class PageService { - constructor(private http: HttpClient, - private routeService: RouteService) { - } + constructor(private http: HttpClient, + private routeService: RouteService) { + } + + getRootDirectoryForPath(path: string): Observable { + const url = `api/directories`; + const params = new HttpParams().set('path', this.routeService.urlToRelativePath(path)); + return this.http.get>(url, {params}) + .pipe( + map(res => res[0]) + ); + } - getRootDirectoryForPath(path: string): Observable { - const url = `api/directories`; - const params = new HttpParams().set('path', this.routeService.urlToRelativePath(path)); - return this.http.get>(url, {params}) - .pipe( - map(res => res[0]) - ); - } + getPage(path: string): Observable { + const url = `api/pages`; + const params = new HttpParams().set('path', this.routeService.urlToRelativePath(path)); + return this.http.get>(url, {params}) + .pipe( + map(res => res[0]), + map(page => { + const res: Page = { + title: this.getPageTitle(page), + path: page.path, + order: page.order, + parts: page.content, + sourceUrl: page.sourceUrl + }; + return res; + }) + ); + } - getPage(path: string): Observable { - const url = `api/pages`; - const params = new HttpParams().set('path', this.routeService.urlToRelativePath(path)); - return this.http.get>(url, {params}) - .pipe( - map(res => res[0]), - map(page => { - const res: Page = { - title: this.getPageTitle(page), - path: page.path, - order: page.order, - parts: page.content, - sourceUrl: page.sourceUrl - }; - return res; - }) - ); - } + searchPages(keyword: string): Observable { + const url = `api/pages/search`; + const params = new HttpParams().set('keyword', keyword); + return this.http.get(url, {params}) + .pipe( + map(res => res) + ); + } - private getPageTitle(page: PageApi): string { - if (page.content && page.content.length === 1 && page.content[0].type === 'includeExternalPage') { - return undefined; - } else { - return page.description; + private getPageTitle(page: PageApi): string { + if (page.content && page.content.length === 1 && page.content[0].type === 'includeExternalPage') { + return undefined; + } else { + return page.description; + } } - } + } diff --git a/frontend/src/app/_services/route.service.spec.ts b/frontend/src/app/_services/route.service.spec.ts index 74c3dcdc5..1f723b50f 100644 --- a/frontend/src/app/_services/route.service.spec.ts +++ b/frontend/src/app/_services/route.service.spec.ts @@ -11,6 +11,12 @@ describe('RouteService', () => { service = TestBed.get(RouteService); }); + it('isNavigationUrl ', () => { + expect(service.isNavigationUrl("app/documentation/search")).toEqual(false); + expect(service.isNavigationUrl("app/documentation/navigate")).toEqual(true); + + }); + it('navigationParamsToNavigationRoute focus on nodes', () => { expect(service.navigationParamsToNavigationRoute({nodes: "_publisher"}).nodes).toEqual(["publisher"]); expect(service.navigationParamsToNavigationRoute({nodes: "publisher"}).nodes).toEqual(["publisher"]); @@ -84,8 +90,13 @@ describe('RouteService', () => { }); it('backEndPathToNavigationRoute focus on directory', () => { - expect(service.backEndPathToNavigationRoute("publisherSystems>>/")).toEqual({ project: "publisherSystems", branch: "_", directories: [] }); - expect(service.backEndPathToNavigationRoute("publisherData>>/Public/")).toEqual({ project: "publisherData", branch: "_", directories: ["Public"] }); + expect(service.backEndPathToNavigationRoute("publisherSystems>>/")).toEqual({ project: "publisherSystems", branch: "_", directories: [], page:undefined }); + expect(service.backEndPathToNavigationRoute("publisherData>>/Public/")).toEqual({ project: "publisherData", branch: "_", directories: ["Public"], page:undefined }); + }); + + it('backEndHierarchyAndPathToFrontEndPath ', () => { + expect(service.backEndHierarchyAndPathToFrontEndPath("/platform/publisher/systems/mgt","pmws>qa>/Features/Events/Constraints")).toEqual("_platform_publisher_systems_mgt/pmws/qa/_Features_Events/Constraints"); + expect(service.backEndHierarchyAndPathToFrontEndPath("/platform/publisher/systems/mgt","pmbo>qa>/Meta")).toEqual("_platform_publisher_systems_mgt/pmbo/qa/_/Meta"); }); it('menuHierarchyToFrontEndPath focus on node without a directory ', () => { @@ -147,7 +158,7 @@ describe('RouteService', () => { expect(RouteService.legacyFullFrontEndUrlToFullFrontEndUrl("app/documentation/navigate/_publisher/publisherSystems/_/_/Services")).toEqual("app/documentation/navigate/_publisher/publisherSystems/_/_/Services"); }); - fit('relativeUrlToFullFrontEndUrl ', () => { + it('relativeUrlToFullFrontEndUrl ', () => { expect(service.relativeUrlToFullFrontEndUrl('../OfferSearch/Feature',{nodes: '_platform_publisher_systems_public',project: 'shoppingAPIPublic',branch: 'feature_PUB-4629-review-doc',directories: '_Features_OfferFeeds',page: 'Feature'} )) .toEqual("app/documentation/navigate/_platform_publisher_systems_public/shoppingAPIPublic/feature_PUB-4629-review-doc/_Features_OfferSearch/Feature"); diff --git a/frontend/src/app/_services/route.service.ts b/frontend/src/app/_services/route.service.ts index be9c00e24..7424d0930 100644 --- a/frontend/src/app/_services/route.service.ts +++ b/frontend/src/app/_services/route.service.ts @@ -5,6 +5,7 @@ import {MenuHierarchy} from '../_models/menu'; export const EMPTY_CHAR = '_'; export const EMPTY_CHAR_REGEX = /_/g; export const NAVIGATE_PATH = 'app/documentation/navigate/'; +export const SEARCH_PATH = 'app/documentation/search'; @Injectable({ providedIn: 'root' @@ -239,14 +240,24 @@ export class RouteService { } let directoriesAsString = backEndPathElements[2]; let directories = [] as Array; - if (directoriesAsString.length > 1 && directoriesAsString[0] === '/' && directoriesAsString[directoriesAsString.length - 1] === '/') { - directoriesAsString = directoriesAsString.substr(1, directoriesAsString.length - 2); - directories = directoriesAsString.split('/'); + let page = undefined; + + if (directoriesAsString.length == 1) { + + } else { + if (directoriesAsString.length > 1 && directoriesAsString[directoriesAsString.length - 1] === '/') { + directoriesAsString = directoriesAsString.substr(1, directoriesAsString.length - 2); + directories = directoriesAsString.split('/'); + } else { + directories = directoriesAsString.split('/'); + page = directories.pop() + } } return { project: projectId, branch, - directories + directories, + page }; } @@ -300,4 +311,21 @@ export class RouteService { && currentNavigationRoute.directories?.join(EMPTY_CHAR) === menuItemNavigationRoute.directories?.join(EMPTY_CHAR) && currentNavigationRoute.page === menuItemNavigationRoute.page; } + + isNavigationUrl(relativeUrl: string): Boolean { + return relativeUrl != undefined && relativeUrl.startsWith("app/documentation/navigate"); + } + + backEndHierarchyAndPathToFrontEndPath(hierarchy: string, path: string) { + + let navigationRoute = this.backEndPathToNavigationRoute(path); + let hierarchyFrontEndPath = hierarchy.replace(/\//g, EMPTY_CHAR); + let directories; + if (navigationRoute.directories.length > 1) { + directories = navigationRoute.directories.join(EMPTY_CHAR) + } else { + directories = EMPTY_CHAR + } + return hierarchyFrontEndPath + '/' + navigationRoute.project + '/' + navigationRoute.branch + '/' + directories + '/' + navigationRoute.page; + } } diff --git a/frontend/src/app/_testUtils/test-data.spec.ts b/frontend/src/app/_testUtils/test-data.spec.ts index 889a611e9..3e7bf2cfc 100644 --- a/frontend/src/app/_testUtils/test-data.spec.ts +++ b/frontend/src/app/_testUtils/test-data.spec.ts @@ -1,585 +1,613 @@ import {DirectoryApi, HierarchyNodeApi, PageApi} from '../_models/hierarchy'; import {MenuHierarchy, MenuType} from '../_models/menu'; +import {SearchResult} from "../_models/search"; export const MENU_HEADER_SERVICE_RESPONSE: MenuHierarchy = { - name: 'root', - label: 'Root', - type: 'Node' as MenuType, - depth: 0, - route: {nodes: [], directories: [] as Array}, - children: [ - { - name: 'publisher', - label: 'Publisher', - type: 'Node' as MenuType, - depth: 1, - route: {nodes: ['publisher'], directories: [] as Array}, - children: [] as Array - }, - { - name: 'tools', - label: 'Tools', - type: 'Node' as MenuType, - depth: 1, - route: {nodes: ['tools'], directories: [] as Array}, - children: [] as Array - } - ] + name: 'root', + label: 'Root', + type: 'Node' as MenuType, + depth: 0, + route: {nodes: [], directories: [] as Array}, + children: [ + { + name: 'publisher', + label: 'Publisher', + type: 'Node' as MenuType, + depth: 1, + route: {nodes: ['publisher'], directories: [] as Array}, + children: [] as Array + }, + { + name: 'tools', + label: 'Tools', + type: 'Node' as MenuType, + depth: 1, + route: {nodes: ['tools'], directories: [] as Array}, + children: [] as Array + } + ] }; export const MENU_SUBMENU_SERVICE_RESPONSE: HierarchyNodeApi = { - id: '.01.', - hierarchy: '_eng', - slugName: 'eng', - name: 'Engineering view', - childrenLabel: 'System groups', - childLabel: 'System group', - projects: [], - children: [ - { - id: '.01.01.', - hierarchy: '_eng_library', - slugName: 'library', - name: 'Library system group', - childrenLabel: 'Systems', - childLabel: 'System', - projects: [], - children: [ + id: '.01.', + hierarchy: '_eng', + slugName: 'eng', + name: 'Engineering view', + childrenLabel: 'System groups', + childLabel: 'System group', + projects: [], + children: [ { - id: '.01.01.01.', - hierarchy: '_eng_library_suggestion', - slugName: 'suggestion', - name: 'Suggestion system', - childrenLabel: 'Projects', - childLabel: 'Project', - projects: [ - { - id: 'suggestionsReports', - path: 'suggestionsReports', - label: 'Suggestions Reports', - stableBranch: 'master', - branches: [ + id: '.01.01.', + hierarchy: '_eng_library', + slugName: 'library', + name: 'Library system group', + childrenLabel: 'Systems', + childLabel: 'System', + projects: [], + children: [ { - name: 'master', - path: 'suggestionsReports>master' - } - ] - }, - { - id: 'suggestionsWS', - path: 'suggestionsWS', - label: 'Suggestions WebServices', - stableBranch: 'master', - branches: [ + id: '.01.01.01.', + hierarchy: '_eng_library_suggestion', + slugName: 'suggestion', + name: 'Suggestion system', + childrenLabel: 'Projects', + childLabel: 'Project', + projects: [ + { + id: 'suggestionsReports', + path: 'suggestionsReports', + label: 'Suggestions Reports', + stableBranch: 'master', + branches: [ + { + name: 'master', + path: 'suggestionsReports>master' + } + ] + }, + { + id: 'suggestionsWS', + path: 'suggestionsWS', + label: 'Suggestions WebServices', + stableBranch: 'master', + branches: [ + { + name: 'master', + path: 'suggestionsWS>master', + rootDirectory: { + id: '1', + path: 'suggestionsWS>master>/', + name: 'root', + label: 'SuggestionsWS', + description: 'Suggestions WebServices', + order: 0, + children: [ + { + id: '2', + path: 'suggestionsWS>master>/suggestions/', + name: 'suggestions', + label: 'Suggestions', + description: 'Suggestions...', + order: 0, + children: [] + }, + { + id: '3', + path: 'suggestionsWS>master>/admin/', + name: 'admin', + label: 'Admin', + description: 'Administration...', + order: 1, + children: [] + } + ] + } + }, + { + name: 'bugfix/351', + path: 'suggestionsWS>bugfix/351' + } + ] + } + ], + children: [] + }, { - name: 'master', - path: 'suggestionsWS>master', - rootDirectory: { - id: '1', - path: 'suggestionsWS>master>/', - name: 'root', - label: 'SuggestionsWS', - description: 'Suggestions WebServices', - order: 0, - children: [ - { - id: '2', - path: 'suggestionsWS>master>/suggestions/', - name: 'suggestions', - label: 'Suggestions', - description: 'Suggestions...', - order: 0, - children: [] - }, - { - id: '3', - path: 'suggestionsWS>master>/admin/', - name: 'admin', - label: 'Admin', - description: 'Administration...', - order: 1, - children: [] - } - ] - } + id: '.01.01.02.', + hierarchy: '_eng_library_user', + slugName: 'user', + name: 'User system', + childrenLabel: 'Projects', + childLabel: 'Project', + projects: [ + { + id: 'usersWS', + path: 'usersWS', + label: 'Users WebServices', + stableBranch: 'master', + branches: [ + { + name: 'master', + path: 'usersWS>master' + } + ] + } + ], + children: [] }, { - name: 'bugfix/351', - path: 'suggestionsWS>bugfix/351' + id: '.01.01.03.', + hierarchy: '_eng_library_search', + slugName: 'search', + name: 'Search system', + childrenLabel: 'Projects', + childLabel: 'Project', + projects: [], + children: [] } - ] - } - ], - children: [] + ] + } + ] +}; + +export const DIRECTORIES_SERVICE_RESPONSE: DirectoryApi = { + id: '11', + path: 'publisherManagementWS>qa>/constraints/', + name: 'constraints', + label: 'Constraints', + description: 'Filter offers provided to the publisher', + order: 0, + pages: [ + { + path: 'publisherManagementWS>qa>/constraints/overview', + relativePath: 'overview', + name: 'overview', + label: 'Overview', + description: 'overview', + order: 0, }, { - id: '.01.01.02.', - hierarchy: '_eng_library_user', - slugName: 'user', - name: 'User system', - childrenLabel: 'Projects', - childLabel: 'Project', - projects: [ - { - id: 'usersWS', - path: 'usersWS', - label: 'Users WebServices', - stableBranch: 'master', - branches: [ - { - name: 'master', - path: 'usersWS>master' - } - ] - } - ], - children: [] + path: 'publisherManagementWS>qa>/constraints/for_a_publisher', + relativePath: 'for_a_publisher', + name: 'for_a_publisher', + label: 'For a publisher', + description: 'for_a_publisher', + order: 1, }, { - id: '.01.01.03.', - hierarchy: '_eng_library_search', - slugName: 'search', - name: 'Search system', - childrenLabel: 'Projects', - childLabel: 'Project', - projects: [], - children: [] + path: 'publisherManagementWS>qa>/constraints/for_a_merchant', + relativePath: 'for_a_merchant', + name: 'for_a_merchant', + label: 'For a merchant', + description: 'for_a_merchant', + order: 3, + }, + { + path: 'publisherManagementWS>qa>/constraints/for_an_offer', + relativePath: 'for_an_offer', + name: 'for_an_offer', + label: 'For an offer', + description: 'for_an_offer', + order: 2, } - ] - } - ] -}; - -export const DIRECTORIES_SERVICE_RESPONSE: DirectoryApi = { - id: '11', - path: 'publisherManagementWS>qa>/constraints/', - name: 'constraints', - label: 'Constraints', - description: 'Filter offers provided to the publisher', - order: 0, - pages: [ - { - path: 'publisherManagementWS>qa>/constraints/overview', - relativePath: 'overview', - name: 'overview', - label: 'Overview', - description: 'overview', - order: 0, - }, - { - path: 'publisherManagementWS>qa>/constraints/for_a_publisher', - relativePath: 'for_a_publisher', - name: 'for_a_publisher', - label: 'For a publisher', - description: 'for_a_publisher', - order: 1, - }, - { - path: 'publisherManagementWS>qa>/constraints/for_a_merchant', - relativePath: 'for_a_merchant', - name: 'for_a_merchant', - label: 'For a merchant', - description: 'for_a_merchant', - order: 3, - }, - { - path: 'publisherManagementWS>qa>/constraints/for_an_offer', - relativePath: 'for_an_offer', - name: 'for_an_offer', - label: 'For an offer', - description: 'for_an_offer', - order: 2, - } - ] + ] }; export const PAGE_SERVICE_RESPONSE: PageApi = { - path: 'publisherManagementWS>qa>/constraints/overview', - relativePath: '/constraints/overview', - name: 'overview', - label: 'overview', - description: 'overview', - order: 0, - // tslint:disable-next-line:max-line-length - content: [ - { - type: 'markdown', - data: { - markdown: 'For various reasons, the offers provided to the publishers can be filtered. We don\'t necessary want to provide all the\n' + - 'offers to the publishers :\n' + - '\n' + - '![Overview](../assets/images/constraints_overview.png) \n' + - '\n' + - 'This is the constraints objective. The constraints can be defined manually by the **BizDevs** or automatically by the\n' + - '**TrafficOptimizer**.\n' + - '\n' + - '### Reference and application\n' + - '\n' + - 'The constraints are defined and stored in PMWS component. The impact of those constraints is implemented at the client\n' + - 'level:\n' + - '- **eCS** and **ShoppingAPI** are filtering the call to Search6\n' + - '- **FeedService** is filtering the offers provided (offers coming from OfferProcessing)\n' + - '- others systems are also using the constraints to filter offers provided to external clients : example GSA Exporter or\n' + - 'COP. \n' + - '\n' + - '\n' + - 'The constraints are stored against a profile (which is linked to a contract, which is linked to the publisher\n' + - 'itself ([See details](thegardener://${current.project}/${current.branch}/overview))). All trackings of a profile share\n' + - 'the same constraints.\n' + - '\n' + - 'Clients are most of the time using a tracking as input data to find out what are the constraints to be applied.\n' + - '\n' + - '### Different sources of constraints\n' + - '\n' + - 'There are several ways to define the constraints :\n' + - '- for a publisher, [filter the merchants that can provide offers](thegardener://${current.project}/${current.branch}/constraints/for_a_publisher). \n' + - '- for a merchant, [filter the publishers that can receive offers](thegardener://${current.project}/${current.branch}/constraints/for_a_merchant).\n' + - '- moreover, [offers can be filtered for various reasons](thegardener://${current.project}/${current.branch}/constraints/for_an_offer).\n' + - '\n' + - '![Sources](../assets/images/constraints_sources_overview.png)\n' + - '\n' + - 'All those filters are cumulative : **each offer need to pass through all the filters**. In other words it\'s a AND\n' + - 'between each constraint. \n' + - '\n' + - 'We can see the impact of [those constraints on the PMBO](thegardener://${current.project}/${current.branch}/constraints/from_pmbo). ' - } - } - ] + path: 'publisherManagementWS>qa>/constraints/overview', + relativePath: '/constraints/overview', + name: 'overview', + label: 'overview', + description: 'overview', + order: 0, + // tslint:disable-next-line:max-line-length + content: [ + { + type: 'markdown', + data: { + markdown: 'For various reasons, the offers provided to the publishers can be filtered. We don\'t necessary want to provide all the\n' + + 'offers to the publishers :\n' + + '\n' + + '![Overview](../assets/images/constraints_overview.png) \n' + + '\n' + + 'This is the constraints objective. The constraints can be defined manually by the **BizDevs** or automatically by the\n' + + '**TrafficOptimizer**.\n' + + '\n' + + '### Reference and application\n' + + '\n' + + 'The constraints are defined and stored in PMWS component. The impact of those constraints is implemented at the client\n' + + 'level:\n' + + '- **eCS** and **ShoppingAPI** are filtering the call to Search6\n' + + '- **FeedService** is filtering the offers provided (offers coming from OfferProcessing)\n' + + '- others systems are also using the constraints to filter offers provided to external clients : example GSA Exporter or\n' + + 'COP. \n' + + '\n' + + '\n' + + 'The constraints are stored against a profile (which is linked to a contract, which is linked to the publisher\n' + + 'itself ([See details](thegardener://${current.project}/${current.branch}/overview))). All trackings of a profile share\n' + + 'the same constraints.\n' + + '\n' + + 'Clients are most of the time using a tracking as input data to find out what are the constraints to be applied.\n' + + '\n' + + '### Different sources of constraints\n' + + '\n' + + 'There are several ways to define the constraints :\n' + + '- for a publisher, [filter the merchants that can provide offers](thegardener://${current.project}/${current.branch}/constraints/for_a_publisher). \n' + + '- for a merchant, [filter the publishers that can receive offers](thegardener://${current.project}/${current.branch}/constraints/for_a_merchant).\n' + + '- moreover, [offers can be filtered for various reasons](thegardener://${current.project}/${current.branch}/constraints/for_an_offer).\n' + + '\n' + + '![Sources](../assets/images/constraints_sources_overview.png)\n' + + '\n' + + 'All those filters are cumulative : **each offer need to pass through all the filters**. In other words it\'s a AND\n' + + 'between each constraint. \n' + + '\n' + + 'We can see the impact of [those constraints on the PMBO](thegardener://${current.project}/${current.branch}/constraints/from_pmbo). ' + } + } + ] }; export const PAGE_WITH_EXTERNAL_LINK_SERVICE_RESPONSE: PageApi = { - path: 'publisherManagementWS>qa>/constraints/overview', - relativePath: '/constraints/overview', - name: 'overview', - label: 'overview', - description: 'overview', - order: 0, - // tslint:disable-next-line:max-line-length - content: [{ - type: 'includeExternalPage', - data: { - includeExternalPage: 'http://publisher.corp.kelkoo.net/docs/#/Contact%20Management/getContact' - } - }] + path: 'publisherManagementWS>qa>/constraints/overview', + relativePath: '/constraints/overview', + name: 'overview', + label: 'overview', + description: 'overview', + order: 0, + // tslint:disable-next-line:max-line-length + content: [{ + type: 'includeExternalPage', + data: { + includeExternalPage: 'http://publisher.corp.kelkoo.net/docs/#/Contact%20Management/getContact' + } + }] }; export const PAGE_WITH_SCENARIO: PageApi = { - path: 'suggestionsWS>master>/context', - relativePath: '/context', - name: 'context', - label: 'The context', - description: 'Why providing suggestions', - order: 0, - content: [ - { - type: 'markdown', - data: { - markdown: '**Feature**: Provide book suggestions\n\n\n' - } - }, - { - type: 'scenarios', - data: { - scenarios: { - id: '692', - branchId: '44', - path: 'test/features/register_projects/register_a_project.feature', - background: { - id: '0', - keyword: 'Background', - name: '', - description: '', - steps: [ - { - id: '0', - keyword: 'Given', - text: 'the database is empty', - argument: [] - }, - { - id: '1', - keyword: 'And', - text: 'the cache is empty', - argument: [] - } - ] - }, - tags: [], - language: 'en', - keyword: 'Feature', - name: 'Register a project', - description: 'As a user,\n I want to register my project into theGardener\n So that my project BDD features will be shared with all users', - scenarios: [ - { - keyword: 'Scenario', - name: 'get a project', - description: '', - tags: [ - 'level_2_technical_details', - 'nominal_case', - 'valid' - ], - abstractionLevel: 'level_2_technical_details', - id: '4968', - caseType: 'nominal_case', - steps: [ - { - id: '0', - keyword: 'Given', - text: 'we have the following projects', - argument: [ - [ - 'id', - 'name', - 'repositoryUrl', - 'stableBranch', - 'featuresRootPath' + path: 'suggestionsWS>master>/context', + relativePath: '/context', + name: 'context', + label: 'The context', + description: 'Why providing suggestions', + order: 0, + content: [ + { + type: 'markdown', + data: { + markdown: '**Feature**: Provide book suggestions\n\n\n' + } + }, + { + type: 'scenarios', + data: { + scenarios: { + id: '692', + branchId: '44', + path: 'test/features/register_projects/register_a_project.feature', + background: { + id: '0', + keyword: 'Background', + name: '', + description: '', + steps: [ + { + id: '0', + keyword: 'Given', + text: 'the database is empty', + argument: [] + }, + { + id: '1', + keyword: 'And', + text: 'the cache is empty', + argument: [] + } + ] + }, + tags: [], + language: 'en', + keyword: 'Feature', + name: 'Register a project', + description: 'As a user,\n I want to register my project into theGardener\n So that my project BDD features will be shared with all users', + scenarios: [ + { + keyword: 'Scenario', + name: 'get a project', + description: '', + tags: [ + 'level_2_technical_details', + 'nominal_case', + 'valid' + ], + abstractionLevel: 'level_2_technical_details', + id: '4968', + caseType: 'nominal_case', + steps: [ + { + id: '0', + keyword: 'Given', + text: 'we have the following projects', + argument: [ + [ + 'id', + 'name', + 'repositoryUrl', + 'stableBranch', + 'featuresRootPath' + ], + [ + 'suggestionsWS', + 'Suggestions WebServices', + 'git@gitlab.corp.kelkoo.net:library/suggestionsWS.git', + 'master', + 'test/features' + ] + ] + }, + { + id: '1', + keyword: 'When', + text: 'I perform a \"GET\" on following URL \"/api/projects/suggestionsWS\"', + argument: [] + }, + { + id: '2', + keyword: 'Then', + text: 'I get a response with status \"200\"', + argument: [] + }, + { + id: '3', + keyword: 'And', + text: 'I get the following json response body', + argument: [ + [ + '{\n \"id\": \"suggestionsWS\",\n \"name\": \"Suggestions WebServices\",\n \"repositoryUrl\": \"git@gitlab.corp.kelkoo.net:library/suggestionsWS.git\",\n \"stableBranch\": \"master\",\n \"featuresRootPath\": \"test/features\"\n}' + ] + ] + } + ], + workflowStep: 'valid' + } ], - [ - 'suggestionsWS', - 'Suggestions WebServices', - 'git@gitlab.corp.kelkoo.net:library/suggestionsWS.git', - 'master', - 'test/features' - ] - ] - }, - { - id: '1', - keyword: 'When', - text: 'I perform a \"GET\" on following URL \"/api/projects/suggestionsWS\"', - argument: [] - }, - { - id: '2', - keyword: 'Then', - text: 'I get a response with status \"200\"', - argument: [] - }, - { - id: '3', - keyword: 'And', - text: 'I get the following json response body', - argument: [ - [ - '{\n \"id\": \"suggestionsWS\",\n \"name\": \"Suggestions WebServices\",\n \"repositoryUrl\": \"git@gitlab.corp.kelkoo.net:library/suggestionsWS.git\",\n \"stableBranch\": \"master\",\n \"featuresRootPath\": \"test/features\"\n}' - ] - ] + comments: [] } - ], - workflowStep: 'valid' } - ], - comments: [] + }, + { + type: 'markdown', + data: { + markdown: '\n**Footer**\n\n' + } } - } - }, - { - type: 'markdown', - data: { - markdown: '\n**Footer**\n\n' - } - } - ] + ] }; export const MENU_SERVICE_RESPONSE: HierarchyNodeApi = { - id: '.', - hierarchy: '_', - slugName: 'root', - name: 'Hierarchy root', - childrenLabel: 'Views', - childLabel: 'View', - projects: [], - children: [ - { - id: '.01.', - hierarchy: '_eng', - slugName: 'eng', - name: 'Engineering view', - childrenLabel: 'System groups', - childLabel: 'System group', - projects: [], - children: [ + id: '.', + hierarchy: '_', + slugName: 'root', + name: 'Hierarchy root', + childrenLabel: 'Views', + childLabel: 'View', + projects: [], + children: [ { - id: '.01.01.', - hierarchy: '_eng_library', - slugName: 'library', - name: 'Library system group', - childrenLabel: 'Systems', - childLabel: 'System', - projects: [], - children: [ - { - id: '.01.01.01.', - hierarchy: '_eng_library_suggestion', - slugName: 'suggestion', - name: 'Suggestion system', - childrenLabel: 'Projects', - childLabel: 'Project', - projects: [ - { - id: 'suggestionsReports', - path: 'suggestionsReports', - label: 'Suggestions Reports', - stableBranch: 'master', - branches: [ - { - name: 'master', - path: 'suggestionsReports>master' - } - ] - }, + id: '.01.', + hierarchy: '_eng', + slugName: 'eng', + name: 'Engineering view', + childrenLabel: 'System groups', + childLabel: 'System group', + projects: [], + children: [ { - id: 'suggestionsWS', - path: 'suggestionsWS', - label: 'Suggestions WebServices', - stableBranch: 'master', - branches: [ - { - name: 'master', - path: 'suggestionsWS>master', - rootDirectory: { - id: '1', - path: 'suggestionsWS>master>/', - name: 'root', - label: 'SuggestionsWS', - description: 'Suggestions WebServices', - order: 0, - pages: [ - 'context' - ], - children: [ - { - id: '2', - path: 'suggestionsWS>master>/suggestions/', - name: 'suggestions', - label: 'Suggestions', - description: 'Suggestions...', - order: 0, - pages: [ - 'suggestionsWS>master>/suggestions/suggestion', - 'suggestionsWS>master>/suggestions/examples' + id: '.01.01.', + hierarchy: '_eng_library', + slugName: 'library', + name: 'Library system group', + childrenLabel: 'Systems', + childLabel: 'System', + projects: [], + children: [ + { + id: '.01.01.01.', + hierarchy: '_eng_library_suggestion', + slugName: 'suggestion', + name: 'Suggestion system', + childrenLabel: 'Projects', + childLabel: 'Project', + projects: [ + { + id: 'suggestionsReports', + path: 'suggestionsReports', + label: 'Suggestions Reports', + stableBranch: 'master', + branches: [ + { + name: 'master', + path: 'suggestionsReports>master' + } + ] + }, + { + id: 'suggestionsWS', + path: 'suggestionsWS', + label: 'Suggestions WebServices', + stableBranch: 'master', + branches: [ + { + name: 'master', + path: 'suggestionsWS>master', + rootDirectory: { + id: '1', + path: 'suggestionsWS>master>/', + name: 'root', + label: 'SuggestionsWS', + description: 'Suggestions WebServices', + order: 0, + pages: [ + 'context' + ], + children: [ + { + id: '2', + path: 'suggestionsWS>master>/suggestions/', + name: 'suggestions', + label: 'Suggestions', + description: 'Suggestions...', + order: 0, + pages: [ + 'suggestionsWS>master>/suggestions/suggestion', + 'suggestionsWS>master>/suggestions/examples' + ], + children: [] + }, + { + id: '3', + path: 'suggestionsWS>master>/admin/', + name: 'admin', + label: 'Admin', + description: 'Administration...', + order: 1, + pages: [ + 'suggestionsWS>master>/admin/admin' + ], + children: [] + } + ] + } + }, + { + name: 'bugfix/351', + path: 'suggestionsWS>bugfix/351' + } + ] + } ], children: [] - }, - { - id: '3', - path: 'suggestionsWS>master>/admin/', - name: 'admin', - label: 'Admin', - description: 'Administration...', - order: 1, - pages: [ - 'suggestionsWS>master>/admin/admin' + }, + { + id: '.01.01.02.', + hierarchy: '_eng_library_user', + slugName: 'user', + name: 'User system', + childrenLabel: 'Projects', + childLabel: 'Project', + projects: [ + { + id: 'usersWS', + path: 'usersWS', + label: 'Users WebServices', + stableBranch: 'master', + branches: [ + { + name: 'master', + path: 'usersWS>master' + } + ] + } ], children: [] - } - ] - } - }, - { - name: 'bugfix/351', - path: 'suggestionsWS>bugfix/351' - } - ] + }, + { + id: '.01.01.03.', + hierarchy: '_eng_library_search', + slugName: 'search', + name: 'Search system', + childrenLabel: 'Projects', + childLabel: 'Project', + projects: [], + children: [] + } + ] } - ], - children: [] - }, - { - id: '.01.01.02.', - hierarchy: '_eng_library_user', - slugName: 'user', - name: 'User system', - childrenLabel: 'Projects', - childLabel: 'Project', - projects: [ + ] + }, + { + id: '.02.', + hierarchy: '_biz', + slugName: 'biz', + name: 'Business view', + childrenLabel: 'Units', + childLabel: 'Unit', + projects: [ { - id: 'usersWS', - path: 'usersWS', - label: 'Users WebServices', - stableBranch: 'master', - branches: [ - { - name: 'master', - path: 'usersWS>master' - } - ] + id: 'suggestionsWS', + path: 'suggestionsWS', + label: 'Suggestions WebServices', + stableBranch: 'master', + branches: [ + { + name: 'master', + path: 'suggestionsWS>master' + }, + { + name: 'bugfix/351', + path: 'suggestionsWS>bugfix/351' + } + ] } - ], - children: [] - }, - { - id: '.01.01.03.', - hierarchy: '_eng_library_search', - slugName: 'search', - name: 'Search system', - childrenLabel: 'Projects', - childLabel: 'Project', - projects: [], - children: [] - } - ] + ], + children: [] } - ] - }, - { - id: '.02.', - hierarchy: '_biz', - slugName: 'biz', - name: 'Business view', - childrenLabel: 'Units', - childLabel: 'Unit', - projects: [ + ] +}; + +export const MARKDOWN_WITH_ESCAPED_MARKDOWN: PageApi = { + path: 'theGardener>master>/guides/write', + relativePath: '/guides/write', + name: 'write', + label: 'write', + description: 'write', + order: 3, + content: [ { - id: 'suggestionsWS', - path: 'suggestionsWS', - label: 'Suggestions WebServices', - stableBranch: 'master', - branches: [ - { - name: 'master', - path: 'suggestionsWS>master' - }, - { - name: 'bugfix/351', - path: 'suggestionsWS>bugfix/351' + type: 'markdown', + data: { + markdown: '**Markdown containing escaped Markdown\n' + + '````\n' + + '```thegardener\n' + + '{\n' + + ' "page" :\n' + + ' {\n' + + ' "label": "Write documentation",\n' + + ' "description": "How to write documentation with theGardener format ?"\n' + + ' }\n' + + '}\n' + + '```\n' + + '````' } - ] } - ], - children: [] - } - ] + ] }; -export const MARKDOWN_WITH_ESCAPED_MARKDOWN: PageApi = { - path: 'theGardener>master>/guides/write', - relativePath: '/guides/write', - name: 'write', - label: 'write', - description: 'write', - order: 3, - content: [ - { - type: 'markdown', - data: { - markdown: '**Markdown containing escaped Markdown\n' + - '````\n' + - '```thegardener\n' + - '{\n' + - ' "page" :\n' + - ' {\n' + - ' "label": "Write documentation",\n' + - ' "description": "How to write documentation with theGardener format ?"\n' + - ' }\n' + - '}\n' + - '```\n' + - '````' - } - } - ] +export const SEARCH_RESULTS: SearchResult ={ + "items": [ + { + "page": { + "id": "/suggestion/suggestionsWS>master>/context", + "hierarchy": "/suggestion", + "path": "suggestionsWS>master>/context", + "breadcrumb": " Suggestion system / Suggestions WebServices / The context", + "project": "Suggestions WebServices", + "branch": "master", + "label": "The context", + "description": "Why providing suggestions", + "pageContent": "**Feature**: Provide book suggestions" + }, + "highlights": [ + { + "fragment": " Suggestion system / Suggestions WebServices / The context", + "word": "context" + }, + { + "fragment": "The context", + "word": "context" + } + ] + } + ] }; - diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index a94ee6697..4f7814cf9 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -1,6 +1,7 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; import {NavigatePageComponent} from './output/navigate/navigate-page.component'; +import {SearchPageComponent} from "./output/search/search-page/search-page.component"; const routes: Routes = [ { @@ -39,7 +40,12 @@ const routes: Routes = [ path: 'app/documentation/navigate/:nodes/:project/:branch/:directories/:page', component: NavigatePageComponent, pathMatch: 'full', - } + }, + { + path: 'app/documentation/search', + component: SearchPageComponent, + pathMatch: 'full', + }, ]; @NgModule({ diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 91a85edce..9901b5890 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -49,6 +49,10 @@ import {AnchorPipe} from './anchor.pipe'; import {OpenApiModelComponent} from './output/page-content/open-api-model/open-api-model.component'; import { OpenApiEndPointsComponent } from './output/page-content/open-api-end-points/open-api-end-points.component'; import { NavigateMobileMenuComponent } from './output/navigate/navigate-mobile-menu/navigate-mobile-menu.component'; +import { SearchPageComponent } from './output/search/search-page/search-page.component'; +import { SearchResultsComponent } from './output/search/search-results/search-results.component'; +import { SearchResultItemComponent } from './output/search/search-result-item/search-result-item.component'; +import { SearchQueryComponent } from './output/search/search-query/search-query.component'; const nonProductionProviders = [{ provide: HTTP_INTERCEPTORS, @@ -78,6 +82,10 @@ const nonProductionProviders = [{ OpenApiModelComponent, OpenApiEndPointsComponent, NavigateMobileMenuComponent, + SearchPageComponent, + SearchResultsComponent, + SearchResultItemComponent, + SearchQueryComponent, ], imports: [ HttpClientModule, diff --git a/frontend/src/app/output/search/search-page/search-page.component.html b/frontend/src/app/output/search/search-page/search-page.component.html new file mode 100644 index 000000000..384da39fe --- /dev/null +++ b/frontend/src/app/output/search/search-page/search-page.component.html @@ -0,0 +1,9 @@ +
+
+

Search results for

+

'{{keyword}}'

+
+
+ +
+
diff --git a/frontend/src/app/output/search/search-page/search-page.component.scss b/frontend/src/app/output/search/search-page/search-page.component.scss new file mode 100644 index 000000000..c72f4a107 --- /dev/null +++ b/frontend/src/app/output/search/search-page/search-page.component.scss @@ -0,0 +1,31 @@ +@import "backoffice"; + +.page { + display: grid; + grid-template-columns: minmax(300px, 20%) 1fr; + grid-template-areas: "sidebar content"; + width: 100%; + height: 100%; + + @media screen and (max-width: 600px) { + display: grid; + grid-template-columns: 1fr; + grid-template-areas: "content"; + } +} + +.content { + grid-area: content; + overflow: auto; + margin-top: 3em; + margin-left: 2em; + + @media screen and (max-width: 600px) { + grid-column: 1 / span 2; + } +} + +.title { + margin-left: 1em; +} + diff --git a/frontend/src/app/output/search/search-page/search-page.component.ts b/frontend/src/app/output/search/search-page/search-page.component.ts new file mode 100644 index 000000000..001e554b6 --- /dev/null +++ b/frontend/src/app/output/search/search-page/search-page.component.ts @@ -0,0 +1,34 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {ActivatedRoute} from "@angular/router"; +import {PageService} from "../../../_services/page.service"; +import {SearchResult} from "../../../_models/search"; +import {NotificationService} from "../../../_services/notification.service"; + +@Component({ + selector: 'app-search-page', + templateUrl: './search-page.component.html', + styleUrls: ['./search-page.component.scss'] +}) +export class SearchPageComponent implements OnInit { + + @Input() keyword: string; + searchResult: SearchResult; + + constructor( private activatedRoute: ActivatedRoute, private notificationService: NotificationService, private pageService: PageService) { } + + ngOnInit(): void { + this.activatedRoute.queryParams.subscribe(queryParams => { + this.keyword = queryParams.keyword + this.search(); + }); + } + + private search() { + this.pageService.searchPages(this.keyword).subscribe( + result => { + this.searchResult = result; + }, error => { + this.notificationService.showError('Error while searching in the page index', error); + }); + } +} diff --git a/frontend/src/app/output/search/search-query/search-query.component.html b/frontend/src/app/output/search/search-query/search-query.component.html new file mode 100644 index 000000000..dc9eff539 --- /dev/null +++ b/frontend/src/app/output/search/search-query/search-query.component.html @@ -0,0 +1,4 @@ +
+ + +
diff --git a/frontend/src/app/output/search/search-query/search-query.component.scss b/frontend/src/app/output/search/search-query/search-query.component.scss new file mode 100644 index 000000000..2c4e39f2c --- /dev/null +++ b/frontend/src/app/output/search/search-query/search-query.component.scss @@ -0,0 +1,16 @@ +@import "../../../../stylesheets/backoffice"; + +.searchContainer { + display: inline-flex; + flex: 1 1 300px; + position: relative; + border: 1px solid $color-border-lighter; + border-radius: 5px; + overflow: hidden; +} + +.searchIcon { + padding: 0.5rem; +} + + diff --git a/frontend/src/app/output/search/search-query/search-query.component.ts b/frontend/src/app/output/search/search-query/search-query.component.ts new file mode 100644 index 000000000..f4a9e3428 --- /dev/null +++ b/frontend/src/app/output/search/search-query/search-query.component.ts @@ -0,0 +1,30 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {ActivatedRoute, Router} from "@angular/router"; +import {SEARCH_PATH} from "../../../_services/route.service"; + +@Component({ + selector: 'app-search-query', + templateUrl: './search-query.component.html', + styleUrls: ['./search-query.component.scss'] +}) +export class SearchQueryComponent implements OnInit { + + @Input() keyword: string + + constructor(private activatedRoute: ActivatedRoute, private router: Router) { + } + + ngOnInit(): void { + if (this.activatedRoute.queryParams) { + this.activatedRoute.queryParams.subscribe(queryParams => { + this.keyword = queryParams.keyword + }); + } + } + + search() { + if (this.keyword != "") { + this.router.navigateByUrl(SEARCH_PATH + `?keyword=${this.keyword.trim()}`) + } + } +} diff --git a/frontend/src/app/output/search/search-result-item/search-result-item.component.html b/frontend/src/app/output/search/search-result-item/search-result-item.component.html new file mode 100644 index 000000000..270f7c019 --- /dev/null +++ b/frontend/src/app/output/search/search-result-item/search-result-item.component.html @@ -0,0 +1,4 @@ +{{ showIndex() }} - {{ item.page.breadcrumb }} +

+ "... ..." +

diff --git a/frontend/src/app/output/search/search-result-item/search-result-item.component.scss b/frontend/src/app/output/search/search-result-item/search-result-item.component.scss new file mode 100644 index 000000000..dfa8552ed --- /dev/null +++ b/frontend/src/app/output/search/search-result-item/search-result-item.component.scss @@ -0,0 +1,6 @@ +@import "backoffice"; + +::ng-deep em{ + font-weight: bold; + color: $color-selected-item; +} diff --git a/frontend/src/app/output/search/search-result-item/search-result-item.component.ts b/frontend/src/app/output/search/search-result-item/search-result-item.component.ts new file mode 100644 index 000000000..0230f330e --- /dev/null +++ b/frontend/src/app/output/search/search-result-item/search-result-item.component.ts @@ -0,0 +1,31 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {PageIndexDocument, SearchResultItem} from "../../../_models/search"; +import {NAVIGATE_PATH, RouteService} from "../../../_services/route.service"; +import {Router} from "@angular/router"; + +@Component({ + selector: 'app-search-result-item', + templateUrl: './search-result-item.component.html', + styleUrls: ['./search-result-item.component.scss'] +}) +export class SearchResultItemComponent implements OnInit { + + @Input() item: SearchResultItem; + @Input() index: number; + + + constructor(private routeService: RouteService,private router: Router) { } + + ngOnInit(): void { + } + + navigateTo(page: PageIndexDocument){ + let path = this.routeService.backEndHierarchyAndPathToFrontEndPath(page.hierarchy,page.path); + this.router.navigateByUrl( NAVIGATE_PATH + path); + } + + showIndex(): string { + return (this.index + 1).toString() + } + +} diff --git a/frontend/src/app/output/search/search-results/search-results.component.html b/frontend/src/app/output/search/search-results/search-results.component.html new file mode 100644 index 000000000..1b18a89a6 --- /dev/null +++ b/frontend/src/app/output/search/search-results/search-results.component.html @@ -0,0 +1,10 @@ + +
+
+ +
+
+ + + No results + diff --git a/frontend/src/app/output/search/search-results/search-results.component.scss b/frontend/src/app/output/search/search-results/search-results.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/output/search/search-results/search-results.component.ts b/frontend/src/app/output/search/search-results/search-results.component.ts new file mode 100644 index 000000000..71fd17a20 --- /dev/null +++ b/frontend/src/app/output/search/search-results/search-results.component.ts @@ -0,0 +1,14 @@ +import {Component, Input} from '@angular/core'; +import {SearchResult} from "../../../_models/search"; + +@Component({ + selector: 'app-search-results', + templateUrl: './search-results.component.html', + styleUrls: ['./search-results.component.scss'] +}) +export class SearchResultsComponent { + + @Input() searchResult: SearchResult; + + +} diff --git a/local-conf/application.dev.conf b/local-conf/application.dev.conf index 68690a609..79104cc6a 100644 --- a/local-conf/application.dev.conf +++ b/local-conf/application.dev.conf @@ -5,8 +5,6 @@ application.title = "In documentation we trust." application.logoSrc = "assets/images/logo-white.png" -projects.synchronize.from.remote.enabled=false - db.default.driver=com.mysql.jdbc.Driver db.default.url="jdbc:mysql://dc1-thegardener-dev-srv-01.dev.dc1.kelkoo.net:3306/thegardener" db.default.username=root diff --git a/pom.xml b/pom.xml deleted file mode 100644 index af9483bd0..000000000 --- a/pom.xml +++ /dev/null @@ -1,168 +0,0 @@ - - - - - 4.0.0 - com.kelkoo.gardener - theGardener - 1.3.3-SNAPSHOT - kelkoo-rpm - - theGardener - Provide a workflow so that Product Owner and Developers can write and iterate on a feature with several scenarios - - - com.kelkoo - kelkooMavenParent - 2.6.1 - - - - - scm:git|git@gitlab.corp.kelkoo.net:syndication/theGardener.git - http://gitlab.corp.kelkoo.net/syndication/theGardener - HEAD - - - - - - - org.codehaus.mojo - exec-maven-plugin - - - sbt clean - clean - - exec - - - sbt - - clean - -batch - - - - - sbt compile - compile - - exec - - - sbt - - compile - -batch - - - - - prepare-play-dist - prepare-package - - exec - - - sbt - - stage - -batch - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - org.apache.maven.plugins - maven-compiler-plugin - - true - - - - com.kelkoo.maven.plugins - kelkoo-rpm-maven-plugin - - play - - - - - - - - - - org.codehaus.mojo - exec-maven-plugin - 1.4.0 - - - - - - - - - - sbt-tests - - - !skipTests - - - - - - org.codehaus.mojo - exec-maven-plugin - - - sbt test - test - - exec - - - sbt - - test - - - - - - - - - - - - - - - com.kelkoo.common - kelkooPlayScripts - 1.3.6 - tar.gz - kelkoo - runtime - - - - - 1.8 - - - diff --git a/test/OnGoingBDDTest.scala b/test/OnGoingBDDTest.scala index dbbab22c8..16a2f9836 100644 --- a/test/OnGoingBDDTest.scala +++ b/test/OnGoingBDDTest.scala @@ -1,9 +1,7 @@ import io.cucumber.junit.{Cucumber, CucumberOptions} -import org.junit._ import org.junit.runner._ @RunWith(classOf[Cucumber]) -@Ignore @CucumberOptions( features = Array("test/features"), glue = Array("steps"), diff --git a/test/features/navigation/provide_menu.feature b/test/features/navigation/provide_menu.feature index 04cd41f9d..840efe750 100644 --- a/test/features/navigation/provide_menu.feature +++ b/test/features/navigation/provide_menu.feature @@ -807,7 +807,7 @@ Feature: Provide menu } """ - @level_2_technical_details @nominal_case @valid @ongoing + @level_2_technical_details @nominal_case @valid Scenario: provide sub menu with shortcuts Given the hierarchy nodes are | id | slugName | name | childrenLabel | childLabel | directoryPath | shortcut | diff --git a/test/features/search/search_pages.feature b/test/features/search/search_pages.feature new file mode 100644 index 000000000..ef87b45cc --- /dev/null +++ b/test/features/search/search_pages.feature @@ -0,0 +1,297 @@ +@ongoing +Feature: Full text search pages + + Background: + Given the database is empty + And the cache is empty + And No project is checkout + And the remote projects are empty + And the hierarchy nodes are + | id | slugName | name | childrenLabel | childLabel | directoryPath | + | . | root | Hierarchy root | Views | View | | + | .01. | suggestion | Suggestion system | Projects | Project | | + | .02. | lib | Library | Projects | Project | libraryDoc>master>/ | + And we have the following projects + | id | name | repositoryUrl | stableBranch | featuresRootPath | documentationRootPath | + | suggestionsWS | Suggestions WebServices | target/remote/data/GetFeatures/library/suggestionsWS/ | master | test/features | doc | + | libraryDoc | library system group documentation | target/remote/data/GetFeatures/library/libraryDoc/ | master | test/features | doc | + And the links between hierarchy nodes are + | projectId | hierarchyId | + | suggestionsWS | .01. | + And we have those branches in the database + | id | name | isStable | projectId | + | 1 | master | true | suggestionsWS | + | 2 | feature/654-simple-full-text-search-on-pages | false | suggestionsWS | + | 3 | master | true | libraryDoc | + + @level_2_technical_details @nominal_case @valid + Scenario: get result of a search + Given we have those directories in the database + | id | name | label | description | order | relativePath | path | branchId | + | 1 | root | SuggestionsWS | Suggestions WebServices | 0 | / | suggestionsWS>master>/ | 1 | + | 2 | suggestions | Suggestions | Suggestions... | 0 | /suggestions/ | suggestionsWS>master>/suggestions/ | 1 | + | 3 | admin | Admin | Administration... | 1 | /admin/ | suggestionsWS>master>/admin/ | 1 | + And we have those pages in the database + | id | name | label | description | order | relativePath | path | markdown | directoryId | + | 1 | context | The context | Why providing suggestions | 0 | /context | suggestionsWS>master>/context | **Feature**: Provide book suggestions | 1 | + | 2 | suggestion | The suggestions | The suggestions... | 0 | /suggestions/suggestion | suggestionsWS>master>/suggestions/suggestion | **What's a suggestion ?** | 2 | + | 3 | examples | examples | Some examples | 1 | /suggestions/examples | suggestionsWS>master>/suggestions/examples | **Some suggestion examples** | 2 | + | 4 | admin | admin | admin | 0 | /admin/admin | suggestionsWS>master>/admin/admin | **Page for the admin users** | 3 | + And the lucene index is loaded from the database + And I perform a "GET" on following URL "/api/pages/search?keyword=context" + Then I get a response with status "200" + Then I get the following json response body + """ +{ + "items": [ + { + "page": { + "id": "/suggestion/suggestionsWS>master>/context", + "hierarchy": "/suggestion", + "path": "suggestionsWS>master>/context", + "breadcrumb": "Suggestion system / Suggestions WebServices / The context", + "project": "Suggestions WebServices", + "branch": "master", + "label": "The context", + "description": "Why providing suggestions", + "pageContent": "**Feature**: Provide book suggestions" + }, + "highlights": [ + { + "fragment": "Suggestion system / Suggestions WebServices / The context", + "word": "context" + }, + { + "fragment": "The context", + "word": "context" + } + ] + } + ] +} +""" + + @level_2_technical_details @nominal_case @valid + Scenario: get result of a search with multiple results + Given we have those directories in the database + | id | name | label | description | order | relativePath | path | branchId | + | 1 | root | SuggestionsWS | Suggestions WebServices | 0 | / | suggestionsWS>master>/ | 1 | + | 2 | suggestions | Suggestions | Suggestions... | 0 | /suggestions/ | suggestionsWS>master>/suggestions/ | 1 | + | 3 | admin | Admin | Administration... | 1 | /admin/ | suggestionsWS>master>/admin/ | 1 | + | 4 | root | SuggestionsWS | Suggestions WebServices | 0 | / | suggestionsWS>master>/ | 2 | + And we have those pages in the database + | id | name | label | description | order | relativePath | path | markdown | directoryId | + | 1 | context | The context | Why providing suggestions | 0 | /context | suggestionsWS>master>/context | **Feature**: Provide book suggestions | 1 | + | 2 | suggestion | The suggestions | The suggestions... | 0 | /suggestions/suggestion | suggestionsWS>master>/suggestions/suggestion | **What's a suggestion ?** | 2 | + | 3 | examples | examples | Some examples | 1 | /suggestions/examples | suggestionsWS>master>/suggestions/examples | **Some suggestion examples** | 2 | + | 4 | admin | admin | admin | 0 | /admin/admin | suggestionsWS>master>/admin/admin | **Page for the admin users** | 3 | + | 5 | context | The context | Why providing suggestions | 0 | /context | suggestionsWS>feature/654-simple-full-text-search-on-pages>/context | **Feature**: Provide book suggestions context | 4 | + And the lucene index is loaded from the database + When I perform a "GET" on following URL "/api/pages/search?keyword=suggestions" + Then I get a response with status "200" + Then I get the following json response body + """ +{ + "items": [ + { + "page": { + "id": "/suggestion/suggestionsWS>master>/suggestions/suggestion", + "hierarchy": "/suggestion", + "path": "suggestionsWS>master>/suggestions/suggestion", + "breadcrumb": "Suggestion system / Suggestions WebServices / suggestions / The suggestions", + "project": "Suggestions WebServices", + "branch": "master", + "label": "The suggestions", + "description": "The suggestions...", + "pageContent": "**What's a suggestion ?**" + }, + "highlights": [ + { + "fragment": "Suggestion system / Suggestions WebServices / suggestions / The suggestions", + "word": "Suggestions" + }, + { + "fragment": "The suggestions", + "word": "suggestions" + }, + { + "fragment": "The suggestions...", + "word": "suggestions" + } + ] + }, + { + "page": { + "id": "/suggestion/suggestionsWS>master>/context", + "hierarchy": "/suggestion", + "path": "suggestionsWS>master>/context", + "breadcrumb": "Suggestion system / Suggestions WebServices / The context", + "project": "Suggestions WebServices", + "branch": "master", + "label": "The context", + "description": "Why providing suggestions", + "pageContent": "**Feature**: Provide book suggestions" + }, + "highlights": [ + { + "fragment": "Suggestion system / Suggestions WebServices / The context", + "word": "Suggestions" + }, + { + "fragment": "Why providing suggestions", + "word": "suggestions" + }, + { + "fragment": "**Feature**: Provide book suggestions", + "word": "suggestions" + } + ] + }, + { + "page": { + "id": "/suggestion/suggestionsWS>master>/suggestions/examples", + "hierarchy": "/suggestion", + "path": "suggestionsWS>master>/suggestions/examples", + "breadcrumb": "Suggestion system / Suggestions WebServices / suggestions / examples", + "project": "Suggestions WebServices", + "branch": "master", + "label": "examples", + "description": "Some examples", + "pageContent": "**Some suggestion examples**" + }, + "highlights": [ + { + "fragment": "Suggestion system / Suggestions WebServices / suggestions / examples", + "word": "Suggestions" + } + ] + }, + { + "page": { + "id": "/suggestion/suggestionsWS>master>/admin/admin", + "hierarchy": "/suggestion", + "path": "suggestionsWS>master>/admin/admin", + "breadcrumb": "Suggestion system / Suggestions WebServices / admin / admin", + "project": "Suggestions WebServices", + "branch": "master", + "label": "admin", + "description": "admin", + "pageContent": "**Page for the admin users**" + }, + "highlights": [ + { + "fragment": "Suggestion system / Suggestions WebServices / admin / admin", + "word": "Suggestions" + } + ] + } + ] +} +""" + + @level_2_technical_details @nominal_case @valid + Scenario: only pages from stable branches are indexed in the search + Given we have those directories in the database + | id | name | label | description | order | relativePath | path | branchId | + | 1 | root | SuggestionsWS | Suggestions WebServices | 0 | / | suggestionsWS>master>/ | 1 | + | 2 | suggestions | Suggestions | Suggestions... | 0 | /suggestions/ | suggestionsWS>master>/suggestions/ | 1 | + | 3 | admin | Admin | Administration... | 1 | /admin/ | suggestionsWS>master>/admin/ | 1 | + | 4 | root | SuggestionsWS | Suggestions WebServices | 0 | / | suggestionsWS>master>/ | 2 | + And we have those pages in the database + | id | name | label | description | order | relativePath | path | markdown | directoryId | + | 1 | context | The context | Why providing suggestions | 0 | /context | suggestionsWS>master>/context | **Feature**: Provide book suggestions | 1 | + | 2 | suggestion | The suggestions | The suggestions... | 0 | /suggestions/suggestion | suggestionsWS>master>/suggestions/suggestion | **What's a suggestion ?** | 2 | + | 3 | examples | examples | Some examples | 1 | /suggestions/examples | suggestionsWS>master>/suggestions/examples | **Some suggestion examples** | 2 | + | 4 | admin | admin | admin | 0 | /admin/admin | suggestionsWS>master>/admin/admin | **Page for the admin users** | 3 | + | 5 | context | The context | Why providing suggestions | 0 | /context | suggestionsWS>feature/654-simple-full-text-search-on-pages>/context | **Feature**: Provide book suggestions context | 4 | + And the lucene index is loaded from the database + When I perform a "GET" on following URL "/api/pages/search?keyword=context" + Then I get a response with status "200" + Then I get the following json response body + """ +{ + "items": [ + { + "page": { + "id": "/suggestion/suggestionsWS>master>/context", + "hierarchy": "/suggestion", + "path": "suggestionsWS>master>/context", + "breadcrumb": "Suggestion system / Suggestions WebServices / The context", + "project": "Suggestions WebServices", + "branch": "master", + "label": "The context", + "description": "Why providing suggestions", + "pageContent": "**Feature**: Provide book suggestions" + }, + "highlights": [ + { + "fragment": "Suggestion system / Suggestions WebServices / The context", + "word": "context" + }, + { + "fragment": "The context", + "word": "context" + } + ] + } + ] +} +""" + + @level_2_technical_details @nominal_case @valid @ongoing + Scenario: get result of a search with pages only attached to the hierarchy by the node.directoryPath + Given we have those directories in the database + | id | name | label | description | order | relativePath | path | branchId | + | 1 | root | SuggestionsWS | Suggestions WebServices | 0 | / | suggestionsWS>master>/ | 1 | + | 2 | suggestions | Suggestions | Suggestions... | 0 | /suggestions/ | suggestionsWS>master>/suggestions/ | 1 | + | 3 | admin | Admin | Administration... | 1 | /admin/ | suggestionsWS>master>/admin/ | 1 | + | 4 | libraryDoc | libraryDoc | library system group documentation | 0 | / | libraryDoc>master>/ | 3 | + And we have those pages in the database + | id | name | label | description | order | relativePath | path | markdown | directoryId | + | 1 | context | The context | Why providing suggestions | 0 | /context | suggestionsWS>master>/context | **Feature**: Provide book suggestions | 1 | + | 2 | suggestion | The suggestions | The suggestions... | 0 | /suggestions/suggestion | suggestionsWS>master>/suggestions/suggestion | **What's a suggestion ?** | 2 | + | 3 | examples | examples | Some examples | 1 | /suggestions/examples | suggestionsWS>master>/suggestions/examples | **Some suggestion examples** | 2 | + | 4 | admin | admin | admin | 0 | /admin/admin | suggestionsWS>master>/admin/admin | **Page for the admin users** | 3 | + | 5 | libDocOverview | Overview library | Overview library documentation | 0 | /overview | libraryDoc>master>/overview | **Documentation on the library** | 4 | + And the lucene index is loaded from the database + And I perform a "GET" on following URL "/api/pages/search?keyword=library" + Then I get a response with status "200" + Then I get the following json response body + """ +{ + "items": [ + { + "page": { + "id": "/lib/libraryDoc>master>/overview", + "hierarchy": "/lib", + "path": "libraryDoc>master>/overview", + "breadcrumb": "Library / library system group documentation / Overview library", + "project": "library system group documentation", + "branch": "master", + "label": "Overview library", + "description": "Overview library documentation", + "pageContent": "**Documentation on the library**" + }, + "highlights": [ + { + "fragment": "Library / library system group documentation / Overview library", + "word": "Library" + }, + { + "fragment": "Overview library", + "word": "library" + }, + { + "fragment": "Overview library documentation", + "word": "library" + }, + { + "fragment": "**Documentation on the library**", + "word": "library" + } + ] + } + ] +} + """ + diff --git a/test/features/search/search_through_name_pages.feature b/test/features/search/search_through_name_pages.feature deleted file mode 100644 index b890b26fa..000000000 --- a/test/features/search/search_through_name_pages.feature +++ /dev/null @@ -1,142 +0,0 @@ -Feature: search through pages by name - As a user, - I want to be able to search a page by its name to find more easily what i need - - Background: - Given the database is empty - And the cache is empty - And No project is checkout - And the remote projects are empty - And the hierarchy nodes are - | id | slugName | name | childrenLabel | childLabel | - | . | root | Hierarchy root | Views | View | - | .01. | suggestion | Suggestion system | Projects | Project | - And we have the following projects - | id | name | repositoryUrl | stableBranch | featuresRootPath | documentationRootPath | - | suggestionsWS | Suggestions WebServices | target/remote/data/GetFeatures/library/suggestionsWS/ | master | test/features | doc | - And the links between hierarchy nodes are - | projectId | hierarchyId | - | suggestionsWS | .01. | - And we have those branches in the database - | id | name | isStable | projectId | - | 1 | master | true | suggestionsWS | - | 2 | feature/654-simple-full-text-search-on-pages | false | suggestionsWS | - - @level_2_technical_details @nominal_case @valid - Scenario: get result of a search - Given we have those directories in the database - | id | name | label | description | order | relativePath | path | branchId | - | 1 | root | SuggestionsWS | Suggestions WebServices | 0 | / | suggestionsWS>master>/ | 1 | - | 2 | suggestions | Suggestions | Suggestions... | 0 | /suggestions/ | suggestionsWS>master>/suggestions/ | 1 | - | 3 | admin | Admin | Administration... | 1 | /admin/ | suggestionsWS>master>/admin/ | 1 | - And we have those pages in the database - | id | name | label | description | order | relativePath | path | markdown | directoryId | - | 1 | context | The context | Why providing suggestions | 0 | /context | suggestionsWS>master>/context | **Feature**: Provide book suggestions | 1 | - | 2 | suggestion | The suggestions | The suggestions... | 0 | /suggestions/suggestion | suggestionsWS>master>/suggestions/suggestion | **What's a suggestion ?** | 2 | - | 3 | examples | examples | Some examples | 1 | /suggestions/examples | suggestionsWS>master>/suggestions/examples | **Some suggestion examples** | 2 | - | 4 | admin | admin | admin | 0 | /admin/admin | suggestionsWS>master>/admin/admin | **Page for the admin users** | 3 | - And the lucene index is loaded from the database - And I perform a "GET" on following URL "/api/pages/search?keywords=context" - Then I get the following json response body - """ -[ - { - "hierarchy":"/Suggestion system/master/context", - "path":"suggestionsWS>master>/context", - "branch":"master", - "label":"The context", - "description":"Why providing suggestions", - "pageContent":"**Feature**: Provide book suggestions" - } -] - - """ - - @level_2_technical_details @nominal_case @valid - Scenario: get result of a search with multiple results - Given we have those directories in the database - | id | name | label | description | order | relativePath | path | branchId | - | 1 | root | SuggestionsWS | Suggestions WebServices | 0 | / | suggestionsWS>master>/ | 1 | - | 2 | suggestions | Suggestions | Suggestions... | 0 | /suggestions/ | suggestionsWS>master>/suggestions/ | 1 | - | 3 | admin | Admin | Administration... | 1 | /admin/ | suggestionsWS>master>/admin/ | 1 | - | 4 | root | SuggestionsWS | Suggestions WebServices | 0 | / | suggestionsWS>master>/ | 2 | - And we have those pages in the database - | id | name | label | description | order | relativePath | path | markdown | directoryId | - | 1 | context | The context | Why providing suggestions | 0 | /context | suggestionsWS>master>/context | **Feature**: Provide book suggestions | 1 | - | 2 | suggestion | The suggestions | The suggestions... | 0 | /suggestions/suggestion | suggestionsWS>master>/suggestions/suggestion | **What's a suggestion ?** | 2 | - | 3 | examples | examples | Some examples | 1 | /suggestions/examples | suggestionsWS>master>/suggestions/examples | **Some suggestion examples** | 2 | - | 4 | admin | admin | admin | 0 | /admin/admin | suggestionsWS>master>/admin/admin | **Page for the admin users** | 3 | - | 5 | context | The context | Why providing suggestions | 0 | /context | suggestionsWS>feature/654-simple-full-text-search-on-pages>/context | **Feature**: Provide book suggestions context | 4 | - And the lucene index is loaded from the database - When I perform a "GET" on following URL "/api/pages/search?keywords=context" - Then I get the following json response body - """ -[ - { - "hierarchy":"/Suggestion system/feature/654-simple-full-text-search-on-pages/context", - "path":"suggestionsWS>feature/654-simple-full-text-search-on-pages>/context", - "branch":"feature/654-simple-full-text-search-on-pages", - "label":"The context", - "description":"Why providing suggestions", - "pageContent":"**Feature**: Provide book suggestions context" - }, - { - "hierarchy":"/Suggestion system/master/context", - "path":"suggestionsWS>master>/context", - "branch":"master", - "label":"The context", - "description":"Why providing suggestions", - "pageContent":"**Feature**: Provide book suggestions" - } -] -""" - - @level_2_technical_details @nominal_case @valid - Scenario: get result of a search with multiple results - Given we have those directories in the database - | id | name | label | description | order | relativePath | path | branchId | - | 1 | root | SuggestionsWS | Suggestions WebServices | 0 | / | suggestionsWS>master>/ | 1 | - | 2 | suggestions | Suggestions | Suggestions... | 0 | /suggestions/ | suggestionsWS>master>/suggestions/ | 1 | - | 3 | admin | Admin | Administration... | 1 | /admin/ | suggestionsWS>master>/admin/ | 1 | - And we have those pages in the database - | id | name | label | description | order | relativePath | path | markdown | directoryId | - | 1 | context | The context | Why providing suggestions | 0 | /context | suggestionsWS>master>/context | **Feature**: Provide book suggestions | 1 | - | 2 | suggestion | The suggestions | The suggestions... | 0 | /suggestions/suggestion | suggestionsWS>master>/suggestions/suggestion | **What's a suggestion ?** | 2 | - | 3 | examples | examples | Some examples | 1 | /suggestions/examples | suggestionsWS>master>/suggestions/examples | **Some suggestion examples** | 2 | - | 4 | admin | admin | admin | 0 | /admin/admin | suggestionsWS>master>/admin/admin | **Page for the admin users** | 3 | - And the lucene index is loaded from the database - When I perform a "GET" on following URL "/api/pages/search?keywords=suggestion" - Then I get the following json response body - """ -[ - { - "hierarchy":"/Suggestion system/master/suggestions/suggestion", - "path":"suggestionsWS>master>/suggestions/suggestion", - "branch":"master", - "label":"The suggestions", - "description":"The suggestions...", - "pageContent":"**What's a suggestion ?**" - }, - { - "hierarchy":"/Suggestion system/master/context", - "path":"suggestionsWS>master>/context", - "branch":"master", - "label":"The context", - "description":"Why providing suggestions", - "pageContent":"**Feature**: Provide book suggestions" - }, - { - "hierarchy":"/Suggestion system/master/suggestions/examples", - "path":"suggestionsWS>master>/suggestions/examples", - "branch":"master", - "label":"examples", - "description":"Some examples", - "pageContent":"**Some suggestion examples**" - } -] -""" - - - - - diff --git a/test/services/SearchServiceTest.scala b/test/services/SearchServiceTest.scala index 5a40eadfd..d4e281a47 100644 --- a/test/services/SearchServiceTest.scala +++ b/test/services/SearchServiceTest.scala @@ -14,31 +14,67 @@ class SearchServiceTest extends WordSpec with MustMatchers with BeforeAndAfter w val pageIndex = new IndexService() val searchService = new SearchService(pageIndex) - val pageIndex1 = PageIndexDocument("hierarchy1", "path1", "branch1", "Doeco", "this is a test for markdown", "") - val pageIndex2 = PageIndexDocument("hierarchy2", "path2", "branch2", "Superstore", "", "") - val pageIndex3 = PageIndexDocument("hierarchy3", "path3", "branch3", "Doe co", "this is a text for markdown doe", "") - val pageIndex4 = PageIndexDocument("hierarchy4", "path4", "branch4", "co", "doe", "") - val pageIndex5 = PageIndexDocument("hierarchy5", "path5", "branch5", "Buymore", "this is a test for markdown this is a text for markdown", "") - val pageIndex6 = PageIndexDocument("hierarchy6", "path6", "branch6", "Do-Lots-Co", "", "") + val pageIndex1 = PageIndexDocument("id1", "hierarchy1", "path1", "breadcrum1", "project1", "branch1", "Doeco", "this is a test for markdown", "") + val pageIndex2 = PageIndexDocument("id2", "hierarchy2", "path2", "breadcrum2", "project2", "branch2", "Superstore", "page two", "") + val pageIndex3 = PageIndexDocument("id3", "hierarchy3", "path3", "breadcrum3", "project3", "branch3", "Doe co", "this is a text for markdown doe", "") + val pageIndex4 = PageIndexDocument("id4", "hierarchy4", "path4", "breadcrum4", "project4", "branch4", "co", "doe", "") + val pageIndex5 = PageIndexDocument("id5", "hierarchy5", "path5", "breadcrum5", "project5", "branch5", "Buymore", "this is a test for markdown this is a text for markdown", "") + val pageIndex6 = PageIndexDocument("id6", "hierarchy6", "path6", "breadcrum6", "project6", "branch6", "Do-Lots-Co", "", "") before { Mockito.reset(pageRepository) - pageIndex.addDocument(PageIndexDocument("hierarchy1", "path1", "branch1", "Doeco", "this is a test for markdown", "")) - pageIndex.addDocument(PageIndexDocument("hierarchy2", "path2", "branch2", "Superstore", "", "")) - pageIndex.addDocument(PageIndexDocument("hierarchy3", "path3", "branch3", "Doe co", "this is a text for markdown doe", "")) - pageIndex.addDocument(PageIndexDocument("hierarchy4", "path4", "branch4", "co", "doe", "")) - pageIndex.addDocument(PageIndexDocument("hierarchy5", "path5", "branch5", "Buymore", "this is a test for markdown this is a text for markdown", "")) - pageIndex.addDocument(PageIndexDocument("hierarchy6", "path6", "branch6", "Do-Lots-Co", "", "")) + pageIndex.reset() + pageIndex.insertOrUpdateDocument(PageIndexDocument("id1", "hierarchy1", "path1", "breadcrum1", "project1", "branch1", "Doeco", "this is a test for markdown", "")) + pageIndex.insertOrUpdateDocument(PageIndexDocument("id2", "hierarchy2", "path2", "breadcrum2", "project2", "branch2", "Superstore", "page two", "")) + pageIndex.insertOrUpdateDocument(PageIndexDocument("id3", "hierarchy3", "path3", "breadcrum3", "project3", "branch3", "Doe co", "this is a text for markdown doe", "")) + pageIndex.insertOrUpdateDocument(PageIndexDocument("id4", "hierarchy4", "path4", "breadcrum4", "project4", "branch4", "co", "doe", "")) + pageIndex.insertOrUpdateDocument(PageIndexDocument("id5", "hierarchy5", "path5", "breadcrum5", "project5", "branch5", "Buymore", "this is a test for markdown this is a text for markdown", "")) + pageIndex.insertOrUpdateDocument(PageIndexDocument("id6", "hierarchy6", "path6", "breadcrum6", "project6", "branch6", "Do-Lots-Co", "", "")) } "PageService" should { "search in lucene" in { - searchService.searchForPage("branch test") must contain theSameElementsAs Seq(pageIndex1, pageIndex5) - searchService.searchForPage("branch1") must contain theSameElementsAs Seq(pageIndex1) - searchService.searchForPage(" text") must contain theSameElementsAs Seq(pageIndex3, pageIndex5) - searchService.searchForPage("doe") must contain theSameElementsAs Seq(pageIndex3, pageIndex1, pageIndex4) + var items = searchService.searchForPage("branch test").items + items.length must equal(2) + items(0).page.id must equal("id1") + items(1).page.id must equal("id5") + + items = searchService.searchForPage("branch1").items + items.length must equal(1) + items(0).page.id must equal("id1") + + items = searchService.searchForPage(" text").items + items.length must equal(2) + items(0).page.id must equal("id3") + items(1).page.id must equal("id5") + + items = searchService.searchForPage("doe").items + items.length must equal(3) + items(0).page.id must equal("id3") + items(1).page.id must equal("id1") + items(2).page.id must equal("id4") + } + + + "insert or update in lucene" in { + var result = searchService.searchForPage("Superstore").items + + result.size must equal(1) + result.head.page.id must equal("id2") + result.head.page.description must equal("page two") + + + for(pageNumber <- 1 to 10){ + pageIndex.insertOrUpdateDocument(PageIndexDocument("id2", "hierarchy2", "path2", "breadcrum2", "branch2", "project2", "Superstore", s"page ${pageNumber}", "")) + } + + result = searchService.searchForPage("Superstore").items + + result.size must equal(1) + result.head.page.id must equal("id2") + result.head.page.description must equal(s"page 10") } diff --git a/test/steps/CommonSteps.scala b/test/steps/CommonSteps.scala index 1bb9234dc..03c5405e2 100644 --- a/test/steps/CommonSteps.scala +++ b/test/steps/CommonSteps.scala @@ -175,7 +175,7 @@ case class Configuration(path: String, value: String) case class PageRow(id: Long, name: String, label: String, description: String, order: Int, markdown: String, relativePath: String, path: String, directoryId: Long, dependOnOpenApi: Boolean) -case class LuceneDoc(hierarchy: String, path: String, branch: String, label: String, description: String, pageContent: String) +case class LuceneDoc(id: String, hierarchy: String, path: String, breadcrumb: String, project: String, branch: String, label: String, description: String, pageContent: String) class CommonSteps extends ScalaDsl with EN with MockitoSugar with Logging with JacksonDefaultDataTableEntryTransformer { @@ -301,7 +301,7 @@ Scenario: providing several book suggestions Given("""^we have the following document in the lucene index$""") { docs: util.List[LuceneDoc] => docs.asScala.map(doc => - pageIndex.addDocument(PageIndexDocument(doc.hierarchy, doc.path, doc.branch, doc.label, doc.description, doc.pageContent)) + pageIndex.insertOrUpdateDocument(PageIndexDocument(doc.id, doc.hierarchy, doc.path, doc.breadcrumb, doc.project, doc.branch, doc.label, doc.description, doc.pageContent)) ) }