mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-19 02:49:21 +02:00
Merge remote-tracking branch 'origin/main' into el/2762-add-liz-to-fixture
This commit is contained in:
commit
4205c33e3f
12 changed files with 270 additions and 102 deletions
51
.github/pull_request_template.md
vendored
51
.github/pull_request_template.md
vendored
|
@ -1,11 +1,7 @@
|
||||||
## Ticket
|
## Ticket
|
||||||
|
|
||||||
|
<!-- PR title format: `#issue_number: Descriptive name ideally matching ticket name - [sandbox]`-->
|
||||||
Resolves #00
|
Resolves #00
|
||||||
<!--Reminder, when a code change is made that is user facing, beyond content updates, then the following are required:
|
|
||||||
- a developer approves the PR
|
|
||||||
- a designer approves the PR or checks off all relevant items in this checklist.
|
|
||||||
|
|
||||||
All other changes require just a single approving review.-->
|
|
||||||
|
|
||||||
## Changes
|
## Changes
|
||||||
|
|
||||||
|
@ -45,82 +41,63 @@ All other changes require just a single approving review.-->
|
||||||
|
|
||||||
- [ ] Met the acceptance criteria, or will meet them in a subsequent PR
|
- [ ] Met the acceptance criteria, or will meet them in a subsequent PR
|
||||||
- [ ] Created/modified automated tests
|
- [ ] Created/modified automated tests
|
||||||
- [ ] Added at least 2 developers as PR reviewers (only 1 will need to approve)
|
- [ ] Update documentation in READMEs and/or onboarding guide
|
||||||
- [ ] 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)
|
#### Ensured code standards are met (Original Developer)
|
||||||
|
<!-- Mark "- N/A" and check at the end of each check that is not applicable to your PR -->
|
||||||
- [ ] All new functions and methods are commented using plain language
|
- [ ] If any updated dependencies on Pipfile, also update dependencies in requirements.txt.
|
||||||
- [ ] Did dependency updates in Pipfile also get changed in requirements.txt?
|
|
||||||
- [ ] Interactions with external systems are wrapped in try/except
|
- [ ] Interactions with external systems are wrapped in try/except
|
||||||
- [ ] Error handling exists for unusual or missing values
|
- [ ] Error handling exists for unusual or missing values
|
||||||
|
|
||||||
#### Validated user-facing changes (if applicable)
|
#### 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
|
- [ ] Tag @dotgov-designers in this PR's Reviewers for design review. If code is not user-facing, delete design reviewer checklist
|
||||||
|
- [ ] Verify new pages have been added to .pa11yci file so that they will be tested with our automated accessibility testing
|
||||||
- [ ] Checked keyboard navigability
|
- [ ] Checked keyboard navigability
|
||||||
- [ ] Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)
|
- [ ] 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
|
### As a code reviewer, I have
|
||||||
|
|
||||||
#### Reviewed, tested, and left feedback about the changes
|
#### Reviewed, tested, and left feedback about the changes
|
||||||
|
|
||||||
- [ ] Pulled this branch locally and tested it
|
- [ ] Pulled this branch locally and tested it
|
||||||
- [ ] Reviewed this code and left comments
|
- [ ] Verified code meets all checks above. Address any checks that are not satisfied
|
||||||
|
- [ ] Reviewed this code and left comments. Indicate if comments must be addressed before code is merged
|
||||||
- [ ] Checked that all code is adequately covered by tests
|
- [ ] Checked that all code is adequately covered by tests
|
||||||
- [ ] Made it clear which comments need to be addressed before this work is merged
|
- [ ] Verify migrations are valid and do not conflict with existing migrations
|
||||||
- [ ] 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
|
#### Validated user-facing changes as a developer
|
||||||
|
**Note:** Multiple code reviewers can share the checklists above, a second reviewer should not make a duplicate checklist. All checks should be checked before approving, even those labeled N/A.
|
||||||
|
|
||||||
- [ ] New pages have been added to .pa11yci file so that they will be tested with our automated accessibility testing
|
- [ ] New pages have been added to .pa11yci file so that they will be tested with our automated accessibility testing
|
||||||
- [ ] Checked keyboard navigability
|
- [ ] Checked keyboard navigability
|
||||||
- [ ] Meets all designs and user flows provided by design/product
|
- [ ] 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 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
|
- [ ] (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
|
### As a designer reviewer, I have
|
||||||
|
|
||||||
#### Verified that the changes match the design intention
|
#### Verified that the changes match the design intention
|
||||||
|
|
||||||
- [ ] Checked that the design translated visually
|
- [ ] Checked that the design translated visually
|
||||||
- [ ] Checked behavior
|
- [ ] Checked behavior. Comment any found issues or broken flows.
|
||||||
- [ ] Checked different states (empty, one, some, error)
|
- [ ] Checked different states (empty, one, some, error)
|
||||||
- [ ] Checked for landmarks, page heading structure, and links
|
- [ ] Checked for landmarks, page heading structure, and links
|
||||||
- [ ] Tried to break the intended flow
|
|
||||||
|
|
||||||
#### Validated user-facing changes as a designer
|
#### Validated user-facing changes as a designer
|
||||||
|
|
||||||
- [ ] Checked keyboard navigability
|
- [ ] Checked keyboard navigability
|
||||||
- [ ] Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)
|
- [ ] 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)
|
- [ ] Tested with multiple browsers (check off which ones were used)
|
||||||
- [ ] Chrome
|
- [ ] Chrome
|
||||||
- [ ] Microsoft Edge
|
- [ ] Microsoft Edge
|
||||||
- [ ] FireFox
|
- [ ] FireFox
|
||||||
- [ ] Safari
|
- [ ] Safari
|
||||||
|
|
||||||
- [ ] (Rarely needed) Tested as both an analyst and applicant user
|
- [ ] (Rarely needed) Tested as both an analyst and applicant user
|
||||||
|
|
||||||
|
### References
|
||||||
|
- [Code review best practices](../docs/dev-practices/code_review.md)
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<!-- If this PR makes visible interface changes, an image of the finished interface can help reviewers
|
<!-- If this PR makes visible interface changes, an image of the finished interface can help reviewers
|
||||||
|
|
|
@ -14,17 +14,6 @@ There are a handful of things we do not commit to the repository:
|
||||||
For developers, you can auto-deploy your code to your sandbox (if applicable) by naming your branch thusly: jsd/123-feature-description
|
For developers, you can auto-deploy your code to your sandbox (if applicable) by naming your branch thusly: jsd/123-feature-description
|
||||||
Where 'jsd' stands for your initials and sandbox environment name (if you were called John Smith Doe), and 123 matches the ticket number if applicable.
|
Where 'jsd' stands for your initials and sandbox environment name (if you were called John Smith Doe), and 123 matches the ticket number if applicable.
|
||||||
|
|
||||||
## Approvals
|
|
||||||
|
|
||||||
When a code change is made that is not user facing, then the following is required:
|
|
||||||
- a developer approves the PR
|
|
||||||
|
|
||||||
When a code change is made that is user facing, beyond content updates, then the following are required:
|
|
||||||
- a developer approves the PR
|
|
||||||
- a designer approves the PR or checks off all relevant items in this checklist
|
|
||||||
|
|
||||||
Content or document updates require a single person to approve.
|
|
||||||
|
|
||||||
## Project Management
|
## Project Management
|
||||||
|
|
||||||
We use [Github Projects](https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/about-projects) for project management and tracking.
|
We use [Github Projects](https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/about-projects) for project management and tracking.
|
||||||
|
@ -39,14 +28,6 @@ Every issue in this respository and on the project board should be appropriately
|
||||||
|
|
||||||
We also have labels for each discipline and for research and project management related tasks. While this repository and project board track development work, we try to document all work related to the project here as well.
|
We also have labels for each discipline and for research and project management related tasks. While this repository and project board track development work, we try to document all work related to the project here as well.
|
||||||
|
|
||||||
## Pull request etiquette
|
|
||||||
|
|
||||||
- The submitter is in charge of merging their PRs unless the approver is given explicit permission.
|
|
||||||
- Do not commit to another person's branch unless given explicit permission.
|
|
||||||
- Keep pull requests as small as possible. This makes them easier to review and track changes.
|
|
||||||
- Bias towards approving i.e. "good to merge once X is fixed" rather than blocking until X is fixed, requiring an additional review.
|
|
||||||
- Write descriptive pull requests. This is not only something that makes it easier to review, but is a great source of documentation.
|
|
||||||
|
|
||||||
## Branch Naming
|
## Branch Naming
|
||||||
|
|
||||||
Our branch naming convention is `name/topic-or-feature`, for example: `lmm/add-contributing-doc`.
|
Our branch naming convention is `name/issue_no-description`, for example: `lmm/1234-add-contributing-doc`.
|
||||||
|
|
31
docs/dev-practices/code_review.md
Normal file
31
docs/dev-practices/code_review.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
## Code Review
|
||||||
|
|
||||||
|
Pull requests should be titled in the format of `#issue_number: Descriptive name ideally matching ticket name - [sandbox]`
|
||||||
|
Pull requests including a migration should be suffixed with ` - MIGRATION`
|
||||||
|
|
||||||
|
After creating a pull request, pull request submitters should:
|
||||||
|
- Add at least 2 developers as PR reviewers (only 1 will need to approve).
|
||||||
|
- Message on Slack or in standup to notify the team that a PR is ready for review.
|
||||||
|
- If any model was updated to modify/add/delete columns, run makemigrations and commit the associated migrations file.
|
||||||
|
|
||||||
|
## Pull request approvals
|
||||||
|
Code changes on user-facing features (excluding content updates) require approval from at least one developer and one designer.
|
||||||
|
All other changes require a single approving review.
|
||||||
|
|
||||||
|
The submitter is responsible for merging their PR unless the approver is given explicit permission. Similarly, do not commit to another person's branch unless given explicit permission.
|
||||||
|
|
||||||
|
Bias towards approving i.e. "good to merge once X is fixed" rather than blocking until X is fixed, requiring an additional review.
|
||||||
|
|
||||||
|
## Pull Requests for User-facing changes
|
||||||
|
When making or reviewing user-facing changes, test that your changes work on multiple browsers including Chrome, Microsoft Edge, Firefox, and Safari.
|
||||||
|
|
||||||
|
Add new pages to the .pa11yci file so they are included in our automated accessibility testing.
|
||||||
|
|
||||||
|
## Other Pull request norms
|
||||||
|
- Keep pull requests as small as possible. This makes them easier to review and track changes.
|
||||||
|
- Write descriptive pull requests. This is not only something that makes it easier to review, but is a great source of documentation.
|
||||||
|
|
||||||
|
## Coding standards
|
||||||
|
|
||||||
|
### Plain language
|
||||||
|
All functions and methods should use plain language.
|
|
@ -25,6 +25,8 @@
|
||||||
/**
|
/**
|
||||||
* Edits made for dotgov project:
|
* Edits made for dotgov project:
|
||||||
* - tooltip exposed to window to be accessible in other js files
|
* - tooltip exposed to window to be accessible in other js files
|
||||||
|
* - tooltip positioning logic updated to allow position:fixed
|
||||||
|
* - tooltip dynamic content updated to include nested element (for better sizing control)
|
||||||
* - modal exposed to window to be accessible in other js files
|
* - modal exposed to window to be accessible in other js files
|
||||||
* - fixed bug in createHeaderButton which added newlines to header button tooltips
|
* - fixed bug in createHeaderButton which added newlines to header button tooltips
|
||||||
*/
|
*/
|
||||||
|
@ -5938,6 +5940,22 @@ const showToolTip = (tooltipBody, tooltipTrigger, position) => {
|
||||||
return offset;
|
return offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- DOTGOV EDIT (Added section)
|
||||||
|
// DOTGOV: Tooltip positioning logic updated to allow position:fixed
|
||||||
|
const tooltipStyle = window.getComputedStyle(tooltipBody);
|
||||||
|
const tooltipIsFixedPositioned = tooltipStyle.position === 'fixed';
|
||||||
|
const triggerRect = tooltipTrigger.getBoundingClientRect(); //detect if tooltip is set to "fixed" position
|
||||||
|
const targetLeft = tooltipIsFixedPositioned ? triggerRect.left + triggerRect.width/2 + 'px': `50%`
|
||||||
|
const targetTop = tooltipIsFixedPositioned ? triggerRect.top + triggerRect.height/2 + 'px': `50%`
|
||||||
|
if (tooltipIsFixedPositioned) {
|
||||||
|
/* DOTGOV: Add listener to handle scrolling if tooltip position = 'fixed'
|
||||||
|
(so that the tooltip doesn't appear to stick to the screen) */
|
||||||
|
window.addEventListener('scroll', function() {
|
||||||
|
findBestPosition(tooltipBody)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// ---- END DOTGOV EDIT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Positions tooltip at the top
|
* Positions tooltip at the top
|
||||||
* @param {HTMLElement} e - this is the tooltip body
|
* @param {HTMLElement} e - this is the tooltip body
|
||||||
|
@ -5949,8 +5967,16 @@ const showToolTip = (tooltipBody, tooltipTrigger, position) => {
|
||||||
const topMargin = calculateMarginOffset("top", e.offsetHeight, tooltipTrigger);
|
const topMargin = calculateMarginOffset("top", e.offsetHeight, tooltipTrigger);
|
||||||
const leftMargin = calculateMarginOffset("left", e.offsetWidth, tooltipTrigger);
|
const leftMargin = calculateMarginOffset("left", e.offsetWidth, tooltipTrigger);
|
||||||
setPositionClass("top");
|
setPositionClass("top");
|
||||||
e.style.left = `50%`; // center the element
|
|
||||||
e.style.top = `-${TRIANGLE_SIZE}px`; // consider the pseudo element
|
// ---- DOTGOV EDIT
|
||||||
|
// e.style.left = `50%`; // center the element
|
||||||
|
// e.style.top = `-${TRIANGLE_SIZE}px`; // consider the pseudo element
|
||||||
|
|
||||||
|
// DOTGOV: updated logic for position:fixed
|
||||||
|
e.style.left = targetLeft; // center the element
|
||||||
|
e.style.top = tooltipIsFixedPositioned ?`${triggerRect.top-TRIANGLE_SIZE}px`:`-${TRIANGLE_SIZE}px`; // consider the pseudo element
|
||||||
|
// ---- END DOTGOV EDIT
|
||||||
|
|
||||||
// apply our margins based on the offset
|
// apply our margins based on the offset
|
||||||
e.style.margin = `-${topMargin}px 0 0 -${leftMargin / 2}px`;
|
e.style.margin = `-${topMargin}px 0 0 -${leftMargin / 2}px`;
|
||||||
};
|
};
|
||||||
|
@ -5963,7 +5989,17 @@ const showToolTip = (tooltipBody, tooltipTrigger, position) => {
|
||||||
resetPositionStyles(e);
|
resetPositionStyles(e);
|
||||||
const leftMargin = calculateMarginOffset("left", e.offsetWidth, tooltipTrigger);
|
const leftMargin = calculateMarginOffset("left", e.offsetWidth, tooltipTrigger);
|
||||||
setPositionClass("bottom");
|
setPositionClass("bottom");
|
||||||
e.style.left = `50%`;
|
|
||||||
|
// ---- DOTGOV EDIT
|
||||||
|
// e.style.left = `50%`;
|
||||||
|
|
||||||
|
// DOTGOV: updated logic for position:fixed
|
||||||
|
if (tooltipIsFixedPositioned){
|
||||||
|
e.style.top = triggerRect.bottom+'px';
|
||||||
|
}
|
||||||
|
// ---- END DOTGOV EDIT
|
||||||
|
|
||||||
|
e.style.left = targetLeft;
|
||||||
e.style.margin = `${TRIANGLE_SIZE}px 0 0 -${leftMargin / 2}px`;
|
e.style.margin = `${TRIANGLE_SIZE}px 0 0 -${leftMargin / 2}px`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5975,8 +6011,16 @@ const showToolTip = (tooltipBody, tooltipTrigger, position) => {
|
||||||
resetPositionStyles(e);
|
resetPositionStyles(e);
|
||||||
const topMargin = calculateMarginOffset("top", e.offsetHeight, tooltipTrigger);
|
const topMargin = calculateMarginOffset("top", e.offsetHeight, tooltipTrigger);
|
||||||
setPositionClass("right");
|
setPositionClass("right");
|
||||||
e.style.top = `50%`;
|
|
||||||
e.style.left = `${tooltipTrigger.offsetLeft + tooltipTrigger.offsetWidth + TRIANGLE_SIZE}px`;
|
// ---- DOTGOV EDIT
|
||||||
|
// e.style.top = `50%`;
|
||||||
|
// e.style.left = `${tooltipTrigger.offsetLeft + tooltipTrigger.offsetWidth + TRIANGLE_SIZE}px`;
|
||||||
|
|
||||||
|
// DOTGOV: updated logic for position:fixed
|
||||||
|
e.style.top = targetTop;
|
||||||
|
e.style.left = tooltipIsFixedPositioned ? `${triggerRect.right + TRIANGLE_SIZE}px`:`${tooltipTrigger.offsetLeft + tooltipTrigger.offsetWidth + TRIANGLE_SIZE}px`;
|
||||||
|
// ---- END DOTGOV EDIT
|
||||||
|
|
||||||
e.style.margin = `-${topMargin / 2}px 0 0 0`;
|
e.style.margin = `-${topMargin / 2}px 0 0 0`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5991,8 +6035,16 @@ const showToolTip = (tooltipBody, tooltipTrigger, position) => {
|
||||||
// we have to check for some utility margins
|
// we have to check for some utility margins
|
||||||
const leftMargin = calculateMarginOffset("left", tooltipTrigger.offsetLeft > e.offsetWidth ? tooltipTrigger.offsetLeft - e.offsetWidth : e.offsetWidth, tooltipTrigger);
|
const leftMargin = calculateMarginOffset("left", tooltipTrigger.offsetLeft > e.offsetWidth ? tooltipTrigger.offsetLeft - e.offsetWidth : e.offsetWidth, tooltipTrigger);
|
||||||
setPositionClass("left");
|
setPositionClass("left");
|
||||||
e.style.top = `50%`;
|
|
||||||
e.style.left = `-${TRIANGLE_SIZE}px`;
|
// ---- DOTGOV EDIT
|
||||||
|
// e.style.top = `50%`;
|
||||||
|
// e.style.left = `-${TRIANGLE_SIZE}px`;
|
||||||
|
|
||||||
|
// DOTGOV: updated logic for position:fixed
|
||||||
|
e.style.top = targetTop;
|
||||||
|
e.style.left = tooltipIsFixedPositioned ? `${triggerRect.left-TRIANGLE_SIZE}px` : `-${TRIANGLE_SIZE}px`;
|
||||||
|
// ---- END DOTGOV EDIT
|
||||||
|
|
||||||
e.style.margin = `-${topMargin / 2}px 0 0 ${tooltipTrigger.offsetLeft > e.offsetWidth ? leftMargin : -leftMargin}px`; // adjust the margin
|
e.style.margin = `-${topMargin / 2}px 0 0 ${tooltipTrigger.offsetLeft > e.offsetWidth ? leftMargin : -leftMargin}px`; // adjust the margin
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6017,6 +6069,7 @@ const showToolTip = (tooltipBody, tooltipTrigger, position) => {
|
||||||
if (i < positions.length) {
|
if (i < positions.length) {
|
||||||
const pos = positions[i];
|
const pos = positions[i];
|
||||||
pos(element);
|
pos(element);
|
||||||
|
|
||||||
if (!isElementInViewport(element)) {
|
if (!isElementInViewport(element)) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
tryPositions(i += 1);
|
tryPositions(i += 1);
|
||||||
|
@ -6128,7 +6181,17 @@ const setUpAttributes = tooltipTrigger => {
|
||||||
tooltipBody.setAttribute("aria-hidden", "true");
|
tooltipBody.setAttribute("aria-hidden", "true");
|
||||||
|
|
||||||
// place the text in the tooltip
|
// place the text in the tooltip
|
||||||
tooltipBody.textContent = tooltipContent;
|
|
||||||
|
// -- DOTGOV EDIT
|
||||||
|
// tooltipBody.textContent = tooltipContent;
|
||||||
|
|
||||||
|
// DOTGOV: nest the text element to allow us greater control over width and wrapping behavior
|
||||||
|
tooltipBody.innerHTML = `
|
||||||
|
<span class="usa-tooltip__content">
|
||||||
|
${tooltipContent}
|
||||||
|
</span>`
|
||||||
|
// -- END DOTGOV EDIT
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tooltipBody,
|
tooltipBody,
|
||||||
position,
|
position,
|
||||||
|
|
|
@ -28,3 +28,47 @@
|
||||||
#extended-logo .usa-tooltip__body {
|
#extended-logo .usa-tooltip__body {
|
||||||
font-weight: 400 !important;
|
font-weight: 400 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.domains__table {
|
||||||
|
/*
|
||||||
|
Trick tooltips in the domains table to do 2 things...
|
||||||
|
1 - Shrink itself to a padded viewport window
|
||||||
|
(override width and wrapping properties in key areas to constrain tooltip size)
|
||||||
|
2 - NOT be clipped by the table's scrollable view
|
||||||
|
(Set tooltip position to "fixed", which prevents tooltip from being clipped by parent
|
||||||
|
containers. Fixed-position detection was added to uswds positioning logic to update positioning
|
||||||
|
calculations accordingly.)
|
||||||
|
*/
|
||||||
|
.usa-tooltip__body {
|
||||||
|
white-space: inherit;
|
||||||
|
max-width: fit-content; // prevent adjusted widths from being larger than content
|
||||||
|
position: fixed; // prevents clipping by parent containers
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Override width adustments in this dynamically added class
|
||||||
|
(this is original to the javascript handler as a way to shrink tooltip contents within the viewport,
|
||||||
|
but is insufficient for our needs. We cannot simply override its properties
|
||||||
|
because the logic surrounding its dynamic appearance in the DOM does not account
|
||||||
|
for parent containers (basically, this class isn't in the DOM when we need it).
|
||||||
|
Intercept .usa-tooltip__content instead and nullify the effects of
|
||||||
|
.usa-tooltip__body--wrap to prevent conflicts)
|
||||||
|
*/
|
||||||
|
.usa-tooltip__body--wrap {
|
||||||
|
min-width: inherit;
|
||||||
|
width: inherit;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Add width and wrapping to tooltip content in order to confine it to a smaller viewport window.
|
||||||
|
*/
|
||||||
|
.usa-tooltip__content {
|
||||||
|
width: 50vw;
|
||||||
|
text-wrap: wrap;
|
||||||
|
text-align: center;
|
||||||
|
font-size: inherit; //inherit tooltip fontsize of .93rem
|
||||||
|
max-width: fit-content;
|
||||||
|
@include at-media('desktop') {
|
||||||
|
width: 70vw;
|
||||||
|
}
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
|
@ -51,7 +52,8 @@ class UserPortfolioPermissionFixture:
|
||||||
|
|
||||||
user_portfolio_permissions_to_create = []
|
user_portfolio_permissions_to_create = []
|
||||||
for user in users:
|
for user in users:
|
||||||
for portfolio in portfolios:
|
# Assign a random portfolio to a user
|
||||||
|
portfolio = random.choice(portfolios) # nosec
|
||||||
try:
|
try:
|
||||||
if not UserPortfolioPermission.objects.filter(user=user, portfolio=portfolio).exists():
|
if not UserPortfolioPermission.objects.filter(user=user, portfolio=portfolio).exists():
|
||||||
user_portfolio_permission = UserPortfolioPermission(
|
user_portfolio_permission = UserPortfolioPermission(
|
||||||
|
|
|
@ -4,11 +4,31 @@
|
||||||
{% block title %}Add a domain manager | {% endblock %}
|
{% block title %}Add a domain manager | {% endblock %}
|
||||||
|
|
||||||
{% block domain_content %}
|
{% block domain_content %}
|
||||||
|
{% block breadcrumb %}
|
||||||
|
{% url 'domain-users' pk=domain.id as url %}
|
||||||
|
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain manager breadcrumb">
|
||||||
|
<ol class="usa-breadcrumb__list">
|
||||||
|
<li class="usa-breadcrumb__list-item">
|
||||||
|
<a href="{{ url }}" class="usa-breadcrumb__link"><span>Domain managers</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
|
||||||
|
<span>Add a domain manager</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
{% endblock breadcrumb %}
|
||||||
<h1>Add a domain manager</h1>
|
<h1>Add a domain manager</h1>
|
||||||
|
{% if has_organization_feature_flag %}
|
||||||
<p>You can add another user to help manage your domain. They will need to sign
|
<p>
|
||||||
in to the .gov registrar with their Login.gov account.
|
You can add another user to help manage your domain. Users can only be a member of one .gov organization,
|
||||||
|
and they'll need to sign in with their Login.gov account.
|
||||||
</p>
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p>
|
||||||
|
You can add another user to help manage your domain. They will need to sign in to the .gov registrar with
|
||||||
|
their Login.gov account.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<form class="usa-form usa-form--large" method="post" novalidate>
|
<form class="usa-form usa-form--large" method="post" novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
<div class="usa-alert usa-alert--info usa-alert--slim">
|
<div class="usa-alert usa-alert--info usa-alert--slim">
|
||||||
<div class="usa-alert__body">
|
<div class="usa-alert__body">
|
||||||
<p class="usa-alert__text ">
|
<p class="usa-alert__text ">
|
||||||
To manage information for this domain, you must add yourself as a domain manager.
|
You don't have access to manage {{domain.name}}. If you need to make updates, contact one of the listed domain managers.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,8 +8,7 @@
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Domain managers can update all information related to a domain within the
|
Domain managers can update all information related to a domain within the
|
||||||
.gov registrar, including contact details, senior official, security
|
.gov registrar, including including security email and DNS name servers.
|
||||||
email, and DNS name servers.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul class="usa-list">
|
<ul class="usa-list">
|
||||||
|
@ -17,7 +16,8 @@
|
||||||
<li>After adding a domain manager, an email invitation will be sent to that user with
|
<li>After adding a domain manager, an email invitation will be sent to that user with
|
||||||
instructions on how to set up an account.</li>
|
instructions on how to set up an account.</li>
|
||||||
<li>All domain managers must keep their contact information updated and be responsive if contacted by the .gov team.</li>
|
<li>All domain managers must keep their contact information updated and be responsive if contacted by the .gov team.</li>
|
||||||
<li>Domains must have at least one domain manager. You can’t remove yourself as a domain manager if you’re the only one assigned to this domain. Add another domain manager before you remove yourself from this domain.</li>
|
<li>All domain managers will be notified when updates are made to this domain.</li>
|
||||||
|
<li>Domains must have at least one domain manager. You can’t remove yourself as a domain manager if you’re the only one assigned to this domain.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{% if domain.permissions %}
|
{% if domain.permissions %}
|
||||||
|
|
|
@ -340,7 +340,10 @@ class TestDomainDetail(TestDomainOverview):
|
||||||
detail_page = self.client.get(f"/domain/{domain.id}")
|
detail_page = self.client.get(f"/domain/{domain.id}")
|
||||||
# Check that alert message displays properly
|
# Check that alert message displays properly
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
detail_page, "To manage information for this domain, you must add yourself as a domain manager."
|
detail_page,
|
||||||
|
"You don't have access to manage "
|
||||||
|
+ domain.name
|
||||||
|
+ ". If you need to make updates, contact one of the listed domain managers.",
|
||||||
)
|
)
|
||||||
# Check that user does not have option to Edit domain
|
# Check that user does not have option to Edit domain
|
||||||
self.assertNotContains(detail_page, "Edit")
|
self.assertNotContains(detail_page, "Edit")
|
||||||
|
|
|
@ -23,6 +23,15 @@ class InvalidDomainError(ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OutsideOrgMemberError(ValueError):
|
||||||
|
"""
|
||||||
|
Error raised when an org member tries adding a user from a different .gov org.
|
||||||
|
To be deleted when users can be members of multiple orgs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ActionNotAllowed(Exception):
|
class ActionNotAllowed(Exception):
|
||||||
"""User accessed an action that is not
|
"""User accessed an action that is not
|
||||||
allowed by the current state"""
|
allowed by the current state"""
|
||||||
|
|
|
@ -21,8 +21,10 @@ from registrar.models import (
|
||||||
DomainRequest,
|
DomainRequest,
|
||||||
DomainInformation,
|
DomainInformation,
|
||||||
DomainInvitation,
|
DomainInvitation,
|
||||||
|
PortfolioInvitation,
|
||||||
User,
|
User,
|
||||||
UserDomainRole,
|
UserDomainRole,
|
||||||
|
UserPortfolioPermission,
|
||||||
PublicContact,
|
PublicContact,
|
||||||
)
|
)
|
||||||
from registrar.utility.enums import DefaultEmail
|
from registrar.utility.enums import DefaultEmail
|
||||||
|
@ -35,9 +37,11 @@ from registrar.utility.errors import (
|
||||||
DsDataErrorCodes,
|
DsDataErrorCodes,
|
||||||
SecurityEmailError,
|
SecurityEmailError,
|
||||||
SecurityEmailErrorCodes,
|
SecurityEmailErrorCodes,
|
||||||
|
OutsideOrgMemberError,
|
||||||
)
|
)
|
||||||
from registrar.models.utility.contact_error import ContactError
|
from registrar.models.utility.contact_error import ContactError
|
||||||
from registrar.views.utility.permission_views import UserDomainRolePermissionDeleteView
|
from registrar.views.utility.permission_views import UserDomainRolePermissionDeleteView
|
||||||
|
from registrar.utility.waffle import flag_is_active_for_user
|
||||||
|
|
||||||
from ..forms import (
|
from ..forms import (
|
||||||
SeniorOfficialContactForm,
|
SeniorOfficialContactForm,
|
||||||
|
@ -778,7 +782,18 @@ class DomainAddUserView(DomainFormBaseView):
|
||||||
"""Get an absolute URL for this domain."""
|
"""Get an absolute URL for this domain."""
|
||||||
return self.request.build_absolute_uri(reverse("domain", kwargs={"pk": self.object.id}))
|
return self.request.build_absolute_uri(reverse("domain", kwargs={"pk": self.object.id}))
|
||||||
|
|
||||||
def _send_domain_invitation_email(self, email: str, requestor: User, add_success=True):
|
def _is_member_of_different_org(self, email, requestor, requested_user):
|
||||||
|
"""Verifies if an email belongs to a different organization as a member or invited member."""
|
||||||
|
# Check if user is a already member of a different organization than the requestor's org
|
||||||
|
requestor_org = UserPortfolioPermission.objects.filter(user=requestor).first().portfolio
|
||||||
|
existing_org_permission = UserPortfolioPermission.objects.filter(user=requested_user).first()
|
||||||
|
existing_org_invitation = PortfolioInvitation.objects.filter(email=email).first()
|
||||||
|
|
||||||
|
return (existing_org_permission and existing_org_permission.portfolio != requestor_org) or (
|
||||||
|
existing_org_invitation and existing_org_invitation.portfolio != requestor_org
|
||||||
|
)
|
||||||
|
|
||||||
|
def _send_domain_invitation_email(self, email: str, requestor: User, requested_user=None, add_success=True):
|
||||||
"""Performs the sending of the domain invitation email,
|
"""Performs the sending of the domain invitation email,
|
||||||
does not make a domain information object
|
does not make a domain information object
|
||||||
email: string- email to send to
|
email: string- email to send to
|
||||||
|
@ -803,6 +818,13 @@ class DomainAddUserView(DomainFormBaseView):
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Check is user is a member or invited member of a different org from this domain's org
|
||||||
|
if flag_is_active_for_user(requestor, "organization_feature") and self._is_member_of_different_org(
|
||||||
|
email, requestor, requested_user
|
||||||
|
):
|
||||||
|
add_success = False
|
||||||
|
raise OutsideOrgMemberError
|
||||||
|
|
||||||
# Check to see if an invite has already been sent
|
# Check to see if an invite has already been sent
|
||||||
try:
|
try:
|
||||||
invite = DomainInvitation.objects.get(email=email, domain=self.object)
|
invite = DomainInvitation.objects.get(email=email, domain=self.object)
|
||||||
|
@ -859,16 +881,21 @@ class DomainAddUserView(DomainFormBaseView):
|
||||||
Throws EmailSendingError."""
|
Throws EmailSendingError."""
|
||||||
requested_email = form.cleaned_data["email"]
|
requested_email = form.cleaned_data["email"]
|
||||||
requestor = self.request.user
|
requestor = self.request.user
|
||||||
|
email_success = False
|
||||||
# look up a user with that email
|
# look up a user with that email
|
||||||
try:
|
try:
|
||||||
requested_user = User.objects.get(email=requested_email)
|
requested_user = User.objects.get(email=requested_email)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
# no matching user, go make an invitation
|
# no matching user, go make an invitation
|
||||||
|
email_success = True
|
||||||
return self._make_invitation(requested_email, requestor)
|
return self._make_invitation(requested_email, requestor)
|
||||||
else:
|
else:
|
||||||
# if user already exists then just send an email
|
# if user already exists then just send an email
|
||||||
try:
|
try:
|
||||||
self._send_domain_invitation_email(requested_email, requestor, add_success=False)
|
self._send_domain_invitation_email(
|
||||||
|
requested_email, requestor, requested_user=requested_user, add_success=False
|
||||||
|
)
|
||||||
|
email_success = True
|
||||||
except EmailSendingError:
|
except EmailSendingError:
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"Could not send email invitation (EmailSendingError)",
|
"Could not send email invitation (EmailSendingError)",
|
||||||
|
@ -876,6 +903,17 @@ class DomainAddUserView(DomainFormBaseView):
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
messages.warning(self.request, "Could not send email invitation.")
|
messages.warning(self.request, "Could not send email invitation.")
|
||||||
|
email_success = True
|
||||||
|
except OutsideOrgMemberError:
|
||||||
|
logger.warn(
|
||||||
|
"Could not send email. Can not invite member of a .gov organization to a different organization.",
|
||||||
|
self.object,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
messages.error(
|
||||||
|
self.request,
|
||||||
|
f"{requested_email} is already a member of another .gov organization.",
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"Could not send email invitation (Other Exception)",
|
"Could not send email invitation (Other Exception)",
|
||||||
|
@ -883,17 +921,17 @@ class DomainAddUserView(DomainFormBaseView):
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
messages.warning(self.request, "Could not send email invitation.")
|
messages.warning(self.request, "Could not send email invitation.")
|
||||||
|
if email_success:
|
||||||
try:
|
try:
|
||||||
UserDomainRole.objects.create(
|
UserDomainRole.objects.create(
|
||||||
user=requested_user,
|
user=requested_user,
|
||||||
domain=self.object,
|
domain=self.object,
|
||||||
role=UserDomainRole.Roles.MANAGER,
|
role=UserDomainRole.Roles.MANAGER,
|
||||||
)
|
)
|
||||||
|
messages.success(self.request, f"Added user {requested_email}.")
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
messages.warning(self.request, f"{requested_email} is already a manager for this domain")
|
messages.warning(self.request, f"{requested_email} is already a manager for this domain")
|
||||||
else:
|
|
||||||
messages.success(self.request, f"Added user {requested_email}.")
|
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue