✨(anct) fetch and display organization names of communes
ANCT-specific extraction of organization names for communes, front end changes to match.
This commit is contained in:
committed by
Laurent Bossavit
parent
d495ef3e19
commit
20cc173e93
@@ -10,6 +10,7 @@ and this project adheres to
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(anct) fetch and display organization names of communes #583
|
||||
- ✨(frontend) display email if no username #562
|
||||
- 🧑💻(oidc) add ability to pull registration ID (e.g. SIRET) from OIDC #577
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"username": "e2e.marie",
|
||||
"email": "marie.varzy@gmail.com",
|
||||
"firstName": "Marie",
|
||||
"lastName": "Devarzy",
|
||||
"lastName": "Delamairie",
|
||||
"enabled": true,
|
||||
"attributes": {
|
||||
"siret": "21580304000017"
|
||||
|
||||
@@ -641,6 +641,8 @@ class Development(Base):
|
||||
|
||||
OIDC_ORGANIZATION_REGISTRATION_ID_FIELD = "siret"
|
||||
|
||||
ORGANIZATION_PLUGINS = ["plugins.organizations.NameFromSiretOrganizationPlugin"]
|
||||
|
||||
def __init__(self):
|
||||
"""In dev, force installs needed for Swagger API."""
|
||||
# pylint: disable=invalid-name
|
||||
@@ -686,6 +688,10 @@ class Test(Base):
|
||||
# this is a dev credentials for mail provisioning API
|
||||
MAIL_PROVISIONING_API_CREDENTIALS = "bGFfcmVnaWU6cGFzc3dvcmQ="
|
||||
|
||||
OIDC_ORGANIZATION_REGISTRATION_ID_FIELD = "siret"
|
||||
|
||||
ORGANIZATION_PLUGINS = ["plugins.organizations.NameFromSiretOrganizationPlugin"]
|
||||
|
||||
ORGANIZATION_REGISTRATION_ID_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.core.validators.RegexValidator",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter, Retry
|
||||
|
||||
from core.plugins.base import BaseOrganizationPlugin
|
||||
|
||||
@@ -33,20 +34,17 @@ class NameFromSiretOrganizationPlugin(BaseOrganizationPlugin):
|
||||
logger.warning("Empty list 'liste_enseignes' in %s", organization_data)
|
||||
return None
|
||||
|
||||
def _get_organization_name_from_siret(self, siret):
|
||||
"""Return the organization name from the SIRET."""
|
||||
try:
|
||||
response = requests.get(self._api_url.format(siret=siret), timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
except requests.RequestException as exc:
|
||||
logger.exception("%s: Unable to fetch organization name from SIRET", exc)
|
||||
return None
|
||||
|
||||
def get_organization_name_from_results(self, data, siret):
|
||||
"""Return the organization name from the results of a SIRET search."""
|
||||
for result in data["results"]:
|
||||
nature = "nature_juridique"
|
||||
commune = nature in result and result[nature] == "7210"
|
||||
for organization in result["matching_etablissements"]:
|
||||
if organization.get("siret") == siret:
|
||||
return self._extract_name_from_organization_data(organization)
|
||||
if commune:
|
||||
return organization["libelle_commune"].title()
|
||||
|
||||
return self._extract_name_from_organization_data(organization)
|
||||
|
||||
logger.warning("No organization name found for SIRET %s", siret)
|
||||
return None
|
||||
@@ -63,10 +61,21 @@ class NameFromSiretOrganizationPlugin(BaseOrganizationPlugin):
|
||||
|
||||
# In the nominal case, there is only one registration ID because
|
||||
# the organization as been created from it.
|
||||
name = self._get_organization_name_from_siret(
|
||||
organization.registration_id_list[0]
|
||||
)
|
||||
if not name:
|
||||
try:
|
||||
# Retry logic as the API may be rate limited
|
||||
s = requests.Session()
|
||||
retries = Retry(total=5, backoff_factor=0.1, status_forcelist=[429])
|
||||
s.mount("https://", HTTPAdapter(max_retries=retries))
|
||||
|
||||
siret = organization.registration_id_list[0]
|
||||
response = s.get(self._api_url.format(siret=siret), timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
name = self.get_organization_name_from_results(data, siret)
|
||||
if not name:
|
||||
return
|
||||
except requests.RequestException as exc:
|
||||
logger.exception("%s: Unable to fetch organization name from SIRET", exc)
|
||||
return
|
||||
|
||||
organization.name = name
|
||||
|
||||
@@ -6,6 +6,8 @@ import responses
|
||||
from core.models import Organization
|
||||
from core.plugins.loader import get_organization_plugins
|
||||
|
||||
from plugins.organizations import NameFromSiretOrganizationPlugin
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@@ -135,3 +137,34 @@ def test_organization_plugins_run_after_create_name_already_set(
|
||||
name="Magic WOW", registration_id_list=["12345678901234"]
|
||||
)
|
||||
assert organization.name == "Magic WOW"
|
||||
|
||||
|
||||
def test_extract_name_from_org_data_when_commune(
|
||||
organization_plugins_settings,
|
||||
):
|
||||
"""Test the name is extracted correctly for a French commune."""
|
||||
data = {
|
||||
"results": [
|
||||
{
|
||||
"nom_complet": "COMMUNE DE VARZY",
|
||||
"nom_raison_sociale": "COMMUNE DE VARZY",
|
||||
"siege": {
|
||||
"libelle_commune": "VARZY",
|
||||
"liste_enseignes": ["MAIRIE"],
|
||||
"siret": "21580304000017",
|
||||
},
|
||||
"nature_juridique": "7210",
|
||||
"matching_etablissements": [
|
||||
{
|
||||
"siret": "21580304000017",
|
||||
"libelle_commune": "VARZY",
|
||||
"liste_enseignes": ["MAIRIE"],
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
plugin = NameFromSiretOrganizationPlugin()
|
||||
name = plugin.get_organization_name_from_results(data, "21580304000017")
|
||||
assert name == "Varzy"
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name?: string;
|
||||
organization?: Organization;
|
||||
abilities?: {
|
||||
mailboxes: UserAbilities;
|
||||
contacts: UserAbilities;
|
||||
@@ -16,6 +17,12 @@ export interface User {
|
||||
};
|
||||
}
|
||||
|
||||
export interface Organization {
|
||||
id: string;
|
||||
name: string;
|
||||
registration_id_list: [string];
|
||||
}
|
||||
|
||||
export type UserAbilities = {
|
||||
can_view?: boolean;
|
||||
can_create?: boolean;
|
||||
|
||||
@@ -14,7 +14,12 @@ export const AccountDropdown = () => {
|
||||
<DropButton
|
||||
button={
|
||||
<Box $flex $direction="row" $align="center">
|
||||
<Text $theme="primary">{userName}</Text>
|
||||
<Box $flex $direction="column" $align="left">
|
||||
<Text $theme="primary">{userName}</Text>
|
||||
{userData?.organization?.registration_id_list?.at(0) && (
|
||||
<Text $theme="primary">{userData?.organization?.name}</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Text className="material-icons" $theme="primary" aria-hidden="true">
|
||||
arrow_drop_down
|
||||
</Text>
|
||||
|
||||
@@ -9,9 +9,6 @@ test.beforeEach(async ({ page, browserName }) => {
|
||||
|
||||
test.describe('OIDC interop with SIRET', () => {
|
||||
test('it checks the SIRET is displayed in /me endpoint', async ({ page }) => {
|
||||
const header = page.locator('header').first();
|
||||
await expect(header.getByAltText('Marianne Logo')).toBeVisible();
|
||||
|
||||
const response = await page.request.get(
|
||||
'http://localhost:8071/api/v1.0/users/me/',
|
||||
);
|
||||
@@ -21,3 +18,16 @@ test.describe('OIDC interop with SIRET', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('When a commune, display commune name below user name', () => {
|
||||
test('it checks the name is added below the user name', async ({ page }) => {
|
||||
const header = page.locator('header').first();
|
||||
await expect(header.getByAltText('Marianne Logo')).toBeVisible();
|
||||
|
||||
const logout = page.getByRole('button', {
|
||||
name: 'Marie Delamairie',
|
||||
});
|
||||
|
||||
await expect(logout.getByText('Varzy')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,7 +55,7 @@ backend:
|
||||
OIDC_RP_SCOPES: "openid email siret"
|
||||
OIDC_REDIRECT_ALLOWED_HOSTS: https://desk-preprod.beta.numerique.gouv.fr
|
||||
OIDC_AUTH_REQUEST_EXTRA_PARAMS: "{'acr_values': 'eidas1'}"
|
||||
ORGANIZATION_PLUGINS: "plugins.organizations.NameFromSiretOrganizationPlugin"
|
||||
ORGANIZATION_PLUGINS: ["plugins.organizations.NameFromSiretOrganizationPlugin"]
|
||||
ORGANIZATION_REGISTRATION_ID_VALIDATORS: '[{"NAME": "django.core.validators.RegexValidator", "OPTIONS": {"regex": "^[0-9]{14}$"}}]'
|
||||
LOGIN_REDIRECT_URL: https://desk-preprod.beta.numerique.gouv.fr
|
||||
LOGIN_REDIRECT_URL_FAILURE: https://desk-preprod.beta.numerique.gouv.fr
|
||||
|
||||
@@ -44,7 +44,7 @@ backend:
|
||||
OIDC_OP_TOKEN_ENDPOINT: https://auth.agentconnect.gouv.fr/api/v2/token
|
||||
OIDC_OP_USER_ENDPOINT: https://auth.agentconnect.gouv.fr/api/v2/userinfo
|
||||
OIDC_OP_LOGOUT_ENDPOINT: https://auth.agentconnect.gouv.fr/api/v2/session/end
|
||||
ORGANIZATION_PLUGINS: "plugins.organizations.NameFromSiretOrganizationPlugin"
|
||||
ORGANIZATION_PLUGINS: ["plugins.organizations.NameFromSiretOrganizationPlugin"]
|
||||
OIDC_ORGANIZATION_REGISTRATION_ID_FIELD: "siret"
|
||||
OIDC_RP_CLIENT_ID:
|
||||
secretKeyRef:
|
||||
|
||||
Reference in New Issue
Block a user