Skip to content

Commit

Permalink
✨(anct) fetch and display organization names of communes
Browse files Browse the repository at this point in the history
ANCT-specific extraction of organization names for communes, front
end changes to match.
  • Loading branch information
Laurent Bossavit authored and Laurent Bossavit committed Jan 3, 2025
1 parent 9ba3084 commit b940300
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to

### Added

- ✨(anct) fetch and display organization names of communes #583
- 🧑‍💻(oidc) add ability to pull registration ID (e.g. SIRET) from OIDC #577

### Fixed
Expand Down
6 changes: 6 additions & 0 deletions src/backend/people/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down
33 changes: 18 additions & 15 deletions src/backend/plugins/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,17 @@ def _extract_name_from_organization_data(organization_data):
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
Expand All @@ -63,10 +60,16 @@ def run_after_create(self, organization):

# 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:
siret = organization.registration_id_list[0]
response = requests.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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from core.models import Organization
from core.plugins.loader import get_organization_plugins

from plugins.organizations import NameFromSiretOrganizationPlugin

pytestmark = pytest.mark.django_db


Expand Down Expand Up @@ -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"
7 changes: 7 additions & 0 deletions src/frontend/apps/desk/src/core/auth/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@ export interface User {
id: string;
email: string;
name?: string;
organization?: Organization;
abilities?: {
mailboxes: UserAbilities;
contacts: UserAbilities;
teams: UserAbilities;
};
}

export interface Organization {
id: string;
name: string;
registration_id_list: [string];
}

export type UserAbilities = {
can_view?: boolean;
can_create?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
13 changes: 13 additions & 0 deletions src/frontend/apps/e2e/__tests__/app-desk/siret.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,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: new RegExp(`Marie Devarzy`, 'i'),
});

await expect(logout.getByText('Varzy')).toBeVisible();
});
});
2 changes: 1 addition & 1 deletion src/helm/env.d/preprod/values.desk.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/helm/env.d/production/values.desk.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit b940300

Please sign in to comment.