diff --git a/newswires/app/controllers/QueryController.scala b/newswires/app/controllers/QueryController.scala index 56901b5..038f456 100644 --- a/newswires/app/controllers/QueryController.scala +++ b/newswires/app/controllers/QueryController.scala @@ -15,6 +15,7 @@ import play.api.mvc.{ Request } import play.api.{Configuration, Logging} +import service.FeatureSwitchProvider class QueryController( val controllerComponents: ControllerComponents, @@ -42,13 +43,19 @@ class QueryController( ): Action[AnyContent] = apiAuthAction { request: UserRequest[AnyContent] => val bucket = request.getQueryString("bucket").flatMap(SearchBuckets.get) + val suppliersToExcludeByDefault = + if (FeatureSwitchProvider.ShowGuSuppliers.isOn) List("GuReuters", "GuAP") + else Nil + val queryParams = SearchParams( text = maybeFreeTextQuery, keywordIncl = maybeKeywords.map(_.split(",").toList).getOrElse(Nil), keywordExcl = paramToList(request, "keywordsExcl"), suppliersIncl = suppliers, - suppliersExcl = - request.queryString.get("supplierExcl").map(_.toList).getOrElse(Nil), + suppliersExcl = request.queryString + .get("supplierExcl") + .map(_.toList) + .getOrElse(Nil) ++ suppliersToExcludeByDefault, subjectsIncl = subjects, subjectsExcl = subjectsExcl ) diff --git a/newswires/app/controllers/ViteController.scala b/newswires/app/controllers/ViteController.scala index b3e7a32..b57f536 100644 --- a/newswires/app/controllers/ViteController.scala +++ b/newswires/app/controllers/ViteController.scala @@ -7,6 +7,8 @@ import play.api.libs.ws.WSClient import play.api.mvc._ import play.api.{Configuration, Mode} import play.filters.csrf.CSRFAddToken +import service.FeatureSwitchProvider +import service.FeatureSwitchProvider.FeatureSwitch import views.html.helper.CSRF import java.net.URL @@ -14,7 +16,7 @@ import scala.concurrent.{ExecutionContext, Future} import scala.io.Source case class ClientConfig( - suppliersToExclude: List[String] + switches: Map[String, Boolean] ) object ClientConfig { @@ -61,7 +63,7 @@ class ViteController( def injectClientConfig(body: String): String = { val config = views.html.fragments.clientConfig( - ClientConfig(List("GuReuters", "GuAP")) + ClientConfig(FeatureSwitchProvider.clientSideSwitchStates) ) body.replace( diff --git a/newswires/app/service/FeatureSwitchProvider.scala b/newswires/app/service/FeatureSwitchProvider.scala new file mode 100644 index 0000000..3de5f06 --- /dev/null +++ b/newswires/app/service/FeatureSwitchProvider.scala @@ -0,0 +1,34 @@ +package service + +import play.api.libs.json.{Json, OFormat} + +sealed trait SwitchState +case object On extends SwitchState +case object Off extends SwitchState + +object FeatureSwitchProvider { + + case class FeatureSwitch( + name: String, + description: String, + exposeToClient: Boolean = false, + private val safeState: SwitchState + ) { + def isOn: Boolean = + safeState == On // currently we're only using safeState to determine state + } + + val ShowGuSuppliers: FeatureSwitch = + FeatureSwitch( + name = "ShowGuSuppliers", + safeState = Off, + description = "Show suppliers from the Guardian", + exposeToClient = true + ) + + private val switches = List( + ShowGuSuppliers + ) + def clientSideSwitchStates: Map[String, Boolean] = + switches.filter(_.exposeToClient).map(s => s.name -> s.isOn).toMap +} diff --git a/newswires/client/src/context/SearchContext.test.tsx b/newswires/client/src/context/SearchContext.test.tsx index fb427ff..b2be0aa 100644 --- a/newswires/client/src/context/SearchContext.test.tsx +++ b/newswires/client/src/context/SearchContext.test.tsx @@ -1,5 +1,4 @@ import { act, render } from '@testing-library/react'; -import type React from 'react'; import { flushPendingPromises } from '../tests/testHelpers.ts'; import type { SearchContextShape } from './SearchContext.tsx'; import { SearchContextProvider, useSearch } from './SearchContext.tsx'; diff --git a/newswires/client/src/context/SearchContext.tsx b/newswires/client/src/context/SearchContext.tsx index 69a0388..a6e0198 100644 --- a/newswires/client/src/context/SearchContext.tsx +++ b/newswires/client/src/context/SearchContext.tsx @@ -5,7 +5,6 @@ import { useContext, useEffect, useReducer, - useRef, useState, } from 'react'; import { z } from 'zod'; diff --git a/newswires/client/src/serverSideConfig.ts/serverSideConfig.ts b/newswires/client/src/serverSideConfig.ts/serverSideConfig.ts index d852abb..8e3d33c 100644 --- a/newswires/client/src/serverSideConfig.ts/serverSideConfig.ts +++ b/newswires/client/src/serverSideConfig.ts/serverSideConfig.ts @@ -1 +1,4 @@ -export const SUPPLIERS_TO_EXCLUDE = window.configuration.suppliersToExclude; +export const SUPPLIERS_TO_EXCLUDE = window.configuration.switches + .ShowGuSuppliers + ? [] + : ['GUAP', 'GUREUTERS']; diff --git a/newswires/client/src/serverSideConfig.ts/windowConfigType.ts b/newswires/client/src/serverSideConfig.ts/windowConfigType.ts index f907cff..385cd57 100644 --- a/newswires/client/src/serverSideConfig.ts/windowConfigType.ts +++ b/newswires/client/src/serverSideConfig.ts/windowConfigType.ts @@ -1,10 +1,14 @@ +interface FeatureSwitches { + ShowGuSuppliers: boolean; +} + declare global { /* ~ Here, declare things that go in the global namespace, or augment *~ existing declarations in the global namespace */ interface Window { configuration: { - suppliersToExclude: string[]; + switches: FeatureSwitches; }; } } diff --git a/newswires/client/src/suppliers.ts b/newswires/client/src/suppliers.ts index a527133..0da8fcb 100644 --- a/newswires/client/src/suppliers.ts +++ b/newswires/client/src/suppliers.ts @@ -54,7 +54,7 @@ export const recognisedSuppliers = Object.keys(allSupplierData).filter( ); export const supplierData = Object.fromEntries( - Object.entries(allSupplierData).filter(([supplier]) => + Object.entries(allSupplierData).filter(([supplier, _]) => recognisedSuppliers.includes(supplier), ), );