Skip to content

Commit

Permalink
feat: refonte de la recherche établissements (#84)
Browse files Browse the repository at this point in the history
* search -> POST, add parameters

* unused params in method

* correction siegeUniquement

* correction recherche

* test départements et activités
  • Loading branch information
chrnin authored Jun 9, 2021
1 parent 0ea8b34 commit 0af0a0f
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 56 deletions.
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func runAPI() {
etablissement.GET("/comments/:siret", validSiret, getEntrepriseComments)
etablissement.POST("/comments/:siret", validSiret, addEntrepriseComment)
etablissement.PUT("/comments/:id", updateEntrepriseComment)
etablissement.GET("/search/:search", searchEtablissementHandler)
etablissement.POST("/search", searchEtablissementHandler)

follow := router.Group("/follow", getKeycloakMiddleware(), logMiddleware)
follow.GET("", getEtablissementsFollowedByCurrentUser)
Expand Down
107 changes: 107 additions & 0 deletions migrations/210531_0_new_search.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
drop function get_search;
create or replace function get_search (
in roles_users text[], -- $1
in nblimit int, -- $2
in nboffset int, -- $3
in libelle_liste text, -- $4
in siret_expression text, -- $5
in raison_sociale_expression text, -- $6
in ignore_roles boolean, -- $7
in ignore_zone boolean, -- $8
in username text, -- $9
in siege_uniquement boolean, -- $10
in order_by text, -- $11
in alert_only boolean, -- $12
in last_procol text[], -- $13
in departements text[], -- $14
in suivi boolean, -- $15
in effectif_min int, -- $16
in effectif_max int, -- $17
in sirens text[], -- $18
in activites text[] -- $19
) returns table (
siret text,
siren text,
raison_sociale text,
commune text,
libelle_departement text,
code_departement text,
valeur_score real,
detail_score jsonb,
first_alert boolean,
chiffre_affaire real,
arrete_bilan date,
exercice_diane int,
variation_ca real,
resultat_expl real,
effectif real,
libelle_n5 text,
libelle_n1 text,
code_activite text,
last_procol text,
activite_partielle boolean,
apconso_heure_consomme int,
apconso_montant int,
hausse_urssaf boolean,
dette_urssaf real,
alert text,
nb_total bigint,
nb_f1 bigint,
nb_f2 bigint,
visible boolean,
in_zone boolean,
followed boolean,
followed_enterprise boolean,
siege boolean,
raison_sociale_groupe text,
territoire_industrie boolean,
comment text,
category text,
since timestamp,
urssaf boolean,
dgefp boolean,
score boolean,
bdf boolean
) as $$
select
s.siret, s.siren, s.raison_sociale, s.commune,
s.libelle_departement, s.code_departement,
case when (permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).score then s.valeur_score end as valeur_score,
case when (permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).score then s.detail_score end as detail_score,
case when (permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).score then s.first_alert end as first_alert,
s.chiffre_affaire, s.arrete_bilan, s.exercice_diane, s.variation_ca, s.resultat_expl, s.effectif,
s.libelle_n5, s.libelle_n1, s.code_activite, s.last_procol,
case when (permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).dgefp then s.activite_partielle end as activite_partielle,
case when (permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).dgefp then s.apconso_heure_consomme end as apconso_heure_consomme,
case when (permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).dgefp then s.apconso_montant end as apconso_montant,
case when (permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).urssaf then s.hausse_urssaf end as hausse_urssaf,
case when (permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).urssaf then s.dette_urssaf end as dette_urssaf,
case when (permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).score then s.alert end,
count(*) over () as nb_total,
count(case when s.alert='Alerte seuil F1' and (permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).score then 1 end) over () as nb_f1,
count(case when s.alert='Alerte seuil F2' and (permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).score then 1 end) over () as nb_f2,
(permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).visible,
(permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).in_zone,
f.id is not null as followed_etablissement,
fe.siren is not null as followed_entreprise,
s.siege, s.raison_sociale_groupe, territoire_industrie,
f.comment, f.category, f.since,
(permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).urssaf,
(permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).dgefp,
(permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).score,
(permissions($1, s.roles, s.first_list_entreprise, s.code_departement, fe.siren is not null)).bdf
from v_summaries s
left join etablissement_follow f on f.active and f.siret = s.siret and f.username = $9
left join v_entreprise_follow fe on fe.siren = s.siren and fe.username = $9
left join v_naf n on n.code_n5 = s.code_activite
where
(s.raison_sociale ilike $6 or s.siret ilike $5)
and (s.roles && $1 or $7) -- plus d'ignoreRoles mais on garde la possibilité de le réimplémenter
and (s.code_departement=any($1) or $8) -- idem pour ignoreZone
and (s.code_departement=any($14) or $14 is null)
and (s.effectif >= $16 or $16 is null)
and (n.code_n1 = any($19) or $19 is null)
and (s.siege or not $10)
order by s.raison_sociale, s.siret
limit $2 offset $3
$$ language sql immutable;
73 changes: 25 additions & 48 deletions search.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package main

import (
"strconv"

"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)

type searchParams struct {
search string
page int
ignoreRoles bool
ignoreZone bool
siegeUniquement bool
roles scope
Search string `json:"search"`
Page int `json:"page"`
Departements []string `json:"departements,omitempty"`
Activites []string `json:"activites,omitempty"`
EffectifMin *int `json:"effectifMin"`
SiegeUniquement bool `json:"siegeUniquement"`
IgnoreRoles bool `json:"ignoreRoles"`
IgnoreZone bool `json:"ignoreZone"`
username string
roles scope
}

type searchResult struct {
Expand All @@ -30,47 +31,23 @@ type searchResult struct {

func searchEtablissementHandler(c *gin.Context) {
var params searchParams
var err error

params.username = c.GetString("username")

if search := c.Param("search"); len(search) >= 3 {
params.search = search
} else {
c.JSON(400, "search string length < 3")
err := c.Bind(&params)
if err != nil {
c.Abort()
return
}

if page, ok := c.GetQuery("page"); ok {
params.page, err = strconv.Atoi(page)
if err != nil {
c.JSON(400, "page has to be integer >= 0")
return
}
}

if ignoreRoles, ok := c.GetQuery("ignoreroles"); ok {
params.ignoreRoles, err = strconv.ParseBool(ignoreRoles)
if err != nil {
c.JSON(400, "france is either `true` or `false`")
return
}
}
params.username = c.GetString("username")

if ignoreZone, ok := c.GetQuery("ignorezone"); ok {
params.ignoreZone, err = strconv.ParseBool(ignoreZone)
if err != nil {
c.JSON(400, "roles is either `true` or `false`")
return
}
if len(params.Search) < 3 {
c.JSON(400, "search string length < 3")
return
}

if siegeUniquement, ok := c.GetQuery("siegeUniquement"); ok {
params.siegeUniquement, err = strconv.ParseBool(siegeUniquement)
if err != nil {
c.JSON(400, "siegeUniquement is either `true` or `false`")
return
}
if params.Page < 0 {
c.JSON(400, "page has to be integer >= 0")
return
}

params.roles = scopeFromContext(c)
Expand All @@ -92,11 +69,11 @@ func searchEtablissement(params searchParams) (searchResult, Jerror) {
zoneGeo := params.roles.zoneGeo()
limit := viper.GetInt("searchPageLength")

offset := params.page * limit
offset := params.Page * limit

summaryparams := summaryParams{
zoneGeo, &limit, &offset, &liste[0].ID, &params.search, &params.ignoreRoles, &params.ignoreZone,
params.username, params.siegeUniquement, "raison_sociale", &False, nil, nil, nil, nil, nil, nil, nil,
zoneGeo, &limit, &offset, &liste[0].ID, &params.Search, &params.IgnoreRoles, &params.IgnoreZone,
params.username, params.SiegeUniquement, "raison_sociale", &False, nil, params.Departements, nil, params.EffectifMin, nil, nil, params.Activites,
}

summaries, err := getSummaries(summaryparams)
Expand All @@ -112,9 +89,9 @@ func searchEtablissement(params searchParams) (searchResult, Jerror) {
search.NBF1 = *summaries.global.countF1
search.NBF2 = *summaries.global.countF2
}
search.From = limit*params.page + 1
search.To = limit*params.page + len(search.Results)
search.Page = params.page
search.From = limit*params.Page + 1
search.To = limit*params.Page + len(search.Results)
search.Page = params.Page
search.PageMax = (search.Total - 1) / limit

if len(search.Results) == 0 {
Expand Down
4 changes: 2 additions & 2 deletions summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ func getSummaries(params summaryParams) (summaries, error) {
sql = `select * from get_score($1, $2, $3, $4, $5, $6, null, $7, $8, $9, 'score', true, $10, $11, $12, $13, $14, $15, $16) as scores;`
} else if params.orderBy == "raison_sociale" {
p := params.toSQLParams()
sqlParams = p[0:10]
sql = `select * from get_search($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'raison_sociale', false, null, null, null, null, null, null) as raison_sociale;`
sqlParams = append(p[0:10], p[13], p[15], p[18])
sql = `select * from get_search($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'raison_sociale', null, null, $11, null, $12, null, null, $13) as raison_sociale;`
} else if params.orderBy == "follow" {
p := params.toSQLParams()
sqlParams = append(sqlParams, p[0], p[3], p[8])
Expand Down
Binary file added test/data/searchActivites.json.gz
Binary file not shown.
Binary file added test/data/searchDepartement.json.gz
Binary file not shown.
7 changes: 6 additions & 1 deletion test/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,12 @@ func testEtablissementVAF(t *testing.T, siret string, vaf string) {
func testSearchVAF(t *testing.T, siret string, vaf string) {
goldenFilePath := fmt.Sprintf("data/getSearch-%s-%s.json.gz", vaf, siret)
t.Logf("la recherche renvoie l'établissement %s sous la forme attendue (ref %s)", siret, goldenFilePath)
_, indented, _ := get(t, "/etablissement/search/"+siret+"?ignorezone=true&ignoreroles=true")
params := map[string]interface{}{
"search": siret,
"ignoreZone": true,
"ignoreRoles": true,
}
_, indented, _ := post(t, "/etablissement/search", params)
diff, _ := processGoldenFile(t, goldenFilePath, indented)
if diff != "" {
t.Errorf("differences entre le résultat et le golden file: %s \n%s", goldenFilePath, diff)
Expand Down
65 changes: 62 additions & 3 deletions test/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,24 @@ func TestFollow(t *testing.T) {
}

func TestSearch(t *testing.T) {
// tester le retour 400 en cas de recherche trop courte
t.Log("/etablissement/search retourne 400")
resp, _, _ := get(t, "/etablissement/search/t")
params := map[string]interface{}{
"search": "t",
}
resp, _, _ := post(t, "/etablissement/search", params)
if resp.StatusCode != 400 {
t.Errorf("mauvais status retourné: %d", resp.StatusCode)
}

// tester la recherche par chaine de caractères
rows, err := db.Query(context.Background(), `select distinct substring(e.siret from 1 for 3) from etablissement e
inner join departements d on d.code = e.departement
inner join regions r on r.id = d.id_region
where r.libelle in ('Bourgogne-Franche-Comté', 'Auvergne-Rhône-Alpes')
order by substring(e.siret from 1 for 3)
limit 10`)
limit 10
`)
if err != nil {
t.Errorf("impossible de se connecter à la base: %s", err.Error())
}
Expand All @@ -73,12 +79,65 @@ func TestSearch(t *testing.T) {
t.Errorf("siret illisible: %s", err.Error())
}

params := make(map[string]interface{})
params["search"] = siret
params["ignoreZone"] = false
params["ignoreRoles"] = false
t.Logf("la recherche %s est bien de la forme attendue", siret)
_, indented, _ := get(t, "/etablissement/search/"+siret)
_, indented, _ := post(t, "/etablissement/search", params)
goldenFilePath := fmt.Sprintf("data/search-%d.json.gz", i)
processGoldenFile(t, goldenFilePath, indented)
i++
}

// tester par département
var departements []string
var siret string
err = db.QueryRow(
context.Background(),
`select array_agg(distinct departement), substring(first(siret) from 1 for 3) from etablissement where departement < '10' and departement != '00'`,
).Scan(&departements, &siret)
if err != nil {
t.Errorf("impossible de se connecter à la base: %s", err.Error())
}

params = map[string]interface{}{
"departements": departements,
"search": siret,
"ignoreZone": true,
"ignoreRoles": true,
}

t.Log("la recherche filtrée par départements est bien de la forme attendue")
_, indented, _ := post(t, "/etablissement/search", params)
goldenFilePath := "data/searchDepartement.json.gz"
processGoldenFile(t, goldenFilePath, indented)
i++

// tester par activité
err = db.QueryRow(
context.Background(),
`select substring(first(siret) from 1 for 3)
from etablissement e
inner join v_naf n on n.code_n5 = e.code_activite
where code_n1 in ('A', 'B', 'C')
`,
).Scan(&siret)
if err != nil {
t.Errorf("impossible de se connecter à la base: %s", err.Error())
}

params = map[string]interface{}{
"activites": []string{"A", "B", "C"},
"search": siret,
"ignoreZone": true,
"ignoreRoles": true,
}

t.Log("la recherche filtrée par activites est bien de la forme attendue")
_, indented, _ = post(t, "/etablissement/search", params)
goldenFilePath = "data/searchActivites.json.gz"
processGoldenFile(t, goldenFilePath, indented)
}
func TestScores(t *testing.T) {
t.Log("/scores/liste retourne le même résultat qu'attendu")
Expand Down
2 changes: 1 addition & 1 deletion test/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ cd workspace
DATAPI_PID=$!
sleep 2

if [ "$1" = '-u' ]; then rm ../data/*.json.gz; GOLDEN_UPDATE=true; else GOLDEN_UPDATE=false; fi
if [ "$1" = '-u' ]; then rm -f ../data/*.json.gz; GOLDEN_UPDATE=true; else GOLDEN_UPDATE=false; fi

if [ "$1" = '-w' ];
then echo "Environnement en attente, commande à exécuter pour les tests"
Expand Down

0 comments on commit 0af0a0f

Please sign in to comment.