Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

15603 - Initial changes for adding in displayName for legalName changes (SP/GP) #2690

Merged
merged 6 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion auth-api/src/auth_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
from auth_api.extensions import mail
from auth_api.models import db, ma
from auth_api.resources import endpoints
from auth_api.services.flags import flags
from auth_api.utils.cache import cache
from auth_api.utils.run_version import get_run_version
from auth_api.utils.util_logging import setup_logging
from auth_api.services import flags

setup_logging(os.path.join(_Config.PROJECT_ROOT, 'logging.conf')) # important to do this first

Expand Down
6 changes: 6 additions & 0 deletions auth-api/src/auth_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ class _Config: # pylint: disable=too-few-public-methods
LEGAL_API_VERSION_2 = os.getenv('LEGAL_API_VERSION_2', '')

LEAR_AFFILIATION_DETAILS_URL = f'{LEGAL_API_URL + LEGAL_API_VERSION_2}/businesses/search'

# Temporary until legal names is implemented.
LEGAL_API_ALTERNATE_URL = os.getenv('LEGAL_API_ALTERNATE_URL', '')
# Temporary until legal names is implemented.
LEAR_ALTERNATE_AFFILIATION_DETAILS_URL = f'{LEGAL_API_ALTERNATE_URL + LEGAL_API_VERSION_2}/businesses/search'

NAMEX_AFFILIATION_DETAILS_URL = f'{NAMEX_API_URL}/requests/search'

# NATS Config
Expand Down
11 changes: 0 additions & 11 deletions auth-api/src/auth_api/models/affiliation.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@
from __future__ import annotations
from typing import List

from flask import current_app
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import contains_eager, relationship

from auth_api.utils.enums import CorpType

from .base_model import VersionedModel
from .db import db
from .entity import Entity as EntityModel
Expand Down Expand Up @@ -53,14 +50,6 @@ def filter_environment(cls, environment: str):
query = query.filter(Affiliation.environment.is_(None))
return query

@property
def affiliation_details_url(self) -> str:
"""The url of the source service containing the affiliations full data."""
if self.entity.corp_type_code == CorpType.NR.value:
return current_app.config.get('NAMEX_AFFILIATION_DETAILS_URL')
# only have LEAR and NAMEX affiliations
return current_app.config.get('LEAR_AFFILIATION_DETAILS_URL')

@classmethod
def find_affiliation_by_org_and_entity_ids(cls, org_id, entity_id, environment) -> Affiliation:
"""Return an affiliation for the provided org and entity ids."""
Expand Down
2 changes: 0 additions & 2 deletions auth-api/src/auth_api/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,3 @@
from .user import User
from .user_settings import UserSettings
from .flags import Flags

flags = Flags()
38 changes: 31 additions & 7 deletions auth-api/src/auth_api/services/affiliation.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from auth_api.models.membership import Membership as MembershipModel
from auth_api.schemas import AffiliationSchema
from auth_api.services.entity import Entity as EntityService
from auth_api.services.flags import flags
from auth_api.services.org import Org as OrgService
from auth_api.services.user import User as UserService
from auth_api.utils.enums import ActivityAction, CorpType, NRActionCodes, NRNameStatus, NRStatus
Expand Down Expand Up @@ -392,12 +393,23 @@ def fix_stale_affiliations(org_id: int, entity_details: Dict, environment: str =

current_app.logger.debug('>fix_stale_affiliations')

@staticmethod
def _affiliation_details_url(affiliation: AffiliationModel) -> str:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to move this out of the model, because of circular deps, can't call model -> service, but service -> service is fine.

"""Determine url to call for affiliation details."""
# only have LEAR and NAMEX affiliations
if affiliation.entity.corp_type_code == CorpType.NR.value:
return current_app.config.get('NAMEX_AFFILIATION_DETAILS_URL')
# Temporary until legal names is implemented.
if flags.is_on('enable-alternate-names-mbr', default=False):
return current_app.config.get('LEAR_ALTERNATE_AFFILIATION_DETAILS_URL')
return current_app.config.get('LEAR_AFFILIATION_DETAILS_URL')

@staticmethod
async def get_affiliation_details(affiliations: List[AffiliationModel]) -> List:
"""Return affiliation details by calling the source api."""
url_identifiers = {} # i.e. turns into { url: [identifiers...] }
for affiliation in affiliations:
url = affiliation.affiliation_details_url
url = Affiliation._affiliation_details_url(affiliation)
url_identifiers.setdefault(url, [affiliation.entity.business_identifier])\
.append(affiliation.entity.business_identifier)

Expand Down Expand Up @@ -428,8 +440,7 @@ def sort_key(item):
raise ServiceUnavailableException('Failed to get affiliation details') from err

@staticmethod
def _combine_affiliation_details(details):
"""Parse affiliation details responses and combine draft entities with NRs if applicable."""
def _group_details(details):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactor to make this easier to deal with

name_requests = {}
businesses = []
drafts = []
Expand All @@ -450,17 +461,24 @@ def _combine_affiliation_details(details):
drafts = [
{'draftType': CorpType.RTMP.value if draft['legalType'] in draft_reg_types
else CorpType.TMP.value, **draft} for draft in data[drafts_key]]
return name_requests, businesses, drafts

@staticmethod
def _update_draft_type_for_amalgamation_nr(business):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactor to make this easier to deal with

if business.get('draftType', None) \
and business['nameRequest']['request_action_cd'] == NRActionCodes.AMALGAMATE.value:
business['draftType'] = CorpType.ATMP.value
return business

@staticmethod
def _combine_nrs(name_requests, businesses, drafts):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactor to make this easier to deal with

# combine NRs
for business in drafts + businesses:
# Only drafts have nrNumber coming back from legal-api.
if 'nrNumber' in business and (nr_num := business['nrNumber']):
if business['nrNumber'] in name_requests:
business['nameRequest'] = name_requests[nr_num]['nameRequest']
# Update the draft type if the draft NR request is for amalgamation
if business.get('draftType', None) \
and business['nameRequest']['request_action_cd'] == NRActionCodes.AMALGAMATE.value:
business['draftType'] = CorpType.ATMP.value
business = Affiliation._update_draft_type_for_amalgamation_nr(business)
# Remove the business if the draft associated to the NR is consumed.
if business['nameRequest']['stateCd'] == NRStatus.CONSUMED.value:
drafts.remove(business)
Expand All @@ -471,6 +489,12 @@ def _combine_affiliation_details(details):

return [name_request for nr_num, name_request in name_requests.items()] + drafts + businesses

@staticmethod
def _combine_affiliation_details(details):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactor to make this easier to deal with

"""Parse affiliation details responses and combine draft entities with NRs if applicable."""
name_requests, businesses, drafts = Affiliation._group_details(details)
return Affiliation._combine_nrs(name_requests, businesses, drafts)

@staticmethod
def _get_nr_details(nr_number: str):
"""Return NR details by calling legal-api."""
Expand Down
33 changes: 19 additions & 14 deletions auth-api/src/auth_api/services/flags.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright © 2019 Province of British Columbia
# Copyright © 2022 Province of British Columbia
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
Expand All @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Manage the Feature Flags initialization, setup and service."""
import logging
from flask import current_app
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LD configuration setup the same as PAY now. so they're consistent. It was never used in auth.

from ldclient import get as ldclient_get, set_config as ldclient_set_config # noqa: I001
from ldclient.config import Config # noqa: I005
Expand Down Expand Up @@ -48,24 +49,18 @@ def init_app(self, app):

if self.sdk_key or app.env != 'production':

if app.env == 'production':
config = Config(sdk_key=self.sdk_key)
else:
if app.env == 'testing':
factory = Files.new_data_source(paths=['flags.json'], auto_update=True)
config = Config(sdk_key=self.sdk_key,
update_processor_class=factory,
send_events=False)
else:
config = Config(sdk_key=self.sdk_key)

ldclient_set_config(config)
client = ldclient_get()

app.extensions['featureflags'] = client
app.teardown_appcontext(self.teardown)

def teardown(self, exception): # pylint: disable=unused-argument; flask method signature
"""Destroy all objects created by this extension."""
client = current_app.extensions['featureflags']
client.close()

def _get_client(self):
try:
Expand All @@ -75,6 +70,7 @@ def _get_client(self):
self.init_app(current_app)
client = current_app.extensions['featureflags']
except KeyError:
logging.warning("Couldn\'t retrieve launch darkly client from extensions.")
client = None

return client
Expand All @@ -89,24 +85,33 @@ def _user_as_key(user: User):
.set('firstName', user.firstname)\
.set('lastName', user.lastname).build()

def is_on(self, flag: str, user: User = None) -> bool:
def is_on(self, flag: str, default: bool = False, user: User = None) -> bool:
"""Assert that the flag is set for this user."""
client = self._get_client()

if not client:
return default

if user:
flag_user = self._user_as_key(user)
else:
flag_user = self._get_anonymous_user()

return bool(client.variation(flag, flag_user, None))
return bool(client.variation(flag, flag_user, default))

def value(self, flag: str, user: User = None) -> bool:
def value(self, flag: str, default=None, user: User = None):
"""Retrieve the value of the (flag, user) tuple."""
client = self._get_client()

if not client:
return default

if user:
flag_user = self._user_as_key(user)
else:
flag_user = self._get_anonymous_user()

return client.variation(flag, flag_user, None)
return client.variation(flag, flag_user, default)


flags = Flags()
6 changes: 3 additions & 3 deletions auth-api/tests/unit/services/test_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def setup():
"""Initialize app with dev env for testing."""
global app
app = Flask(__name__)
app.env = 'development'
app.env = 'testing'


def test_flags_constructor_no_app(setup):
Expand Down Expand Up @@ -152,8 +152,8 @@ def test_flags_read_flag_values_unique_user(setup, test_name, flag_name, expecte
with app.app_context():
flags = Flags()
flags.init_app(app)
val = flags.value(flag_name, user)
flag_on = flags.is_on(flag_name, user)
val = flags.value(flag_name, user=user)
flag_on = flags.is_on(flag_name, user=user, default=False)

assert val == expected
assert flag_on
4 changes: 2 additions & 2 deletions auth-web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion auth-web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "auth-web",
"version": "2.4.59",
"version": "2.4.60",
"appName": "Auth Web",
"sbcName": "SBC Common Components",
"private": true,
Expand Down
9 changes: 9 additions & 0 deletions auth-web/src/models/affiliation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,22 @@ export interface AffiliationFilter {
actions?: string
}

export interface AlternateNames {
entityType?: string
identifier?: string
nameRegisteredDate?: string
nameStartDate?: string
operatingName?: string
}

export interface AffiliationResponse {
identifier?: string
draftType?: CorpTypes
legalType?: CorpTypes
businessNumber?: string
name?: string
legalName?: string
alternateNames?: AlternateNames[]
contacts?: Contact[]
corpType?: CorpType
corpSubType?: CorpType
Expand Down
14 changes: 13 additions & 1 deletion auth-web/src/stores/business.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,24 @@ export const useBusinessStore = defineStore('business', () => {
return useOrgStore().currentOrganization
})

function determineDisplayName (resp: AffiliationResponse): string {
if (!LaunchDarklyService.getFlag(LDFlags.AlternateNamesMbr, false)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will remove this after we get rid of feature flag

return resp.legalName
}
if ([CorpTypes.SOLE_PROP, CorpTypes.PARTNERSHIP].includes(resp.legalType)) {
// Intentionally show blank, if the alternate name is not found. This is to avoid showing the legal name.
return resp.alternateNames?.find(alt => alt.identifier === resp.identifier)?.operatingName
} else {
return resp.legalName
}
}

/* Internal function to build the business object. */
function buildBusinessObject (resp: AffiliationResponse): Business {
return {
businessIdentifier: resp.identifier,
...(resp.businessNumber && { businessNumber: resp.businessNumber }),
...(resp.legalName && { name: resp.legalName }),
...(resp.legalName && { name: determineDisplayName(resp) }),
...(resp.contacts && { contacts: resp.contacts }),
...((resp.draftType || resp.legalType) && { corpType: { code: resp.draftType || resp.legalType } }),
...(resp.legalType && { corpSubType: { code: resp.legalType } }),
Expand Down
1 change: 1 addition & 0 deletions auth-web/src/util/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ export enum Permission {
}

export enum LDFlags {
AlternateNamesMbr = 'enable-alternate-names-mbr',
AffiliationInvitationRequestAccess = 'enable-affiliation-invitation-request-access',
BannerText = 'banner-text',
BusSearchLink = 'bus-search-staff-link',
Expand Down
Loading