Skip to content

Commit

Permalink
Merged smui_v3_13_3
Browse files Browse the repository at this point in the history
  • Loading branch information
pbartusch committed Jan 5, 2022
2 parents 7c36b18 + 8c14c5f commit 455b2f7
Show file tree
Hide file tree
Showing 32 changed files with 2,275 additions and 1,289 deletions.
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
hs_err_pid*
60 changes: 48 additions & 12 deletions app/controllers/ApiController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,36 @@ class ApiController @Inject()(authActionFactory: AuthActionFactory,
SolrIndex(name = searchIndexName, description = searchIndexDescription)
)

Ok(Json.toJson(ApiResult(API_RESULT_OK, "Adding Search Input '" + searchIndexName + "' successful.", Some(solrIndexId))))
Ok(Json.toJson(ApiResult(API_RESULT_OK, "Successfully added Deployment Channel '" + searchIndexName + "'.", Some(solrIndexId))))
}.getOrElse {
BadRequest(Json.toJson(ApiResult(API_RESULT_FAIL, "Adding new Search Input failed. Unexpected body data.", None)))
BadRequest(Json.toJson(ApiResult(API_RESULT_FAIL, "Adding new Deployment Channel failed. Unexpected body data.", None)))
}
}

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 {
// TODO handle exception, give API_RESULT_FAIL
try {
searchManagementRepository.deleteSolrIndex(solrIndexId)
Ok(Json.toJson(ApiResult(API_RESULT_OK, "Deleting Solr Index successful", None)))
} catch {
case e: Exception => BadRequest(
Json.toJson(
ApiResult(API_RESULT_FAIL, s"Deleting Solr Index failed: ${e.getMessage}", None)
)
)
case _ => BadRequest(
Json.toJson(
ApiResult(API_RESULT_FAIL, s"Deleting Solr Index failed due to an unknown error", None)
)
)
}
}
}

Expand Down Expand Up @@ -111,7 +138,7 @@ class ApiController @Inject()(authActionFactory: AuthActionFactory,
Ok(Json.toJson(ApiResult(API_RESULT_OK, "Adding Search Input '" + searchInputTerm + "' successful.", Some(searchInputId))))
}
case errors => {
val msgs = s"Failed to add new input ${searchInputTerm}: " + errors.mkString("\n")
val msgs = s"Failed to add new Search Input ${searchInputTerm}: " + errors.mkString("\n")
logger.error(msgs)
BadRequest(Json.toJson(ApiResult(API_RESULT_FAIL, msgs, None)))
}
Expand Down Expand Up @@ -146,7 +173,7 @@ class ApiController @Inject()(authActionFactory: AuthActionFactory,
}
}
case errors => {
val msgs = s"Failed to update input with new term ${searchInput.term}: " + errors.mkString("\n")
val msgs = s"Failed to update Search Input with new term ${searchInput.term}: " + errors.mkString("\n")
logger.error(msgs)
BadRequest(Json.toJson(ApiResult(API_RESULT_FAIL, msgs, None)))
}
Expand Down Expand Up @@ -180,14 +207,14 @@ class ApiController @Inject()(authActionFactory: AuthActionFactory,
CanonicalSpellingValidator.validateNoEmptySpelling(term) match {
case None => {
val canonicalSpelling = searchManagementRepository.addNewCanonicalSpelling(SolrIndexId(solrIndexId), term)
Ok(Json.toJson(ApiResult(API_RESULT_OK, "Adding new canonical spelling '" + term + "' successful.", Some(canonicalSpelling.id))))
Ok(Json.toJson(ApiResult(API_RESULT_OK, "Successfully added Canonical Spelling '" + term + "'.", Some(canonicalSpelling.id))))
}
case Some(error) => {
BadRequest(Json.toJson(ApiResult(API_RESULT_FAIL, error, None)))
}
}
}.getOrElse {
BadRequest(Json.toJson(ApiResult(API_RESULT_FAIL, "Adding new canonical spelling failed. Unexpected body data.", None)))
BadRequest(Json.toJson(ApiResult(API_RESULT_FAIL, "Adding new Canonical Spelling failed. Unexpected body data.", None)))
}
}
}
Expand All @@ -211,21 +238,21 @@ class ApiController @Inject()(authActionFactory: AuthActionFactory,
CanonicalSpellingValidator.validateCanonicalSpellingsAndAlternatives(spellingWithAlternatives, otherSpellings) match {
case Nil =>
searchManagementRepository.updateSpelling(spellingWithAlternatives)
Ok(Json.toJson(ApiResult(API_RESULT_OK, "Updating canonical spelling successful.", Some(CanonicalSpellingId(canonicalSpellingId)))))
Ok(Json.toJson(ApiResult(API_RESULT_OK, "Successfully updated Canonical Spelling.", Some(CanonicalSpellingId(canonicalSpellingId)))))
case errors =>
val msgs = s"Failed to update spelling ${spellingWithAlternatives.term}: " + errors.mkString("\n")
val msgs = s"Failed to update Canonical Spelling ${spellingWithAlternatives.term}: " + errors.mkString("\n")
logger.error(msgs)
BadRequest(Json.toJson(ApiResult(API_RESULT_FAIL, msgs, None)))
}
}.getOrElse {
BadRequest(Json.toJson(ApiResult(API_RESULT_FAIL, "Updating canonical spelling failed. Unexpected body data.", None)))
BadRequest(Json.toJson(ApiResult(API_RESULT_FAIL, "Updating Canonical Spelling failed. Unexpected body data.", None)))
}
}

def deleteSpelling(canonicalSpellingId: String) = authActionFactory.getAuthenticatedAction(Action).async {
Future {
searchManagementRepository.deleteSpelling(canonicalSpellingId)
Ok(Json.toJson(ApiResult(API_RESULT_OK, "Deleting canonical spelling with alternatives successful.", None)))
Ok(Json.toJson(ApiResult(API_RESULT_OK, "Deleting Canonical Spelling with alternatives successful.", None)))
}
}

Expand Down Expand Up @@ -262,15 +289,15 @@ class ApiController @Inject()(authActionFactory: AuthActionFactory,
// TODO evaluate pushing a non successful deployment attempt to the (database) log as well
BadRequest(
Json.toJson(
ApiResult(API_RESULT_FAIL, s"Updating Solr Index failed.\nScript output:\n${result.output}", None)
ApiResult(API_RESULT_FAIL, s"Updating Search Management Config for Solr Index failed.\nScript output:\n${result.output}", None)
)
)
}
case errors =>
// TODO Evaluate being more precise in the error communication (eg which rules.txt failed?, where? / which line?, why?, etc.)
BadRequest(
Json.toJson(
ApiResult(API_RESULT_FAIL, s"Updating Solr Index failed. Validation errors in rules.txt:\n${errors.mkString("\n")}", None)
ApiResult(API_RESULT_FAIL, s"Updating Search Management Config for Solr Index failed. Validation errors in rules.txt:\n${errors.mkString("\n")}", None)
)
)
}
Expand Down Expand Up @@ -302,6 +329,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
39 changes: 39 additions & 0 deletions app/models/SearchManagementRepository.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import play.api.Logging
@javax.inject.Singleton
class SearchManagementRepository @Inject()(dbapi: DBApi, toggleService: FeatureToggleService)(implicit ec: DatabaseExecutionContext) extends Logging {


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

/**
Expand All @@ -29,10 +30,43 @@ 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");
}

// TODO consider reconfirmation and deletion of history entries (if some exist) (see https://github.com/querqy/smui/issues/97)

val id = SolrIndex.delete(solrIndexId)

id
}

def listAllInputTags(): Seq[InputTag] = db.withConnection { implicit connection =>
InputTag.loadAll()
}
Expand Down Expand Up @@ -189,6 +223,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()
}


}
6 changes: 5 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import com.typesafe.sbt.GitBranchPrompt

name := "search-management-ui"
version := "3.13.2"
version := "3.13.3"

scalaVersion := "2.12.11"

Expand Down Expand Up @@ -111,3 +111,7 @@ dockerBuildArguments in docker := Map(
buildOptions in docker := BuildOptions(
pullBaseImage = BuildOptions.Pull.Always
)

// Fix build on Mac M1 ("Apple Silicon") chipsets (see https://discuss.lightbend.com/t/apple-silicon-m1-playframework-broken-on-apple-silicon/7924/16)
// TODO using jdk8 instead (to avoid `java.lang.IllegalStateException: Unable to load cache item`)
PlayKeys.fileWatchService := play.dev.filewatch.FileWatchService.jdk7(play.sbt.run.toLoggerProxy(sLog.value))
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

0 comments on commit 455b2f7

Please sign in to comment.