diff --git a/.secrets.baseline b/.secrets.baseline index a233e4cf..f7145df8 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^.secrets.baseline$|^.*pgsslrootcert.yml$", "lines": null }, - "generated_at": "2020-01-27T19:24:43Z", + "generated_at": "2020-02-10T21:40:38Z", "plugins_used": [ { "base64_limit": 4.5, @@ -82,7 +82,7 @@ "hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3", "is_secret": false, "is_verified": false, - "line_number": 32, + "line_number": 33, "type": "Secret Keyword" } ], diff --git a/atst/app.py b/atst/app.py index 05578827..db6a09c7 100644 --- a/atst/app.py +++ b/atst/app.py @@ -233,12 +233,18 @@ def make_config(direct_config=None): config.set("default", "DATABASE_URI", database_uri) # Assemble REDIS_URI value + redis_use_tls = config["default"].getboolean("REDIS_TLS") redis_uri = "redis{}://{}:{}@{}".format( # pragma: allowlist secret - ("s" if config["default"].getboolean("REDIS_TLS") else ""), + ("s" if redis_use_tls else ""), (config.get("default", "REDIS_USER") or ""), (config.get("default", "REDIS_PASSWORD") or ""), config.get("default", "REDIS_HOST"), ) + if redis_use_tls: + tls_mode = config.get("default", "REDIS_SSLMODE") + tls_mode_str = tls_mode.lower() if tls_mode else "none" + redis_uri = f"{redis_uri}/?ssl_cert_reqs={tls_mode_str}" + config.set("default", "REDIS_URI", redis_uri) return map_config(config) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 1d921fde..425fe649 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -1,6 +1,5 @@ import json from secrets import token_urlsafe -from typing import Any, Dict from uuid import uuid4 from atst.utils import sha256_hex @@ -1026,12 +1025,10 @@ class AzureCloudProvider(CloudProviderInterface): def update_tenant_creds(self, tenant_id, secret: KeyVaultCredentials): hashed = sha256_hex(tenant_id) - new_secrets = secret.dict() curr_secrets = self._source_tenant_creds(tenant_id) - updated_secrets: Dict[str, Any] = {**curr_secrets.dict(), **new_secrets} - us = KeyVaultCredentials(**updated_secrets) - self.set_secret(hashed, json.dumps(us.dict())) - return us + updated_secrets = curr_secrets.merge_credentials(secret) + self.set_secret(hashed, json.dumps(updated_secrets.dict())) + return updated_secrets def _source_tenant_creds(self, tenant_id) -> KeyVaultCredentials: hashed = sha256_hex(tenant_id) @@ -1060,7 +1057,7 @@ class AzureCloudProvider(CloudProviderInterface): "timeframe": "Custom", "timePeriod": {"from": payload.from_date, "to": payload.to_date,}, "dataset": { - "granularity": "Daily", + "granularity": "Monthly", "aggregation": {"totalCost": {"name": "PreTaxCost", "function": "Sum"}}, "grouping": [{"type": "Dimension", "name": "InvoiceId"}], }, diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index 7ec0636f..da1cccd8 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -1,4 +1,5 @@ from uuid import uuid4 +import pendulum from .cloud_provider_interface import CloudProviderInterface from .exceptions import ( @@ -459,15 +460,26 @@ class MockCloudProvider(CloudProviderInterface): self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION) object_id = str(uuid4()) + start_of_month = pendulum.today(tz="utc").start_of("month").replace(tzinfo=None) + this_month = start_of_month.to_atom_string() + last_month = start_of_month.subtract(months=1).to_atom_string() + two_months_ago = start_of_month.subtract(months=2).to_atom_string() + properties = CostManagementQueryProperties( **dict( columns=[ {"name": "PreTaxCost", "type": "Number"}, - {"name": "UsageDate", "type": "Number"}, + {"name": "BillingMonth", "type": "Datetime"}, {"name": "InvoiceId", "type": "String"}, {"name": "Currency", "type": "String"}, ], - rows=[], + rows=[ + [1.0, two_months_ago, "", "USD"], + [500.0, two_months_ago, "e05009w9sf", "USD"], + [50.0, last_month, "", "USD"], + [1000.0, last_month, "e0500a4qhw", "USD"], + [500.0, this_month, "", "USD"], + ], ) ) diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index 358f7934..25521bb9 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -417,6 +417,15 @@ class KeyVaultCredentials(BaseModel): return values + def merge_credentials( + self, new_creds: "KeyVaultCredentials" + ) -> "KeyVaultCredentials": + updated_creds = {k: v for k, v in new_creds.dict().items() if v} + old_creds = self.dict() + old_creds.update(updated_creds) + + return KeyVaultCredentials(**old_creds) + class SubscriptionCreationCSPPayload(BaseCSPPayload): display_name: str diff --git a/atst/domain/csp/reports.py b/atst/domain/csp/reports.py index 3f9ccbf8..700947f7 100644 --- a/atst/domain/csp/reports.py +++ b/atst/domain/csp/reports.py @@ -1,6 +1,6 @@ -from collections import defaultdict import json from decimal import Decimal +import pendulum def load_fixture_data(): @@ -11,128 +11,25 @@ def load_fixture_data(): class MockReportingProvider: FIXTURE_SPEND_DATA = load_fixture_data() - @classmethod - def get_portfolio_monthly_spending(cls, portfolio): - """ - returns an array of application and environment spending for the - portfolio. Applications and their nested environments are sorted in - alphabetical order by name. - [ - { - name - this_month - last_month - total - environments [ - { - name - this_month - last_month - total - } - ] - } - ] - """ - fixture_apps = cls.FIXTURE_SPEND_DATA.get(portfolio.name, {}).get( - "applications", [] - ) +def prepare_azure_reporting_data(rows: list): + """ + Returns a dict representing invoiced and estimated funds for a portfolio given + a list of rows from CostManagementQueryCSPResult.properties.rows + { + invoiced: Decimal, + estimated: Decimal + } + """ - for application in portfolio.applications: - if application.name not in [app["name"] for app in fixture_apps]: - fixture_apps.append({"name": application.name, "environments": []}) + estimated = [] + while rows: + if pendulum.parse(rows[-1][1]) >= pendulum.now(tz="utc").start_of("month"): + estimated.append(rows.pop()) + else: + break - return sorted( - [ - cls._get_application_monthly_totals(portfolio, fixture_app) - for fixture_app in fixture_apps - if fixture_app["name"] - in [application.name for application in portfolio.applications] - ], - key=lambda app: app["name"], - ) - - @classmethod - def _get_environment_monthly_totals(cls, environment): - """ - returns a dictionary that represents spending totals for an environment e.g. - { - name - this_month - last_month - total - } - """ - return { - "name": environment["name"], - "this_month": sum(environment["spending"]["this_month"].values()), - "last_month": sum(environment["spending"]["last_month"].values()), - "total": sum(environment["spending"]["total"].values()), - } - - @classmethod - def _get_application_monthly_totals(cls, portfolio, fixture_app): - """ - returns a dictionary that represents spending totals for an application - and its environments e.g. - { - name - this_month - last_month - total - environments: [ - { - name - this_month - last_month - total - } - ] - } - """ - application_envs = [ - env - for env in portfolio.all_environments - if env.application.name == fixture_app["name"] - ] - - environments = [ - cls._get_environment_monthly_totals(env) - for env in fixture_app["environments"] - if env["name"] in [e.name for e in application_envs] - ] - - for env in application_envs: - if env.name not in [env["name"] for env in environments]: - environments.append({"name": env.name}) - - return { - "name": fixture_app["name"], - "this_month": sum(env.get("this_month", 0) for env in environments), - "last_month": sum(env.get("last_month", 0) for env in environments), - "total": sum(env.get("total", 0) for env in environments), - "environments": sorted(environments, key=lambda env: env["name"]), - } - - @classmethod - def get_spending_by_JEDI_clin(cls, portfolio): - """ - returns an dictionary of spending per JEDI CLIN for a portfolio - { - jedi_clin: { - invoiced - estimated - }, - } - """ - if portfolio.name in cls.FIXTURE_SPEND_DATA: - CLIN_spend_dict = defaultdict(lambda: defaultdict(Decimal)) - for application in cls.FIXTURE_SPEND_DATA[portfolio.name]["applications"]: - for environment in application["environments"]: - for clin, spend in environment["spending"]["this_month"].items(): - CLIN_spend_dict[clin]["estimated"] += Decimal(spend) - for clin, spend in environment["spending"]["total"].items(): - CLIN_spend_dict[clin]["invoiced"] += Decimal(spend) - return CLIN_spend_dict - return {} + return dict( + invoiced=Decimal(sum([row[0] for row in rows])), + estimated=Decimal(sum([row[0] for row in estimated])), + ) diff --git a/atst/domain/reports.py b/atst/domain/reports.py index 99b229e3..fc619649 100644 --- a/atst/domain/reports.py +++ b/atst/domain/reports.py @@ -1,12 +1,13 @@ from flask import current_app -from itertools import groupby +from atst.domain.csp.cloud.models import ( + ReportingCSPPayload, + CostManagementQueryCSPResult, +) +from atst.domain.csp.reports import prepare_azure_reporting_data +import pendulum class Reports: - @classmethod - def monthly_spending(cls, portfolio): - return current_app.csp.reports.get_portfolio_monthly_spending(portfolio) - @classmethod def expired_task_orders(cls, portfolio): return [ @@ -14,31 +15,19 @@ class Reports: ] @classmethod - def obligated_funds_by_JEDI_clin(cls, portfolio): - clin_spending = current_app.csp.reports.get_spending_by_JEDI_clin(portfolio) - active_clins = portfolio.active_clins - for jedi_clin, clins in groupby( - active_clins, key=lambda clin: clin.jedi_clin_type - ): - if not clin_spending.get(jedi_clin.name): - clin_spending[jedi_clin.name] = {} - clin_spending[jedi_clin.name]["obligated"] = sum( - clin.obligated_amount for clin in clins - ) + def get_portfolio_spending(cls, portfolio): + # TODO: Extend this function to make from_date and to_date configurable + from_date = pendulum.now().subtract(years=1).add(days=1).format("YYYY-MM-DD") + to_date = pendulum.now().format("YYYY-MM-DD") + rows = [] - output = [] - for clin in clin_spending.keys(): - invoiced = clin_spending[clin].get("invoiced", 0) - estimated = clin_spending[clin].get("estimated", 0) - obligated = clin_spending[clin].get("obligated", 0) - remaining = obligated - (invoiced + estimated) - output.append( - { - "name": clin, - "invoiced": invoiced, - "estimated": estimated, - "obligated": obligated, - "remaining": remaining, - } + if portfolio.csp_data: + payload = ReportingCSPPayload( + from_date=from_date, to_date=to_date, **portfolio.csp_data ) - return output + response: CostManagementQueryCSPResult = current_app.csp.cloud.get_reporting_data( + payload + ) + rows = response.properties.rows + + return prepare_azure_reporting_data(rows) diff --git a/atst/filters.py b/atst/filters.py index 3508f1e9..84191017 100644 --- a/atst/filters.py +++ b/atst/filters.py @@ -5,7 +5,7 @@ from flask import render_template from jinja2 import contextfilter from jinja2.exceptions import TemplateNotFound from urllib.parse import urlparse, urlunparse, parse_qs, urlencode -from decimal import DivisionByZero as DivisionByZeroException +from decimal import DivisionByZero as DivisionByZeroException, InvalidOperation def iconSvg(name): @@ -43,7 +43,7 @@ def obligatedFundingGraphWidth(values): numerator, denominator = values try: return (numerator / denominator) * 100 - except DivisionByZeroException: + except (DivisionByZeroException, InvalidOperation): return 0 diff --git a/atst/models/portfolio.py b/atst/models/portfolio.py index 5a8f0f1e..2ddcaa41 100644 --- a/atst/models/portfolio.py +++ b/atst/models/portfolio.py @@ -89,6 +89,12 @@ class Portfolio( def active_task_orders(self): return [task_order for task_order in self.task_orders if task_order.is_active] + @property + def total_obligated_funds(self): + return sum( + (task_order.total_obligated_funds for task_order in self.active_task_orders) + ) + @property def funding_duration(self): """ diff --git a/atst/routes/portfolios/index.py b/atst/routes/portfolios/index.py index f9e7d5cf..795d4b70 100644 --- a/atst/routes/portfolios/index.py +++ b/atst/routes/portfolios/index.py @@ -34,25 +34,25 @@ def create_portfolio(): @user_can(Permissions.VIEW_PORTFOLIO_REPORTS, message="view portfolio reports") def reports(portfolio_id): portfolio = Portfolios.get(g.current_user, portfolio_id) + spending = Reports.get_portfolio_spending(portfolio) + obligated = portfolio.total_obligated_funds + remaining = obligated - (spending["invoiced"] + spending["estimated"]) - current_obligated_funds = Reports.obligated_funds_by_JEDI_clin(portfolio) + current_obligated_funds = { + **spending, + "obligated": obligated, + "remaining": remaining, + } - if any(map(lambda clin: clin["remaining"] < 0, current_obligated_funds)): + if current_obligated_funds["remaining"] < 0: flash("insufficient_funds") - # wrapped in str() because the sum of obligated funds returns a Decimal object - total_portfolio_value = str( - sum( - task_order.total_obligated_funds - for task_order in portfolio.active_task_orders - ) - ) return render_template( "portfolios/reports/index.html", portfolio=portfolio, - total_portfolio_value=total_portfolio_value, + # wrapped in str() because the sum of obligated funds returns a Decimal object + total_portfolio_value=str(portfolio.total_obligated_funds), current_obligated_funds=current_obligated_funds, expired_task_orders=Reports.expired_task_orders(portfolio), - monthly_spending=Reports.monthly_spending(portfolio), retrieved=datetime.now(), # mocked datetime of reporting data retrival ) diff --git a/config/base.ini b/config/base.ini index 1f4c732a..55482741 100644 --- a/config/base.ini +++ b/config/base.ini @@ -38,6 +38,7 @@ PGUSER = postgres PORT=8000 REDIS_HOST=localhost:6379 REDIS_PASSWORD +REDIS_SSLMODE REDIS_TLS=False REDIS_USER SECRET_KEY = change_me_into_something_secret diff --git a/js/components/sidenav_toggler.js b/js/components/sidenav_toggler.js index 11717849..d545b3ed 100644 --- a/js/components/sidenav_toggler.js +++ b/js/components/sidenav_toggler.js @@ -1,5 +1,6 @@ import ExpandSidenavMixin from '../mixins/expand_sidenav' import ToggleMixin from '../mixins/toggle' +import { sidenavCookieName } from '../lib/constants' export default { name: 'sidenav-toggler', @@ -14,7 +15,7 @@ export default { toggle: function(e) { e.preventDefault() this.isVisible = !this.isVisible - document.cookie = this.cookieName + '=' + this.isVisible + '; path=/' + document.cookie = sidenavCookieName + '=' + this.isVisible + '; path=/' this.$parent.$emit('sidenavToggle', this.isVisible) }, }, diff --git a/js/components/toggle_menu.js b/js/components/toggle_menu.js index e17a201a..6c23ce06 100644 --- a/js/components/toggle_menu.js +++ b/js/components/toggle_menu.js @@ -5,6 +5,13 @@ export default { mixins: [ToggleMixin], + props: { + defaultVisible: { + type: Boolean, + default: false, + }, + }, + methods: { toggle: function(e) { if (this.$el.contains(e.target)) { diff --git a/js/lib/constants.js b/js/lib/constants.js new file mode 100644 index 00000000..b4de4fcf --- /dev/null +++ b/js/lib/constants.js @@ -0,0 +1 @@ +export const sidenavCookieName = 'expandSidenav' diff --git a/js/mixins/expand_sidenav.js b/js/mixins/expand_sidenav.js index 7553b7d4..2af9c87a 100644 --- a/js/mixins/expand_sidenav.js +++ b/js/mixins/expand_sidenav.js @@ -1,11 +1,12 @@ +import { sidenavCookieName } from '../lib/constants' + export default { props: { - cookieName: 'expandSidenav', defaultVisible: { type: Boolean, default: function() { - if (document.cookie.match(this.cookieName)) { - return !!document.cookie.match(this.cookieName + ' *= *true') + if (document.cookie.match(sidenavCookieName)) { + return !!document.cookie.match(sidenavCookieName + ' *= *true') } else { return true } diff --git a/js/mixins/form.js b/js/mixins/form.js index 45aa6261..070466de 100644 --- a/js/mixins/form.js +++ b/js/mixins/form.js @@ -15,6 +15,7 @@ export default { return { changed: this.hasChanges, valid: false, + submitted: false, } }, @@ -36,15 +37,16 @@ export default { handleSubmit: function(event) { if (!this.valid) { event.preventDefault() + this.submitted = true } }, }, computed: { canSave: function() { - if (this.changed && this.valid) { + if (this.changed && this.valid && !this.submitted) { return true - } else if (this.enableSave && this.valid) { + } else if (this.enableSave && this.valid && !this.submitted) { return true } else { return false diff --git a/templates/portfolios/reports/expired_task_orders.html b/templates/portfolios/reports/expired_task_orders.html index dcde6683..f55bb57e 100644 --- a/templates/portfolios/reports/expired_task_orders.html +++ b/templates/portfolios/reports/expired_task_orders.html @@ -16,13 +16,12 @@ PoP CLIN Value Amount Obligated - Amount Unspent {% for task_order in expired_task_orders %} - + Task Order {{ task_order.number }} {{ Icon("caret_right", classes="icon--tiny icon--blue" ) }} @@ -39,9 +38,8 @@ - {{ clin.end_date | formattedDate(formatter="%b %d, %Y") }} - {{ clin.total_amount | dollars }} - {{ clin.obligated_amount | dollars }} - {{ 0 | dollars }} + {{ clin.total_amount | dollars }} + {{ clin.obligated_amount | dollars }} {% endfor %} {% endfor %} diff --git a/templates/portfolios/reports/index.html b/templates/portfolios/reports/index.html index 747610d9..51364bf7 100644 --- a/templates/portfolios/reports/index.html +++ b/templates/portfolios/reports/index.html @@ -13,7 +13,5 @@
{% include "portfolios/reports/obligated_funds.html" %} {% include "portfolios/reports/expired_task_orders.html" %} -
- {% include "portfolios/reports/application_and_env_spending.html" %} {% endblock %} diff --git a/templates/portfolios/reports/obligated_funds.html b/templates/portfolios/reports/obligated_funds.html index e0f11792..0b41b16f 100644 --- a/templates/portfolios/reports/obligated_funds.html +++ b/templates/portfolios/reports/obligated_funds.html @@ -7,61 +7,56 @@
- {% for JEDI_clin in current_obligated_funds | sort(attribute='name')%} -
-

- {{ "JEDICLINType.{}".format(JEDI_clin.name) | translate }} -

-

Total obligated amount: {{ JEDI_clin.obligated | dollars }}

-
- {% if JEDI_clin.remaining < 0 %} - - {% else %} - {% set invoiced_width = (JEDI_clin.invoiced, JEDI_clin.obligated) | obligatedFundingGraphWidth %} - {% if invoiced_width %} - - - {% endif %} - - {% set estimated_width = (JEDI_clin.estimated, JEDI_clin.obligated) | obligatedFundingGraphWidth %} - {% if estimated_width %} - - - {% endif %} - +
+

+ Total obligated amount: {{ current_obligated_funds.obligated | dollars }} +

+
+ {% if current_obligated_funds.remaining < 0 %} + + {% else %} + {% set invoiced_width = (current_obligated_funds.invoiced, current_obligated_funds.obligated) | obligatedFundingGraphWidth %} + {% if invoiced_width %} + {% endif %} - + {% set estimated_width = (current_obligated_funds.estimated, current_obligated_funds.obligated) | obligatedFundingGraphWidth %} + {% if estimated_width %} + + + {% endif %} + + + {% endif %} +
+
+
+

+ + Invoiced expended funds: +

+

{{ current_obligated_funds.invoiced | dollars }}

-
-
-

- - Invoiced expended funds: -

-

{{ JEDI_clin.invoiced | dollars }}

-
-
-

- - Estimated expended funds: -

-

{{ JEDI_clin.estimated | dollars }}

-
-
-

- 0 else "insufficient"}}"> - Remaining funds: -

-

{{ JEDI_clin.remaining | dollars }}

-
+
+

+ + Estimated expended funds: +

+

{{ current_obligated_funds.estimated | dollars }}

+
+
+

+ 0 else "insufficient"}}"> + Remaining funds: +

+

{{ current_obligated_funds.remaining | dollars }}

- {% endfor %} +

Active Task Orders diff --git a/tests/domain/cloud/reports/test_reports.py b/tests/domain/cloud/reports/test_reports.py index 1e74e9b9..91079d44 100644 --- a/tests/domain/cloud/reports/test_reports.py +++ b/tests/domain/cloud/reports/test_reports.py @@ -1,58 +1,47 @@ -from atst.domain.csp.reports import MockReportingProvider +from atst.domain.csp.reports import prepare_azure_reporting_data from tests.factories import PortfolioFactory +from decimal import Decimal +import pendulum -def test_get_environment_monthly_totals(): - environment = { - "name": "Test Environment", - "spending": { - "this_month": {"JEDI_CLIN_1": 100, "JEDI_CLIN_2": 100}, - "last_month": {"JEDI_CLIN_1": 200, "JEDI_CLIN_2": 200}, - "total": {"JEDI_CLIN_1": 1000, "JEDI_CLIN_2": 1000}, - }, - } - totals = MockReportingProvider._get_environment_monthly_totals(environment) - assert totals == { - "name": "Test Environment", - "this_month": 200, - "last_month": 400, - "total": 2000, - } +class TestPrepareAzureData: + start_of_month = pendulum.today(tz="utc").start_of("month").replace(tzinfo=None) + next_month = start_of_month.add(months=1).to_atom_string() + this_month = start_of_month.to_atom_string() + last_month = start_of_month.subtract(months=1).to_atom_string() + two_months_ago = last_month = start_of_month.subtract(months=2).to_atom_string() + def test_estimated_and_invoiced(self): + rows = [ + [150.0, self.two_months_ago, "", "USD"], + [100.0, self.last_month, "e0500a4qhw", "USD"], + [50.0, self.this_month, "", "USD"], + [50.0, self.next_month, "", "USD"], + ] + output = prepare_azure_reporting_data(rows) -def test_get_application_monthly_totals(): - portfolio = PortfolioFactory.create( - applications=[ - {"name": "Test Application", "environments": [{"name": "Z"}, {"name": "A"}]} - ], - ) - application = { - "name": "Test Application", - "environments": [ - { - "name": "Z", - "spending": { - "this_month": {"JEDI_CLIN_1": 50, "JEDI_CLIN_2": 50}, - "last_month": {"JEDI_CLIN_1": 150, "JEDI_CLIN_2": 150}, - "total": {"JEDI_CLIN_1": 250, "JEDI_CLIN_2": 250}, - }, - }, - { - "name": "A", - "spending": { - "this_month": {"JEDI_CLIN_1": 100, "JEDI_CLIN_2": 100}, - "last_month": {"JEDI_CLIN_1": 200, "JEDI_CLIN_2": 200}, - "total": {"JEDI_CLIN_1": 1000, "JEDI_CLIN_2": 1000}, - }, - }, - ], - } + assert output.get("invoiced") == Decimal(250.0) + assert output.get("estimated") == Decimal(100.0) - totals = MockReportingProvider._get_application_monthly_totals( - portfolio, application - ) - assert totals["name"] == "Test Application" - assert totals["this_month"] == 300 - assert totals["last_month"] == 700 - assert totals["total"] == 2500 - assert [env["name"] for env in totals["environments"]] == ["A", "Z"] + def test_just_estimated(self): + rows = [ + [100.0, self.this_month, "", "USD"], + ] + output = prepare_azure_reporting_data(rows) + + assert output.get("invoiced") == Decimal(0.0) + assert output.get("estimated") == Decimal(100.0) + + def test_just_invoiced(self): + rows = [ + [100.0, self.last_month, "", "USD"], + ] + output = prepare_azure_reporting_data(rows) + + assert output.get("invoiced") == Decimal(100.0) + assert output.get("estimated") == Decimal(0.0) + + def test_no_rows(self): + output = prepare_azure_reporting_data([]) + assert output.get("invoiced") == Decimal(0.0) + assert output.get("estimated") == Decimal(0.0) diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 3a25f849..c89e6a77 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -25,6 +25,7 @@ from atst.domain.csp.cloud.models import ( CostManagementQueryCSPResult, EnvironmentCSPPayload, EnvironmentCSPResult, + KeyVaultCredentials, PrincipalAdminRoleCSPPayload, PrincipalAdminRoleCSPResult, ProductPurchaseCSPPayload, @@ -938,3 +939,23 @@ def test_create_user(mock_azure: AzureCloudProvider): result = mock_azure.create_user(payload) assert result.id == "id" + + +def test_update_tenant_creds(mock_azure: AzureCloudProvider): + with patch.object( + AzureCloudProvider, "set_secret", wraps=mock_azure.set_secret, + ) as set_secret: + set_secret.return_value = None + existing_secrets = { + "tenant_id": "mytenant", + "tenant_admin_username": "admin", + "tenant_admin_password": "foo", # pragma: allowlist secret + } + mock_azure = mock_get_secret(mock_azure, json.dumps(existing_secrets)) + + mock_new_secrets = KeyVaultCredentials(**MOCK_CREDS) + updated_secret = mock_azure.update_tenant_creds("mytenant", mock_new_secrets) + + assert updated_secret == KeyVaultCredentials( + **{**existing_secrets, **MOCK_CREDS} + ) diff --git a/tests/domain/cloud/test_models.py b/tests/domain/cloud/test_models.py index 10c81293..29bc60cb 100644 --- a/tests/domain/cloud/test_models.py +++ b/tests/domain/cloud/test_models.py @@ -100,6 +100,26 @@ def test_KeyVaultCredentials_enforce_root_creds(): ) +def test_KeyVaultCredentials_merge_credentials(): + old_secret = KeyVaultCredentials( + tenant_id="foo", + tenant_admin_username="bar", + tenant_admin_password="baz", # pragma: allowlist secret + ) + new_secret = KeyVaultCredentials( + tenant_id="foo", tenant_sp_client_id="bip", tenant_sp_key="bop" + ) + + expected_update = KeyVaultCredentials( + tenant_id="foo", + tenant_admin_username="bar", + tenant_admin_password="baz", # pragma: allowlist secret + tenant_sp_client_id="bip", + tenant_sp_key="bop", + ) + assert old_secret.merge_credentials(new_secret) == expected_update + + user_payload = { "tenant_id": "123", "display_name": "Han Solo", diff --git a/tests/domain/test_reports.py b/tests/domain/test_reports.py index 33ac926e..cdd5de5e 100644 --- a/tests/domain/test_reports.py +++ b/tests/domain/test_reports.py @@ -1,8 +1,31 @@ -# TODO: Implement when we get real reporting data -def test_expired_task_orders(): - pass +import pytest + +from atst.domain.reports import Reports +from tests.factories import PortfolioFactory +from decimal import Decimal -# TODO: Implement when we get real reporting data -def test_obligated_funds_by_JEDI_clin(): - pass +@pytest.fixture(scope="function") +def portfolio(): + portfolio = PortfolioFactory.create() + return portfolio + + +class TestGetPortfolioSpending: + csp_data = { + "tenant_id": "", + "billing_profile_properties": { + "invoice_sections": [{"invoice_section_id": "",}] + }, + } + + def test_with_csp_data(self, portfolio): + portfolio.csp_data = self.csp_data + data = Reports.get_portfolio_spending(portfolio) + assert data["invoiced"] == Decimal(1551.0) + assert data["estimated"] == Decimal(500.0) + + def test_without_csp_data(self, portfolio): + data = Reports.get_portfolio_spending(portfolio) + assert data["invoiced"] == Decimal(0) + assert data["estimated"] == Decimal(0) diff --git a/tests/test_app.py b/tests/test_app.py index 937a15e2..21fd8284 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -7,6 +7,7 @@ from atst.app import ( make_crl_validator, apply_config_from_directory, apply_config_from_environment, + make_config, ) @@ -67,3 +68,18 @@ def test_apply_config_from_environment_skips_unknown_settings( monkeypatch.setenv("FLARF", "MAYO") apply_config_from_environment(config_object) assert "FLARF" not in config_object.options("default") + + +class TestMakeConfig: + def test_redis_ssl_connection(self): + config = make_config({"REDIS_TLS": True}) + uri = config.get("REDIS_URI") + assert "rediss" in uri + assert "ssl_cert_reqs" in uri + + def test_non_redis_ssl_connection(self): + config = make_config({"REDIS_TLS": False}) + uri = config.get("REDIS_URI") + assert "rediss" not in uri + assert "redis" in uri + assert "ssl_cert_reqs" not in uri diff --git a/translations.yaml b/translations.yaml index c16f89c6..b80f5dca 100644 --- a/translations.yaml +++ b/translations.yaml @@ -313,7 +313,7 @@ forms: upload_error: There was an error uploading your file. Please try again. If you encounter repeated problems uploading this file, please contact CCPO. size_error: "The file you have selected is too large. Please choose a file no larger than {file_size_limit}MB." filename_error: File names can only contain the characters A-Z, 0-9, space, hyphen, underscore, and period. - number_description: 13-Digit Task Order Number + number_description: Task Order Number pop_errors: date_order: PoP start date must be before end date. range: Date must be between {start} and {end}. @@ -530,7 +530,7 @@ task_orders: form: add_clin: Add Another CLIN add_to_header: Enter the Task Order number - add_to_description: Please input your 13-digit Task Order number. This number may be listed under "Order Number" if your Contracting Officer used form 1149, or "Delivery Order/Call No." if form 1155 was used. Moving forward, this portion of funding will be referenced by the recorded Task Order number. + add_to_description: Please input your Task Order number. This number may be listed under "Order Number" if your Contracting Officer used form 1149, or "Delivery Order/Call No." if form 1155 was used. Moving forward, this portion of funding will be referenced by the recorded Task Order number. builder_base: cancel_modal: Do you want to save this draft? delete_draft: No, delete it diff --git a/uitests/Add_CCPO_User.html b/uitests/Add_CCPO_User.html index e4b43af8..35256565 100644 --- a/uitests/Add_CCPO_User.html +++ b/uitests/Add_CCPO_User.html @@ -202,12 +202,12 @@ waitForElementPresent -css=.global-panel-container > h3 +css=.global-panel-container > .ccpo-panel-container > h3 assertText -css=.global-panel-container > h3 +css=.global-panel-container > .ccpo-panel-container > h3 *Confirm new CCPO user* @@ -277,12 +277,12 @@ waitForElementPresent -css=.global-panel-container > div:nth-of-type(2) > .modal > .modal__container > .modal__dialog > .modal__body > div:nth-of-type(2) > .action-group > a.action-group__action.icon-link.icon-link--default +css=.global-panel-container > .ccpo-panel-container > div:nth-of-type(2) > .modal > .modal__container > .modal__dialog > .modal__body > div:nth-of-type(2) > .action-group > a.action-group__action.icon-link.icon-link--default assertElementPresent -css=.global-panel-container > div:nth-of-type(2) > .modal > .modal__container > .modal__dialog > .modal__body > div:nth-of-type(2) > .action-group > a.action-group__action.icon-link.icon-link--default +css=.global-panel-container > .ccpo-panel-container > div:nth-of-type(2) > .modal > .modal__container > .modal__dialog > .modal__body > div:nth-of-type(2) > .action-group > a.action-group__action.icon-link.icon-link--default diff --git a/uitests/Application_Index_with_App.html b/uitests/Application_Index_with_App.html index c7efd48a..6771ba56 100644 --- a/uitests/Application_Index_with_App.html +++ b/uitests/Application_Index_with_App.html @@ -237,7 +237,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -487,12 +487,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button - + click -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button @@ -535,12 +535,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label assertElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label diff --git a/uitests/Create_New_Application.html b/uitests/Create_New_Application.html index 564c1886..43d4cb0d 100644 --- a/uitests/Create_New_Application.html +++ b/uitests/Create_New_Application.html @@ -224,7 +224,7 @@ Imported from: AT-AT CI - login--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -458,12 +458,12 @@ Imported from: AT-AT CI - login--> waitForElementPresent -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button - + click -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button @@ -503,12 +503,12 @@ Imported from: AT-AT CI - login--> waitForElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label assertElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label diff --git a/uitests/Create_New_TO.html b/uitests/Create_New_TO.html index 1d85c2bc..a9615fa4 100644 --- a/uitests/Create_New_TO.html +++ b/uitests/Create_New_TO.html @@ -165,7 +165,7 @@ Imported from: AT-AT CI - login--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/Edit_App_Member.html b/uitests/Edit_App_Member.html index e4e92fa8..bd65abb2 100644 --- a/uitests/Edit_App_Member.html +++ b/uitests/Edit_App_Member.html @@ -237,7 +237,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -487,12 +487,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button - + click -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button @@ -535,12 +535,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label assertElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label diff --git a/uitests/Edit_Portfolio_Member.html b/uitests/Edit_Portfolio_Member.html index 3fd58cee..c1999ccd 100644 --- a/uitests/Edit_Portfolio_Member.html +++ b/uitests/Edit_Portfolio_Member.html @@ -246,7 +246,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -308,12 +308,12 @@ Imported from: AT-AT CI - Portfolio Settings--> Imported from: AT-AT CI - Portfolio Settings--> waitForElementPresent -css=button.usa-button.usa-button-primary.usa-button-big +css=button.usa-button.usa-button-primary assertText -css=button.usa-button.usa-button-primary.usa-button-big +css=button.usa-button.usa-button-primary Save Changes @@ -595,12 +595,12 @@ Brandon Buchannan's access to this Portfolio is pending until they sign in for t waitForElementPresent -css=.portfolio-content > div:nth-of-type(3) > .modal.form-content--app-mem > .modal__container > .modal__dialog > .modal__body > .modal__form--header > h1 +css=.portfolio-admin > div:nth-of-type(2) > .modal.form-content--app-mem > .modal__container > .modal__dialog > .modal__body > .modal__form--header > h1 assertElementPresent -css=.portfolio-content > div:nth-of-type(3) > .modal.form-content--app-mem > .modal__container > .modal__dialog > .modal__body > .modal__form--header > h1 +css=.portfolio-admin > div:nth-of-type(2) > .modal.form-content--app-mem > .modal__container > .modal__dialog > .modal__body > .modal__form--header > h1 diff --git a/uitests/New_App_Step_1.html b/uitests/New_App_Step_1.html index 83f70ab6..f4ce8b57 100644 --- a/uitests/New_App_Step_1.html +++ b/uitests/New_App_Step_1.html @@ -165,7 +165,7 @@ Imported from: AT-AT CI - login--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/New_App_Step_2.html b/uitests/New_App_Step_2.html index 50338788..e75421a1 100644 --- a/uitests/New_App_Step_2.html +++ b/uitests/New_App_Step_2.html @@ -174,7 +174,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/New_App_Step_2_-_Add_Env.html b/uitests/New_App_Step_2_-_Add_Env.html index 7450d2e3..e5130a74 100644 --- a/uitests/New_App_Step_2_-_Add_Env.html +++ b/uitests/New_App_Step_2_-_Add_Env.html @@ -183,7 +183,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/New_App_Step_3.html b/uitests/New_App_Step_3.html index f8e4948c..6d17272f 100644 --- a/uitests/New_App_Step_3.html +++ b/uitests/New_App_Step_3.html @@ -242,7 +242,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/New_Portfolio.html b/uitests/New_Portfolio.html index cb3f3c26..b75e75ae 100644 --- a/uitests/New_Portfolio.html +++ b/uitests/New_Portfolio.html @@ -156,7 +156,7 @@ assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* diff --git a/uitests/New_Portfolio_Member.html b/uitests/New_Portfolio_Member.html index ab4480ee..a3c7062e 100644 --- a/uitests/New_Portfolio_Member.html +++ b/uitests/New_Portfolio_Member.html @@ -233,7 +233,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -291,12 +291,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=button.usa-button.usa-button-primary.usa-button-big +css=button.usa-button.usa-button-primary assertText -css=button.usa-button.usa-button-primary.usa-button-big +css=button.usa-button.usa-button-primary Save Changes diff --git a/uitests/Portfolio_Settings.html b/uitests/Portfolio_Settings.html index 4c3ea61d..9782587d 100644 --- a/uitests/Portfolio_Settings.html +++ b/uitests/Portfolio_Settings.html @@ -165,7 +165,7 @@ Imported from: AT-AT CI - login--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -219,12 +219,12 @@ Imported from: AT-AT CI - login--> waitForElementPresent -css=button.usa-button.usa-button-primary.usa-button-big +css=button.usa-button.usa-button-primary assertText -css=button.usa-button.usa-button-primary.usa-button-big +css=button.usa-button.usa-button-primary Save Changes diff --git a/uitests/Reports_-_Basics.html b/uitests/Reports_-_Basics.html index ecb8e962..04e6b387 100644 --- a/uitests/Reports_-_Basics.html +++ b/uitests/Reports_-_Basics.html @@ -174,7 +174,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/Reports_-_Empty_State.html b/uitests/Reports_-_Empty_State.html index 448a3cb0..6d3c4988 100644 --- a/uitests/Reports_-_Empty_State.html +++ b/uitests/Reports_-_Empty_State.html @@ -165,7 +165,7 @@ Imported from: AT-AT CI - login--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/Reports_-_Follow_Add_App_Button.html b/uitests/Reports_-_Follow_Add_App_Button.html index c1607b2f..ebbeaa70 100644 --- a/uitests/Reports_-_Follow_Add_App_Button.html +++ b/uitests/Reports_-_Follow_Add_App_Button.html @@ -183,7 +183,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/Reports_-_Follow_TO_link.html b/uitests/Reports_-_Follow_TO_link.html index 7c3d2e6d..91ecd4e3 100644 --- a/uitests/Reports_-_Follow_TO_link.html +++ b/uitests/Reports_-_Follow_TO_link.html @@ -183,7 +183,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/Reports_-_with_TO,_App,_and_Environments.html b/uitests/Reports_-_with_TO,_App,_and_Environments.html index 6fbc96e2..1afec10f 100644 --- a/uitests/Reports_-_with_TO,_App,_and_Environments.html +++ b/uitests/Reports_-_with_TO,_App,_and_Environments.html @@ -237,7 +237,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -487,12 +487,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button - + click -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button @@ -535,12 +535,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label assertElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label diff --git a/uitests/Reports_-_with_expired_TO.html b/uitests/Reports_-_with_expired_TO.html index df76aa00..297cfba6 100644 --- a/uitests/Reports_-_with_expired_TO.html +++ b/uitests/Reports_-_with_expired_TO.html @@ -237,7 +237,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -487,12 +487,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button - + click -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button @@ -535,12 +535,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label assertElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label diff --git a/uitests/Resend_App_Member_Invite.html b/uitests/Resend_App_Member_Invite.html index def438b5..fe718a81 100644 --- a/uitests/Resend_App_Member_Invite.html +++ b/uitests/Resend_App_Member_Invite.html @@ -237,7 +237,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -487,12 +487,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button - + click -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button @@ -535,12 +535,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label assertElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label diff --git a/uitests/Resend_Portfolio_Member_Invite.html b/uitests/Resend_Portfolio_Member_Invite.html index b369438f..2f6570af 100644 --- a/uitests/Resend_Portfolio_Member_Invite.html +++ b/uitests/Resend_Portfolio_Member_Invite.html @@ -246,7 +246,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -308,12 +308,12 @@ Imported from: AT-AT CI - Portfolio Settings--> Imported from: AT-AT CI - Portfolio Settings--> waitForElementPresent -css=button.usa-button.usa-button-primary.usa-button-big +css=button.usa-button.usa-button-primary assertText -css=button.usa-button.usa-button-primary.usa-button-big +css=button.usa-button.usa-button-primary Save Changes @@ -595,12 +595,12 @@ Brandon Buchannan's access to this Portfolio is pending until they sign in for t waitForElementPresent -css=.portfolio-content > div:nth-of-type(4) > .modal.form-content--app-mem > .modal__container > .modal__dialog > .modal__body > .modal__form--header > h1 +css=.portfolio-admin > div:nth-of-type(3) > .modal.form-content--app-mem > .modal__container > .modal__dialog > .modal__body > .modal__form--header > h1 assertText -css=.portfolio-content > div:nth-of-type(4) > .modal.form-content--app-mem > .modal__container > .modal__dialog > .modal__body > .modal__form--header > h1 +css=.portfolio-admin > div:nth-of-type(3) > .modal.form-content--app-mem > .modal__container > .modal__dialog > .modal__body > .modal__form--header > h1 *Verify Member Information* diff --git a/uitests/Revoke_App_Member_Invite.html b/uitests/Revoke_App_Member_Invite.html index 847afe97..8452d19c 100644 --- a/uitests/Revoke_App_Member_Invite.html +++ b/uitests/Revoke_App_Member_Invite.html @@ -237,7 +237,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -487,12 +487,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button - + click -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button @@ -535,12 +535,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label assertElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label diff --git a/uitests/Revoke_Environment_Access.html b/uitests/Revoke_Environment_Access.html index 6506733b..a73e40d7 100644 --- a/uitests/Revoke_Environment_Access.html +++ b/uitests/Revoke_Environment_Access.html @@ -237,7 +237,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -487,12 +487,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button - + click -css=span.action-group-footer > a.usa-button +css=.action-group-footer > .action-group-footer--container > a.usa-button @@ -535,12 +535,12 @@ Imported from: AT-AT CI - New Portfolio--> waitForElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label assertElementPresent -css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label.label--below +css=.accordion-table__items > .accordion-table__item:nth-of-type(1) > .accordion-table__item-content > .environment-list__item > .label diff --git a/uitests/Revoke_Portfolio_Member_Invite.html b/uitests/Revoke_Portfolio_Member_Invite.html index 947bf526..ec74eac0 100644 --- a/uitests/Revoke_Portfolio_Member_Invite.html +++ b/uitests/Revoke_Portfolio_Member_Invite.html @@ -246,7 +246,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad @@ -308,12 +308,12 @@ Imported from: AT-AT CI - Portfolio Settings--> Imported from: AT-AT CI - Portfolio Settings--> waitForElementPresent -css=button.usa-button.usa-button-primary.usa-button-big +css=button.usa-button.usa-button-primary assertText -css=button.usa-button.usa-button-primary.usa-button-big +css=button.usa-button.usa-button-primary Save Changes diff --git a/uitests/TO_Index_(Landing)_Page_-_Empty_State.html b/uitests/TO_Index_(Landing)_Page_-_Empty_State.html index bc0e7d5a..832e159e 100644 --- a/uitests/TO_Index_(Landing)_Page_-_Empty_State.html +++ b/uitests/TO_Index_(Landing)_Page_-_Empty_State.html @@ -165,7 +165,7 @@ Imported from: AT-AT CI - login--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/TO_Index_with_Draft_TO.html b/uitests/TO_Index_with_Draft_TO.html index 6e34eb31..7f5b9de3 100644 --- a/uitests/TO_Index_with_Draft_TO.html +++ b/uitests/TO_Index_with_Draft_TO.html @@ -174,7 +174,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/TO_Index_with_TO.html b/uitests/TO_Index_with_TO.html index 8065848c..46bcfae1 100644 --- a/uitests/TO_Index_with_TO.html +++ b/uitests/TO_Index_with_TO.html @@ -174,7 +174,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/TO_Index_with_Unsigned_TO.html b/uitests/TO_Index_with_Unsigned_TO.html index 49998dbe..8093c99b 100644 --- a/uitests/TO_Index_with_Unsigned_TO.html +++ b/uitests/TO_Index_with_Unsigned_TO.html @@ -174,7 +174,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/TO_Index_with_expired_TO.html b/uitests/TO_Index_with_expired_TO.html index 47459209..d091166f 100644 --- a/uitests/TO_Index_with_expired_TO.html +++ b/uitests/TO_Index_with_expired_TO.html @@ -174,7 +174,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/TO_Index_with_future_TO.html b/uitests/TO_Index_with_future_TO.html index d04b0530..47d1d588 100644 --- a/uitests/TO_Index_with_future_TO.html +++ b/uitests/TO_Index_with_future_TO.html @@ -174,7 +174,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/TO_Step_1.html b/uitests/TO_Step_1.html index ac031608..aac33dd1 100644 --- a/uitests/TO_Step_1.html +++ b/uitests/TO_Step_1.html @@ -165,7 +165,7 @@ Imported from: AT-AT CI - login--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/TO_Step_2.html b/uitests/TO_Step_2.html index 464d0f51..528b2b1a 100644 --- a/uitests/TO_Step_2.html +++ b/uitests/TO_Step_2.html @@ -174,7 +174,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/TO_Step_3.html b/uitests/TO_Step_3.html index ffdf0ae4..8f7e5ae9 100644 --- a/uitests/TO_Step_3.html +++ b/uitests/TO_Step_3.html @@ -183,7 +183,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/TO_Step_3_-_Add_CLIN.html b/uitests/TO_Step_3_-_Add_CLIN.html index 4617ebdd..859ab2c3 100644 --- a/uitests/TO_Step_3_-_Add_CLIN.html +++ b/uitests/TO_Step_3_-_Add_CLIN.html @@ -192,7 +192,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/TO_Step_4.html b/uitests/TO_Step_4.html index 8bf2f648..56087c82 100644 --- a/uitests/TO_Step_4.html +++ b/uitests/TO_Step_4.html @@ -192,7 +192,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad diff --git a/uitests/TO_Step_5.html b/uitests/TO_Step_5.html index 4eb53ab9..3c6f4db9 100644 --- a/uitests/TO_Step_5.html +++ b/uitests/TO_Step_5.html @@ -201,7 +201,7 @@ Imported from: AT-AT CI - New Portfolio--> assertText css=.empty-state > h3 -*You don't have any Applications yet* +*You don’t have any Applications yet* waitForPageToLoad