Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Introduce basic admin screen for managing "Rules Collection" and "Suggested Fields" #80

Merged
merged 24 commits into from
Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
48d5e39
basic admin page introduced
Oct 12, 2021
219160a
pause where we are
Oct 14, 2021
a909c04
Rules collection component is in
Oct 14, 2021
456f82b
finalize text tweaks...
Oct 14, 2021
cd94038
Listing out Rules Collections, which is the renamed version of a Solr…
Oct 19, 2021
f5ee0a5
Able to create a new RulesCollection
Oct 19, 2021
3111ad6
deletes from the list of rules collections are reflected in the list
Oct 20, 2021
d8e6cad
updates are fed to the list
Oct 20, 2021
2973225
Introduce a event model that lets us signal around from the rules col…
Oct 20, 2021
f3e15e5
tweak the sentence!
Oct 20, 2021
a29a6a8
add a new end point to look up a single SolrIndex
Oct 28, 2021
7e99108
now routing to a specific solr index with a key
Oct 28, 2021
34d5792
wrapping in the ngIf prevents a race condidtion while we load the data
Nov 1, 2021
18526b6
Now listing all the suggestedFields for a solr index (rules collection)
Nov 2, 2021
83ec6b7
now able to delete a suggested field from the list.
Nov 2, 2021
a1c8688
able to create a suggested field, still lacking refreshing the list
Nov 2, 2021
bc8aa0e
refactored to have the list of suggested fields for a rule collection…
Nov 2, 2021
31ded31
Barest of unit tests...
Nov 2, 2021
477595f
lint
Nov 2, 2021
36f6a82
unit test that validates that the database model doesn't allow dup su…
Nov 9, 2021
f54f57c
Prevent duplicate SolrIndex objects, and check if the business rules …
Nov 9, 2021
a4c0c51
Fixed location for "SBT Debian packages no longer available"
pbartusch Nov 5, 2021
75fcaa2
First cut of e2e, however not quite sure how to get this properly int…
Nov 9, 2021
18dfb82
fix itnegration test
Dec 10, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ package-lock.json
/public
e2e/cypress/videos
e2e/cypress/screenshots
yarn.lock
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ FROM openjdk:11-buster as builder

ARG NODE_VERSION=10

RUN echo "deb https://dl.bintray.com/sbt/debian /" > /etc/apt/sources.list.d/sbt.list \
# Fixed location for "SBT Debian packages no longer available" (see https://issueexplorer.com/issue/SpinalHDL/VexRiscv/188). Do not use "https://dl.bintray.com/sbt/debian" anymore.
RUN echo "deb https://repo.scala-sbt.org/scalasbt/debian all main" > /etc/apt/sources.list.d/sbt.list \
&& curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | apt-key add \
&& apt-get update \
&& apt-get install -y sbt
Expand Down
22 changes: 22 additions & 0 deletions app/controllers/ApiController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ class ApiController @Inject()(authActionFactory: AuthActionFactory,
}
}

def getSolrIndex(solrIndexId: String) = authActionFactory.getAuthenticatedAction(Action).async {
Future {
Ok(Json.toJson(searchManagementRepository.getSolrIndex(SolrIndexId(solrIndexId))))
}
}

def deleteSolrIndex(solrIndexId: String) = authActionFactory.getAuthenticatedAction(Action).async {
Future {
searchManagementRepository.deleteSolrIndex(solrIndexId)
Ok(Json.toJson(ApiResult(API_RESULT_OK, "Deleting Solr Index successful", None)))
}
}

def downloadAllRulesTxtFiles = authActionFactory.getAuthenticatedAction(Action) { req =>
Ok.chunked(
createStreamResultInBackground(
Expand Down Expand Up @@ -302,6 +315,15 @@ class ApiController @Inject()(authActionFactory: AuthActionFactory,
}
}

// I am requiring the solrIndexId because it is more RESTful, but it turns out we don't need it.
// Maybe validation some day?
def deleteSuggestedSolrField(solrIndexId: String, suggestedFieldId: String) = authActionFactory.getAuthenticatedAction(Action).async { request: Request[AnyContent] =>
Future {
searchManagementRepository.deleteSuggestedSolrField(SuggestedSolrFieldId(suggestedFieldId))
Ok(Json.toJson(ApiResult(API_RESULT_OK, "Deleting Suggested Field successful", None)))
}
}

// TODO consider making method .asynch
def importFromRulesTxt(solrIndexId: String) = authActionFactory.getAuthenticatedAction(Action)(parse.multipartFormData) { request =>
request.body
Expand Down
37 changes: 37 additions & 0 deletions app/models/SearchManagementRepository.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import models.reports.{ActivityReport, DeploymentLog, RulesReport}
@javax.inject.Singleton
class SearchManagementRepository @Inject()(dbapi: DBApi, toggleService: FeatureToggleService)(implicit ec: DatabaseExecutionContext) {


private val db = dbapi.database("default")

// On startup, always sync predefined tags with the DB
Expand Down Expand Up @@ -44,10 +45,41 @@ class SearchManagementRepository @Inject()(dbapi: DBApi, toggleService: FeatureT
SolrIndex.loadNameById(solrIndexId)
}

def getSolrIndex(solrIndexId: SolrIndexId): SolrIndex = db.withConnection { implicit connection =>
SolrIndex.loadById(solrIndexId)
}

def addNewSolrIndex(newSolrIndex: SolrIndex): SolrIndexId = db.withConnection { implicit connection =>
SolrIndex.insert(newSolrIndex)
}

/**
* We check for any InputTags, CanonicalSpellings, and SearchInputs. We don't
* check for the existence of any SuggestedSolrFields.
*/
def deleteSolrIndex(solrIndexId: String): Int = db.withTransaction { implicit connection =>

val solrIndexIdId = SolrIndexId(solrIndexId)
val inputTags = InputTag.loadAll.filter(_.solrIndexId== Option(solrIndexIdId))
if (inputTags.size > 0) {
throw new Exception("Can't delete Solr Index that has " + inputTags.size + "tags existing");
}

val canonicalSpellings = CanonicalSpelling.loadAllForIndex(solrIndexIdId)
if (canonicalSpellings.size > 0) {
throw new Exception("Can't delete Solr Index that has " + canonicalSpellings.size + " canonical spellings existing");
}

val searchInputs = SearchInput.loadAllForIndex(solrIndexIdId)
if (searchInputs.size > 0) {
throw new Exception("Can't delete Solr Index that has " + searchInputs.size + " inputs existing");
}

val id = SolrIndex.delete(solrIndexId)

id
}

def listAllInputTags(): Seq[InputTag] = db.withConnection { implicit connection =>
InputTag.loadAll()
}
Expand Down Expand Up @@ -204,6 +236,11 @@ class SearchManagementRepository @Inject()(dbapi: DBApi, toggleService: FeatureT
def addNewSuggestedSolrField(solrIndexId: SolrIndexId, suggestedSolrFieldName: String): SuggestedSolrField = db.withConnection { implicit connection =>
SuggestedSolrField.insert(solrIndexId, suggestedSolrFieldName)
}
def deleteSuggestedSolrField(suggestedSolrFieldId: SuggestedSolrFieldId): Int = db.withTransaction { implicit connection =>
val id = SuggestedSolrField.delete(suggestedSolrFieldId);

id
}

def addNewDeploymentLogOk(solrIndexId: String, targetPlatform: String): Boolean = db.withConnection { implicit connection =>
SQL("insert into deployment_log(id, solr_index_id, target_platform, last_update, result) values ({id}, {solr_index_id}, {target_platform}, {last_update}, {result})")
Expand Down
13 changes: 12 additions & 1 deletion app/models/SolrIndex.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import java.util.Date
import play.api.libs.json.{Json, OFormat}
import anorm.SqlParser._
import anorm._
import play.api.Logging

class SolrIndexId(id: String) extends Id(id)
object SolrIndexId extends IdObject[SolrIndexId](new SolrIndexId(_))
Expand Down Expand Up @@ -45,10 +46,20 @@ object SolrIndex {
allMatchingIndeces.head.name
}

def loadById(solrIndexId: SolrIndexId)(implicit connection: Connection): SolrIndex = {
val allMatchingIndeces = SQL"select * from #$TABLE_NAME where id = $solrIndexId".as(sqlParser.*)

allMatchingIndeces.head
}

def insert(newSolrIndex: SolrIndex)(implicit connection: Connection): SolrIndexId = {
SQL"insert into #$TABLE_NAME (id, name, description, last_update) values (${newSolrIndex.id}, ${newSolrIndex.name}, ${newSolrIndex.description}, ${new Date()})".execute()
newSolrIndex.id
}

def delete(id: String)(implicit connection: Connection): Int = {
SQL"delete from #$TABLE_NAME where id = $id".executeUpdate()
}


}
}
4 changes: 4 additions & 0 deletions app/models/SuggestedSolrField.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,9 @@ object SuggestedSolrField {
field
}

def delete(id: SuggestedSolrFieldId)(implicit connection: Connection): Int = {
SQL"delete from #$TABLE_NAME where #$ID = $id".executeUpdate()
}


}
14 changes: 14 additions & 0 deletions conf/evolutions/default/8.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# --- !Ups

-- Ensure that we do not allow duplicate solr indexes with same name.
create unique index solr_index_field_name on solr_index (name);

-- Ensure that we do not allow duplicate suggested solr fields for the same solr index.
create unique index suggested_solr_field_name_solr_index on suggested_solr_field (solr_index_id, name);



# --- !Downs

drop index solr_index_field_name
drop index suggested_solr_field_name_solr_index;
3 changes: 3 additions & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ GET /health controllers.HealthController.health
# TODO search-input URL path partially "behind" solrIndexId path component and partially not
GET /api/v1/featureToggles controllers.ApiController.getFeatureToggles
GET /api/v1/solr-index controllers.ApiController.listAllSolrIndeces
GET /api/v1/solr-index/:solrIndexId controllers.ApiController.getSolrIndex(solrIndexId: String)
PUT /api/v1/solr-index controllers.ApiController.addNewSolrIndex
DELETE /api/v1/solr-index/:solrIndexId controllers.ApiController.deleteSolrIndex(solrIndexId: String)
GET /api/v1/inputTags controllers.ApiController.listAllInputTags
GET /api/v1/:solrIndexId/search-input controllers.ApiController.listAllSearchInputs(solrIndexId: String)
GET /api/v1/search-input/:searchInputId controllers.ApiController.getDetailedSearchInput(searchInputId: String)
Expand All @@ -20,6 +22,7 @@ DELETE /api/v1/search-input/:searchInputId controllers.ApiC
POST /api/v1/:solrIndexId/rules-txt/:targetPlatform controllers.ApiController.updateRulesTxtForSolrIndexAndTargetPlatform(solrIndexId: String, targetPlatform: String)
GET /api/v1/:solrIndexId/suggested-solr-field controllers.ApiController.listAllSuggestedSolrFields(solrIndexId: String)
PUT /api/v1/:solrIndexId/suggested-solr-field controllers.ApiController.addNewSuggestedSolrField(solrIndexId: String)
DELETE /api/v1/:solrIndexId/suggested-solr-field/:suggestedFieldId controllers.ApiController.deleteSuggestedSolrField(solrIndexId: String, suggestedFieldId: String)
GET /api/v1/allRulesTxtFiles controllers.ApiController.downloadAllRulesTxtFiles
POST /api/v1/:solrIndexId/import-from-rules-txt controllers.ApiController.importFromRulesTxt(solrIndexId: String)
GET /api/v1/log/deployment-info controllers.ApiController.getLatestDeploymentResult(solrIndexId: String, targetSystem: String)
Expand Down
27 changes: 27 additions & 0 deletions e2e/cypress/integration/admin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//import { truncateTable } from '../src/sql';

context('SMUI app', () => {
beforeEach(() => {
//truncateTable('search_input');
cy.visit('/');
});

it('should be able to create a new Rules Collection', () => {
cy.contains('Admin').click();

var listingCount = 0;
cy.get('app-smui-admin-rules-collection-list .list-group-item').should(collections => {
listingCount = collections.length;
});

cy.get('#collectionName').type('testRulesCollection');
cy.get('#collectionSearchEngineName').type('test_search_engine');
cy.get('#createRulesCollectionBtn').click();

cy.wait(2000)

cy.get('app-smui-admin-rules-collection-list .list-group-item').should(collections => {
expect(collections).to.have.length(listingCount + 1);
});
});
});
Loading