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

#2564: Revise fixtures to include portfolios - [RJM] #2847

Merged
merged 12 commits into from
Oct 1, 2024

Conversation

rachidatecs
Copy link
Contributor

@rachidatecs rachidatecs commented Sep 24, 2024

Ticket

Resolves #2564

Changes

  • Add portfolios, suborgs
  • Add portfolio permissions
  • Refactor fixtures to use bulk create and update
  • Add default user title and phone
  • Fixtures can be run multiple times
  • Fixtures create data for fixture users only (will avoid blowing up sandboxes where we have full data)

Context for reviewers

The portfolios created are Hotel California and Wish You Were Here
On your sandbox, you may have permissions on an existing portfolio and that would take precedence in org view. Just remove your old permissions.

Setup

Locally: It's straightforward, compose up and then run docker-compose exec app python manage.py load a few times

Sandboxes: login cf login -a api.fr.cloud.gov --sso and run fixtures a few times cf run-task getgov-<sandboxname> --command "./manage.py load" --name fixtures. Keep an eye out for breaking errors (you should not see any) and the data (only issue is you may already have a 'primary' portfolio, see above.

Code Review Verification Steps

As the original developer, I have

Satisfied acceptance criteria and met development standards

  • Met the acceptance criteria, or will meet them in a subsequent PR
  • Created/modified automated tests
  • Added at least 2 developers as PR reviewers (only 1 will need to approve)
  • Messaged on Slack or in standup to notify the team that a PR is ready for review
  • Changes to “how we do things” are documented in READMEs and or onboarding guide
  • If any model was updated to modify/add/delete columns, makemigrations was ran and the associated migrations file has been commited.

Ensured code standards are met (Original Developer)

  • All new functions and methods are commented using plain language
  • Did dependency updates in Pipfile also get changed in requirements.txt?
  • Interactions with external systems are wrapped in try/except
  • Error handling exists for unusual or missing values

Validated user-facing changes (if applicable)

  • New pages have been added to .pa11yci file so that they will be tested with our automated accessibility testing
  • Checked keyboard navigability
  • Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)
  • Add at least 1 designer as PR reviewer

As a code reviewer, I have

Reviewed, tested, and left feedback about the changes

  • Pulled this branch locally and tested it
  • Reviewed this code and left comments
  • Checked that all code is adequately covered by tests
  • Made it clear which comments need to be addressed before this work is merged
  • If any model was updated to modify/add/delete columns, makemigrations was ran and the associated migrations file has been commited.

Ensured code standards are met (Code reviewer)

  • All new functions and methods are commented using plain language
  • Interactions with external systems are wrapped in try/except
  • Error handling exists for unusual or missing values
  • (Rarely needed) Did dependency updates in Pipfile also get changed in requirements.txt?

Validated user-facing changes as a developer

  • New pages have been added to .pa11yci file so that they will be tested with our automated accessibility testing

  • Checked keyboard navigability

  • Meets all designs and user flows provided by design/product

  • Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)

  • Tested with multiple browsers, the suggestion is to use ones that the developer didn't (check off which ones were used)

    • Chrome
    • Microsoft Edge
    • FireFox
    • Safari
  • (Rarely needed) Tested as both an analyst and applicant user

Note: Multiple code reviewers can share the checklists above, a second reviewers should not make a duplicate checklist

As a designer reviewer, I have

Verified that the changes match the design intention

  • Checked that the design translated visually
  • Checked behavior
  • Checked different states (empty, one, some, error)
  • Checked for landmarks, page heading structure, and links
  • Tried to break the intended flow

Validated user-facing changes as a designer

  • Checked keyboard navigability

  • Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)

  • Tested with multiple browsers (check off which ones were used)

    • Chrome
    • Microsoft Edge
    • FireFox
    • Safari
  • (Rarely needed) Tested as both an analyst and applicant user

Screenshots

Copy link

🥳 Successfully deployed to developer sandbox rjm.

Copy link

🥳 Successfully deployed to developer sandbox rjm.

Copy link

🥳 Successfully deployed to developer sandbox rjm.

Copy link

🥳 Successfully deployed to developer sandbox rjm.

Copy link

🥳 Successfully deployed to developer sandbox rjm.

Copy link

🥳 Successfully deployed to developer sandbox rjm.

Copy link

🥳 Successfully deployed to developer sandbox rjm.

Copy link
Contributor

@zandercymatics zandercymatics left a comment

Choose a reason for hiding this comment

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

Nice work. This was a lot!

Overall most of the code suggestions are non-blocking. The only ones that I think ought to change are regarding the list of portfolios that we're creating

Comment on lines +46 to +56
@staticmethod
def _generate_fake_expiration_date(days_in_future=365):
"""Generates a fake expiration date between 1 and 365 days in the future."""
current_date = timezone.now().date() # nosec
return current_date + timedelta(days=random.randint(1, days_in_future)) # nosec

@staticmethod
def _generate_fake_expiration_date_in_past():
"""Generates a fake expiration date up to 365 days in the past."""
current_date = timezone.now().date() # nosec
return current_date + timedelta(days=random.randint(-365, -1)) # nosec
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice touch

src/registrar/fixtures/fixtures_domains.py Outdated Show resolved Hide resolved
src/registrar/fixtures/fixtures_domains.py Show resolved Hide resolved
Comment on lines 98 to 110
# Approve the current and expired domain requests
approved_domain = cls._approve_request(domain_request, users)
expired_domain = cls._approve_request(domain_request_expired, users, is_expired=True)

# Collect objects to update
if domain_request:
domain_requests_to_update.append(domain_request)
if domain_request_expired:
domain_requests_to_update.append(domain_request_expired)
if approved_domain:
domains_to_update.append(approved_domain)
if expired_domain:
domains_to_update.append(expired_domain)
Copy link
Contributor

Choose a reason for hiding this comment

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

(Blocking) Having to collect the domains here by using a DB call (get_or_create) is killing the performance benefits of using bulk_update. There is a much faster way to add an expiration date by using two for loops.

The faster way to do this would be to:

  1. Remove your domain logic in _approve_request
  2. After the first loop, create a variable that stores domains_to_update = Domain.objects.filter(domain_info__domain_request__in= domain_requests_to_update)
  3. Loop through that variable doing your .expired logic (as you probably inferred, just the domain.expiration_date = ( bit)
  4. Do a bulk_update on domains as your already doing

This reduces the number of db calls from being equal to the number of users, down to two.

There may be another way to do this, too. Doesn't have to look any sort of way

def _set_foreign_key_fields(cls, portfolio: Portfolio, portfolio_dict: dict, user: User):
"""Helper method used by `load`."""
if not portfolio.senior_official:
if "senior_official" in portfolio_dict and portfolio_dict["senior_official"] is not None:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if "senior_official" in portfolio_dict and portfolio_dict["senior_official"] is not None:
if portfolio_dict.get("senior_official") is not None:

(nitpick/optional)

to run this code.
"""

SUBORGS = [
Copy link
Contributor

Choose a reason for hiding this comment

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

(optional) Imo I think its okay to add a few suborgs with random names, as was done with the random domain names.

Its okay if not though - but we it would be a nice to have to have two suborgs per org (some features rely on that)

cls._bulk_create_suborgs(suborgs_to_create)

@classmethod
def _get_portfolios(cls):
Copy link
Contributor

Choose a reason for hiding this comment

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

(blocking) this function should get all existing portfolios, rather than using a predefined set of portfolios. This won't scale too well if we add more in fixtures_portfolios

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I disagree. There's no benefit in adding multiple portfolios WITH multiple nested suborgs each, because unlike requests, we do not have multiple variations (such as statuses) to test. If we need to add more portfolios, we can easily do so and they would have no suborgs, which is fine for fixtures.

If we need to add multiple suborgs to a portfolio, we can easily do so on portfolio1 or 2 and that would be sufficient.

Copy link
Contributor

Choose a reason for hiding this comment

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

No need to add multiple suborgs to each - just want to reduce the need to change this list across all files when adding another portfolio

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You can add more portfolios, the suborg fixtures will ignore them

return None

@classmethod
def _prepare_suborgs_to_create(cls, portfolios):
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar thing here. We should avoid hard-coding this sort of data.

I think it'd be ok if you did a for loop that checks on each portfolio, if thats easier. Its not that many records.

The more scalable approach would be:
Checking for existence in that for loop you can define the following right before it: Suborganization.objects.filter(name__in=[suborg.get("name") for suborg in cls.SUBORGS]).in_bulk(field_name="name")

Then just do (in the for loop):

          if not existing_suborgs.get(suborg.get("name")):
              suborgs_to_create.append(Suborganization(portfolio=portfolio_item, name=suborg_item.get("name"))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same comment as above

Comment on lines 35 to 37
portfolios = list(
Portfolio.objects.filter(organization_name__in=["Hotel California", "Wish You Were Here"])
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same thing here with linking it to the fixtures_portfolios fixture

},
{
"username": "30001ee7-0467-4df2-8db2-786e79606060",
"first_name": "Zander",
"last_name": "Adkinson",
"title": "ACME specialist",
Copy link
Contributor

Choose a reason for hiding this comment

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

These are fun

@zandercymatics zandercymatics self-assigned this Sep 26, 2024
Copy link

🥳 Successfully deployed to developer sandbox rjm.

1 similar comment
Copy link

🥳 Successfully deployed to developer sandbox rjm.

Copy link

🥳 Successfully deployed to developer sandbox rjm.

Copy link

github-actions bot commented Oct 1, 2024

🥳 Successfully deployed to developer sandbox rjm.

@rachidatecs rachidatecs merged commit d73d611 into main Oct 1, 2024
10 checks passed
@rachidatecs rachidatecs deleted the rjm/2564-fixtures branch October 1, 2024 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update fixtures script to add portfolios an other missing items
3 participants