From 3fc323d7859044cb7e2851b20b58c08ca8d76a4e Mon Sep 17 00:00:00 2001 From: dandds Date: Thu, 10 Jan 2019 16:38:00 -0500 Subject: [PATCH] project -> application everywhere --- atst/domain/{projects.py => applications.py} | 52 ++++----- atst/domain/csp/reports.py | 78 +++++++------- atst/domain/environments.py | 16 +-- atst/domain/roles.py | 10 +- atst/domain/workspaces/scopes.py | 20 ++-- atst/domain/workspaces/workspaces.py | 4 +- atst/forms/{project.py => application.py} | 14 +-- atst/forms/data.py | 10 +- atst/forms/edit_member.py | 2 +- atst/models/__init__.py | 2 +- atst/models/{project.py => application.py} | 6 +- atst/models/environment.py | 12 +-- atst/models/environment_role.py | 8 +- atst/models/task_order.py | 2 +- atst/models/workspace.py | 4 +- atst/models/workspace_role.py | 14 +-- atst/routes/__init__.py | 2 +- .../routes/requests/financial_verification.py | 4 +- atst/routes/requests/index.py | 2 +- atst/routes/workspaces/__init__.py | 2 +- atst/routes/workspaces/applications.py | 102 ++++++++++++++++++ atst/routes/workspaces/index.py | 6 +- atst/routes/workspaces/members.py | 6 +- atst/routes/workspaces/projects.py | 99 ----------------- ...ect_roles.js => edit_application_roles.js} | 2 +- js/components/forms/edit_environment_role.js | 4 +- .../{new_project.js => new_application.js} | 2 +- js/components/requests_list.js | 2 +- js/components/tables/spend_table.js | 16 +-- js/index.js | 8 +- script/seed_sample.py | 4 +- styles/atat.scss | 4 +- styles/components/_site_action.scss | 2 +- styles/elements/_kpi.scss | 2 +- ...oject_edit.scss => _application_edit.scss} | 4 +- ...jects_list.scss => _application_list.scss} | 8 +- styles/sections/_home.scss | 2 +- styles/sections/_reports.scss | 6 +- .../events/{project.html => application.html} | 0 .../audit_log/events/environment_role.html | 2 +- ...t_form.html => edit_application_form.html} | 4 +- templates/help/docs/getting-started.html | 6 +- .../navigation/workspace_navigation.html | 12 +-- templates/requests/screen-3.html | 2 +- .../{projects => applications}/edit.html | 10 +- templates/workspaces/applications/index.html | 55 ++++++++++ .../{projects => applications}/new.html | 18 ++-- templates/workspaces/edit.html | 2 +- templates/workspaces/index.html | 2 +- templates/workspaces/members/edit.html | 30 +++--- templates/workspaces/projects/index.html | 55 ---------- templates/workspaces/reports/index.html | 34 +++--- tests/domain/test_applications.py | 58 ++++++++++ tests/domain/test_audit_log.py | 10 +- tests/domain/test_environments.py | 80 +++++++------- tests/domain/test_projects.py | 57 ---------- tests/domain/test_reports.py | 2 +- tests/domain/test_workspaces.py | 56 +++++----- tests/factories.py | 26 ++--- tests/forms/test_new_request.py | 2 +- tests/models/test_environments.py | 12 ++- tests/models/test_workspace_role.py | 36 ++++--- tests/routes/test_home.py | 4 +- ...{test_projects.py => test_applications.py} | 79 +++++++------- tests/routes/workspaces/test_invitations.py | 2 +- tests/routes/workspaces/test_members.py | 30 +++--- translations.yaml | 24 ++--- 67 files changed, 644 insertions(+), 609 deletions(-) rename atst/domain/{projects.py => applications.py} (53%) rename atst/forms/{project.py => application.py} (64%) rename atst/models/{project.py => application.py} (72%) create mode 100644 atst/routes/workspaces/applications.py delete mode 100644 atst/routes/workspaces/projects.py rename js/components/forms/{edit_project_roles.js => edit_application_roles.js} (92%) rename js/components/forms/{new_project.js => new_application.js} (99%) rename styles/sections/{_project_edit.scss => _application_edit.scss} (79%) rename styles/sections/{_projects_list.scss => _application_list.scss} (65%) rename templates/audit_log/events/{project.html => application.html} (100%) rename templates/fragments/{edit_project_form.html => edit_application_form.html} (53%) rename templates/workspaces/{projects => applications}/edit.html (70%) create mode 100644 templates/workspaces/applications/index.html rename templates/workspaces/{projects => applications}/new.html (81%) delete mode 100644 templates/workspaces/projects/index.html create mode 100644 tests/domain/test_applications.py delete mode 100644 tests/domain/test_projects.py rename tests/routes/workspaces/{test_projects.py => test_applications.py} (68%) diff --git a/atst/domain/projects.py b/atst/domain/applications.py similarity index 53% rename from atst/domain/projects.py rename to atst/domain/applications.py index 9b75976e..c96b4206 100644 --- a/atst/domain/projects.py +++ b/atst/domain/applications.py @@ -3,46 +3,50 @@ from atst.domain.authz import Authorization from atst.domain.environments import Environments from atst.domain.exceptions import NotFoundError from atst.models.permissions import Permissions -from atst.models.project import Project +from atst.models.application import Application from atst.models.environment import Environment from atst.models.environment_role import EnvironmentRole -class Projects(object): +class Applications(object): @classmethod def create(cls, user, workspace, name, description, environment_names): - project = Project(workspace=workspace, name=name, description=description) - db.session.add(project) + application = Application( + workspace=workspace, name=name, description=description + ) + db.session.add(application) - Environments.create_many(project, environment_names) + Environments.create_many(application, environment_names) db.session.commit() - return project + return application @classmethod - def get(cls, user, workspace, project_id): - # TODO: this should check permission for this particular project + def get(cls, user, workspace, application_id): + # TODO: this should check permission for this particular application Authorization.check_workspace_permission( user, workspace, Permissions.VIEW_APPLICATION_IN_WORKSPACE, - "view project in workspace", + "view application in workspace", ) try: - project = db.session.query(Project).filter_by(id=project_id).one() + application = ( + db.session.query(Application).filter_by(id=application_id).one() + ) except NoResultFound: - raise NotFoundError("project") + raise NotFoundError("application") - return project + return application @classmethod def for_user(self, user, workspace): return ( - db.session.query(Project) + db.session.query(Application) .join(Environment) .join(EnvironmentRole) - .filter(Project.workspace_id == workspace.id) + .filter(Application.workspace_id == workspace.id) .filter(EnvironmentRole.user_id == user.id) .all() ) @@ -53,26 +57,26 @@ class Projects(object): user, workspace, Permissions.VIEW_APPLICATION_IN_WORKSPACE, - "view project in workspace", + "view application in workspace", ) try: - projects = ( - db.session.query(Project).filter_by(workspace_id=workspace.id).all() + applications = ( + db.session.query(Application).filter_by(workspace_id=workspace.id).all() ) except NoResultFound: - raise NotFoundError("projects") + raise NotFoundError("applications") - return projects + return applications @classmethod - def update(cls, user, workspace, project, new_data): + def update(cls, user, workspace, application, new_data): if "name" in new_data: - project.name = new_data["name"] + application.name = new_data["name"] if "description" in new_data: - project.description = new_data["description"] + application.description = new_data["description"] - db.session.add(project) + db.session.add(application) db.session.commit() - return project + return application diff --git a/atst/domain/csp/reports.py b/atst/domain/csp/reports.py index c052377b..e7a012e5 100644 --- a/atst/domain/csp/reports.py +++ b/atst/domain/csp/reports.py @@ -22,12 +22,12 @@ class MockEnvironment: self.name = env_name -class MockProject: - def __init__(self, project_name, envs): +class MockApplication: + def __init__(self, application_name, envs): def make_env(name): - return MockEnvironment("{}_{}".format(project_name, name), name) + return MockEnvironment("{}_{}".format(application_name, name), name) - self.name = project_name + self.name = application_name self.environments = [make_env(env_name) for env_name in envs] @@ -161,13 +161,13 @@ class MockReportingProvider(ReportingInterface): REPORT_FIXTURE_MAP = { "Aardvark": { "cumulative": CUMULATIVE_BUDGET_AARDVARK, - "projects": [ - MockProject("LC04", ["Integ", "PreProd", "Prod"]), - MockProject("SF18", ["Integ", "PreProd", "Prod"]), - MockProject("Canton", ["Prod"]), - MockProject("BD04", ["Integ", "PreProd"]), - MockProject("SCV18", ["Dev"]), - MockProject( + "applications": [ + MockApplication("LC04", ["Integ", "PreProd", "Prod"]), + MockApplication("SF18", ["Integ", "PreProd", "Prod"]), + MockApplication("Canton", ["Prod"]), + MockApplication("BD04", ["Integ", "PreProd"]), + MockApplication("SCV18", ["Dev"]), + MockApplication( "Crown", [ "CR Portal Dev", @@ -182,9 +182,9 @@ class MockReportingProvider(ReportingInterface): }, "Beluga": { "cumulative": CUMULATIVE_BUDGET_BELUGA, - "projects": [ - MockProject("NP02", ["Integ", "PreProd", "NP02_Prod"]), - MockProject("FM", ["Integ", "Prod"]), + "applications": [ + MockApplication("NP02", ["Integ", "PreProd", "NP02_Prod"]), + MockApplication("FM", ["Integ", "Prod"]), ], "budget": 70000, }, @@ -194,8 +194,8 @@ class MockReportingProvider(ReportingInterface): return sum( [ spend - for project in data - for env in project.environments + for application in data + for env in application.environments for spend in self.MONTHLY_SPEND_BY_ENVIRONMENT[env.id].values() ] ) @@ -210,31 +210,31 @@ class MockReportingProvider(ReportingInterface): def get_total_spending(self, workspace): if workspace.name in self.REPORT_FIXTURE_MAP: return self._sum_monthly_spend( - self.REPORT_FIXTURE_MAP[workspace.name]["projects"] + self.REPORT_FIXTURE_MAP[workspace.name]["applications"] ) return 0 - def _rollup_project_totals(self, data): - project_totals = {} - for project, environments in data.items(): - project_spend = [ + def _rollup_application_totals(self, data): + application_totals = {} + for application, environments in data.items(): + application_spend = [ (month, spend) for env in environments.values() if env for month, spend in env.items() ] - project_totals[project] = { + application_totals[application] = { month: sum([spend[1] for spend in spends]) - for month, spends in groupby(sorted(project_spend), lambda x: x[0]) + for month, spends in groupby(sorted(application_spend), lambda x: x[0]) } - return project_totals + return application_totals - def _rollup_workspace_totals(self, project_totals): + def _rollup_workspace_totals(self, application_totals): monthly_spend = [ (month, spend) - for project in project_totals.values() - for month, spend in project.items() + for application in application_totals.values() + for month, spend in application.items() ] workspace_totals = {} for month, spends in groupby(sorted(monthly_spend), lambda m: m[0]): @@ -254,39 +254,39 @@ class MockReportingProvider(ReportingInterface): return self.MONTHLY_SPEND_BY_ENVIRONMENT.get(environment_id, {}) def monthly_totals(self, workspace): - """Return month totals rolled up by environment, project, and workspace. + """Return month totals rolled up by environment, application, and workspace. - Data should returned with three top level keys, "workspace", "projects", + Data should returned with three top level keys, "workspace", "applications", and "environments". - The "projects" key will have budget data per month for each project, + The "applications" key will have budget data per month for each application, The "environments" key will have budget data for each environment. The "workspace" key will be total monthly spending for the workspace. For example: { "environments": { "X-Wing": { "Prod": { "01/2018": 75.42 } } }, - "projects": { "X-Wing": { "01/2018": 75.42 } }, + "applications": { "X-Wing": { "01/2018": 75.42 } }, "workspace": { "01/2018": 75.42 }, } """ - projects = workspace.projects + applications = workspace.applications if workspace.name in self.REPORT_FIXTURE_MAP: - projects = self.REPORT_FIXTURE_MAP[workspace.name]["projects"] + applications = self.REPORT_FIXTURE_MAP[workspace.name]["applications"] environments = { - project.name: { + application.name: { env.name: self.monthly_totals_for_environment(env.id) - for env in project.environments + for env in application.environments } - for project in projects + for application in applications } - project_totals = self._rollup_project_totals(environments) - workspace_totals = self._rollup_workspace_totals(project_totals) + application_totals = self._rollup_application_totals(environments) + workspace_totals = self._rollup_workspace_totals(application_totals) return { "environments": environments, - "projects": project_totals, + "applications": application_totals, "workspace": workspace_totals, } diff --git a/atst/domain/environments.py b/atst/domain/environments.py index 680248cd..7502266b 100644 --- a/atst/domain/environments.py +++ b/atst/domain/environments.py @@ -4,7 +4,7 @@ from sqlalchemy.orm.exc import NoResultFound from atst.database import db from atst.models.environment import Environment from atst.models.environment_role import EnvironmentRole -from atst.models.project import Project +from atst.models.application import Application from atst.models.permissions import Permissions from atst.domain.authz import Authorization from atst.domain.environment_roles import EnvironmentRoles @@ -14,18 +14,18 @@ from .exceptions import NotFoundError class Environments(object): @classmethod - def create(cls, project, name): - environment = Environment(project=project, name=name) + def create(cls, application, name): + environment = Environment(application=application, name=name) environment.cloud_id = app.csp.cloud.create_application(environment.name) db.session.add(environment) db.session.commit() return environment @classmethod - def create_many(cls, project, names): + def create_many(cls, application, names): environments = [] for name in names: - environment = Environments.create(project, name) + environment = Environments.create(application, name) environments.append(environment) db.session.add_all(environments) @@ -40,13 +40,13 @@ class Environments(object): return environment @classmethod - def for_user(cls, user, project): + def for_user(cls, user, application): return ( db.session.query(Environment) .join(EnvironmentRole) - .join(Project) + .join(Application) .filter(EnvironmentRole.user_id == user.id) - .filter(Environment.project_id == project.id) + .filter(Environment.project_id == application.id) .all() ) diff --git a/atst/domain/roles.py b/atst/domain/roles.py index 469126a8..d0b41aa3 100644 --- a/atst/domain/roles.py +++ b/atst/domain/roles.py @@ -58,7 +58,7 @@ WORKSPACE_ROLES = [ { "name": "owner", "display_name": "Workspace Owner", - "description": "Adds, edits, deactivates access to all projects, environments, and members. Views budget reports. Initiates and edits JEDI Cloud requests.", + "description": "Adds, edits, deactivates access to all applications, environments, and members. Views budget reports. Initiates and edits JEDI Cloud requests.", "permissions": [ Permissions.REQUEST_JEDI_WORKSPACE, Permissions.VIEW_ORIGINAL_JEDI_REQEUST, @@ -94,7 +94,7 @@ WORKSPACE_ROLES = [ { "name": "admin", "display_name": "Administrator", - "description": "Adds and edits projects, environments, members, but cannot deactivate. Cannot view budget reports or JEDI Cloud requests.", + "description": "Adds and edits applications, environments, members, but cannot deactivate. Cannot view budget reports or JEDI Cloud requests.", "permissions": [ Permissions.VIEW_USAGE_REPORT, Permissions.ADD_AND_ASSIGN_CSP_ROLES, @@ -125,13 +125,13 @@ WORKSPACE_ROLES = [ { "name": "developer", "display_name": "Developer", - "description": "Views only the projects and environments they are granted access to. Can also view members associated with each environment.", + "description": "Views only the applications and environments they are granted access to. Can also view members associated with each environment.", "permissions": [Permissions.VIEW_USAGE_REPORT, Permissions.VIEW_WORKSPACE], }, { "name": "billing_auditor", "display_name": "Billing Auditor", - "description": "Views only the projects and environments they are granted access to. Can also view budgets and reports associated with the workspace.", + "description": "Views only the applications and environments they are granted access to. Can also view budgets and reports associated with the workspace.", "permissions": [ Permissions.VIEW_USAGE_REPORT, Permissions.VIEW_USAGE_DOLLARS, @@ -140,7 +140,7 @@ WORKSPACE_ROLES = [ }, { "name": "security_auditor", - "description": "Views only the projects and environments they are granted access to. Can also view activity logs.", + "description": "Views only the applications and environments they are granted access to. Can also view activity logs.", "display_name": "Security Auditor", "permissions": [ Permissions.VIEW_ASSIGNED_ATAT_ROLE_CONFIGURATIONS, diff --git a/atst/domain/workspaces/scopes.py b/atst/domain/workspaces/scopes.py index e03d1098..69c64642 100644 --- a/atst/domain/workspaces/scopes.py +++ b/atst/domain/workspaces/scopes.py @@ -1,6 +1,6 @@ from atst.domain.authz import Authorization from atst.models.permissions import Permissions -from atst.domain.projects import Projects +from atst.domain.applications import Applications from atst.domain.environments import Environments @@ -24,25 +24,27 @@ class ScopedResource(object): class ScopedWorkspace(ScopedResource): """ An object that obeys the same API as a Workspace, but with the added - functionality that it only returns sub-resources (projects and environments) + functionality that it only returns sub-resources (applications and environments) that the given user is allowed to see. """ @property - def projects(self): - can_view_all_projects = Authorization.has_workspace_permission( + def applications(self): + can_view_all_applications = Authorization.has_workspace_permission( self.user, self.resource, Permissions.VIEW_APPLICATION_IN_WORKSPACE ) - if can_view_all_projects: - projects = self.resource.projects + if can_view_all_applications: + applications = self.resource.applications else: - projects = Projects.for_user(self.user, self.resource) + applications = Applications.for_user(self.user, self.resource) - return [ScopedProject(self.user, project) for project in projects] + return [ + ScopedApplication(self.user, application) for application in applications + ] -class ScopedProject(ScopedResource): +class ScopedApplication(ScopedResource): """ An object that obeys the same API as a Workspace, but with the added functionality that it only returns sub-resources (environments) diff --git a/atst/domain/workspaces/workspaces.py b/atst/domain/workspaces/workspaces.py index 7fe580ae..e8be09de 100644 --- a/atst/domain/workspaces/workspaces.py +++ b/atst/domain/workspaces/workspaces.py @@ -44,10 +44,10 @@ class Workspaces(object): return ScopedWorkspace(user, workspace) @classmethod - def get_for_update_projects(cls, user, workspace_id): + def get_for_update_applications(cls, user, workspace_id): workspace = WorkspacesQuery.get(workspace_id) Authorization.check_workspace_permission( - user, workspace, Permissions.ADD_APPLICATION_IN_WORKSPACE, "add project" + user, workspace, Permissions.ADD_APPLICATION_IN_WORKSPACE, "add application" ) return workspace diff --git a/atst/forms/project.py b/atst/forms/application.py similarity index 64% rename from atst/forms/project.py rename to atst/forms/application.py index 7ed30a00..d917f72d 100644 --- a/atst/forms/project.py +++ b/atst/forms/application.py @@ -5,29 +5,29 @@ from atst.forms.validators import ListItemRequired, ListItemsUnique from atst.utils.localization import translate -class ProjectForm(FlaskForm): +class ApplicationForm(FlaskForm): name = StringField( - label=translate("forms.project.name_label"), validators=[Required()] + label=translate("forms.application.name_label"), validators=[Required()] ) description = TextAreaField( - label=translate("forms.project.description_label"), validators=[Required()] + label=translate("forms.application.description_label"), validators=[Required()] ) -class NewProjectForm(ProjectForm): +class NewApplicationForm(ApplicationForm): EMPTY_ENVIRONMENT_NAMES = ["", None] environment_names = FieldList( - StringField(label=translate("forms.project.environment_names_label")), + StringField(label=translate("forms.application.environment_names_label")), validators=[ ListItemRequired( message=translate( - "forms.project.environment_names_required_validation_message" + "forms.application.environment_names_required_validation_message" ) ), ListItemsUnique( message=translate( - "forms.project.environment_names_unique_validation_message" + "forms.application.environment_names_unique_validation_message" ) ), ], diff --git a/atst/forms/data.py b/atst/forms/data.py index 202664fd..cce774c6 100644 --- a/atst/forms/data.py +++ b/atst/forms/data.py @@ -6,8 +6,8 @@ SERVICE_BRANCHES = [ ("Army and Air Force Exchange Service", "Army and Air Force Exchange Service"), ("Army, Department of the", "Army, Department of the"), ( - "Defense Advanced Research Projects Agency", - "Defense Advanced Research Projects Agency", + "Defense Advanced Research Applications Agency", + "Defense Advanced Research Applications Agency", ), ("Defense Commissary Agency", "Defense Commissary Agency"), ("Defense Contract Audit Agency", "Defense Contract Audit Agency"), @@ -137,7 +137,7 @@ ENVIRONMENT_ROLES = [ "billing_administrator", { "name": "Billing Administrator", - "description": "Views cloud resource usage, budget reports, and invoices; Tracks budgets, including spend reports, cost planning and projections, and sets limits based on cloud service usage.", + "description": "Views cloud resource usage, budget reports, and invoices; Tracks budgets, including spend reports, cost planning and applicationions, and sets limits based on cloud service usage.", }, ), ( @@ -162,7 +162,7 @@ ENVIRONMENT_ROLES = [ ENV_ROLE_MODAL_DESCRIPTION = { "header": "Assign Environment Role", - "body": "An environment role determines the permissions a member of the workspace assumes when using the JEDI Cloud.

A member may have different environment roles across different projects. A member can only have one assigned environment role in a given environment.", + "body": "An environment role determines the permissions a member of the workspace assumes when using the JEDI Cloud.

A member may have different environment roles across different applications. A member can only have one assigned environment role in a given environment.", } FUNDING_TYPES = [ @@ -210,7 +210,7 @@ TEAM_EXPERIENCE = [ ("built_3", "Built or Migrated 3-5 applications"), ( "built_many", - "Built or migrated many applications, or consulted on several such projects", + "Built or migrated many applications, or consulted on several such applications", ), ] diff --git a/atst/forms/edit_member.py b/atst/forms/edit_member.py index 974621c1..e2683d95 100644 --- a/atst/forms/edit_member.py +++ b/atst/forms/edit_member.py @@ -8,7 +8,7 @@ from .data import WORKSPACE_ROLES class EditMemberForm(FlaskForm): - # This form also accepts a field for each environment in each project + # This form also accepts a field for each environment in each application # that the user is a member of workspace_role = SelectField( diff --git a/atst/models/__init__.py b/atst/models/__init__.py index 0116c5ff..6705589c 100644 --- a/atst/models/__init__.py +++ b/atst/models/__init__.py @@ -11,7 +11,7 @@ from .workspace_role import WorkspaceRole from .pe_number import PENumber from .legacy_task_order import LegacyTaskOrder from .workspace import Workspace -from .project import Project +from .application import Application from .environment import Environment from .attachment import Attachment from .request_revision import RequestRevision diff --git a/atst/models/project.py b/atst/models/application.py similarity index 72% rename from atst/models/project.py rename to atst/models/application.py index cd9b5593..c3e0a05c 100644 --- a/atst/models/project.py +++ b/atst/models/application.py @@ -6,7 +6,7 @@ from atst.models.types import Id from atst.models import mixins -class Project(Base, mixins.TimestampsMixin, mixins.AuditableMixin): +class Application(Base, mixins.TimestampsMixin, mixins.AuditableMixin): __tablename__ = "projects" id = Id() @@ -15,13 +15,13 @@ class Project(Base, mixins.TimestampsMixin, mixins.AuditableMixin): workspace_id = Column(ForeignKey("workspaces.id"), nullable=False) workspace = relationship("Workspace") - environments = relationship("Environment", back_populates="project") + environments = relationship("Environment", back_populates="application") @property def displayname(self): return self.name def __repr__(self): # pragma: no cover - return "".format( + return "".format( self.name, self.description, self.workspace.name, self.id ) diff --git a/atst/models/environment.py b/atst/models/environment.py index 01348d39..2c4cb43c 100644 --- a/atst/models/environment.py +++ b/atst/models/environment.py @@ -13,7 +13,7 @@ class Environment(Base, mixins.TimestampsMixin, mixins.AuditableMixin): name = Column(String, nullable=False) project_id = Column(ForeignKey("projects.id"), nullable=False) - project = relationship("Project") + application = relationship("Application") cloud_id = Column(String) @@ -31,16 +31,16 @@ class Environment(Base, mixins.TimestampsMixin, mixins.AuditableMixin): @property def workspace(self): - return self.project.workspace + return self.application.workspace def auditable_workspace_id(self): - return self.project.workspace_id + return self.application.workspace_id def __repr__(self): - return "".format( + return "".format( self.name, self.num_users, - self.project.name, - self.project.workspace.name, + self.application.name, + self.application.workspace.name, self.id, ) diff --git a/atst/models/environment_role.py b/atst/models/environment_role.py index e3e3f008..158b5cfe 100644 --- a/atst/models/environment_role.py +++ b/atst/models/environment_role.py @@ -45,10 +45,10 @@ class EnvironmentRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin): "role": self.role, "environment": self.environment.displayname, "environment_id": str(self.environment_id), - "project": self.environment.project.name, - "project_id": str(self.environment.project_id), - "workspace": self.environment.project.workspace.name, - "workspace_id": str(self.environment.project.workspace.id), + "application": self.environment.application.name, + "application_id": str(self.environment.project_id), + "workspace": self.environment.application.workspace.name, + "workspace_id": str(self.environment.application.workspace.id), } diff --git a/atst/models/task_order.py b/atst/models/task_order.py index b4c3e50d..19d8a0ab 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -47,7 +47,7 @@ class TaskOrder(Base, mixins.TimestampsMixin): defense_component = Column(String) # Department of Defense Component app_migration = Column(String) # App Migration native_apps = Column(String) # Native Apps - complexity = Column(ARRAY(String)) # Project Complexity + complexity = Column(ARRAY(String)) # Application Complexity complexity_other = Column(String) dev_team = Column(ARRAY(String)) # Development Team dev_team_other = Column(String) diff --git a/atst/models/workspace.py b/atst/models/workspace.py index d02acd19..fccc6ee4 100644 --- a/atst/models/workspace.py +++ b/atst/models/workspace.py @@ -14,7 +14,7 @@ class Workspace(Base, mixins.TimestampsMixin, mixins.AuditableMixin): id = types.Id() name = Column(String) request_id = Column(ForeignKey("requests.id"), nullable=True) - projects = relationship("Project", back_populates="workspace") + applications = relationship("Application", back_populates="workspace") roles = relationship("WorkspaceRole") task_orders = relationship("TaskOrder") @@ -54,7 +54,7 @@ class Workspace(Base, mixins.TimestampsMixin, mixins.AuditableMixin): @property def all_environments(self): - return list(chain.from_iterable(p.environments for p in self.projects)) + return list(chain.from_iterable(p.environments for p in self.applications)) def auditable_workspace_id(self): return self.id diff --git a/atst/models/workspace_role.py b/atst/models/workspace_role.py index 580dc07f..84c64a8f 100644 --- a/atst/models/workspace_role.py +++ b/atst/models/workspace_role.py @@ -8,7 +8,7 @@ from .types import Id from atst.database import db from atst.models.environment_role import EnvironmentRole -from atst.models.project import Project +from atst.models.application import Application from atst.models.environment import Environment from atst.models.role import Role @@ -126,9 +126,9 @@ class WorkspaceRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin): return ( db.session.query(EnvironmentRole) .join(EnvironmentRole.environment) - .join(Environment.project) - .join(Project.workspace) - .filter(Project.workspace_id == self.workspace_id) + .join(Environment.application) + .join(Application.workspace) + .filter(Application.workspace_id == self.workspace_id) .filter(EnvironmentRole.user_id == self.user_id) .count() ) @@ -138,9 +138,9 @@ class WorkspaceRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin): return ( db.session.query(EnvironmentRole) .join(EnvironmentRole.environment) - .join(Environment.project) - .join(Project.workspace) - .filter(Project.workspace_id == self.workspace_id) + .join(Environment.application) + .join(Application.workspace) + .filter(Application.workspace_id == self.workspace_id) .filter(EnvironmentRole.user_id == self.user_id) .all() ) diff --git a/atst/routes/__init__.py b/atst/routes/__init__.py index 07550af7..af63d5dd 100644 --- a/atst/routes/__init__.py +++ b/atst/routes/__init__.py @@ -67,7 +67,7 @@ def home(): ) else: return redirect( - url_for("workspaces.workspace_projects", workspace_id=workspace_id) + url_for("workspaces.workspace_applications", workspace_id=workspace_id) ) else: return redirect(url_for("workspaces.workspaces")) diff --git a/atst/routes/requests/financial_verification.py b/atst/routes/requests/financial_verification.py index 4ccec8fd..52d8311e 100644 --- a/atst/routes/requests/financial_verification.py +++ b/atst/routes/requests/financial_verification.py @@ -250,7 +250,9 @@ def update_financial_verification(request_id): if updated_request.legacy_task_order.verified: workspace = Requests.auto_approve_and_create_workspace(updated_request) flash("new_workspace") - return redirect(url_for("workspaces.new_project", workspace_id=workspace.id)) + return redirect( + url_for("workspaces.new_application", workspace_id=workspace.id) + ) else: return redirect(url_for("requests.requests_index", modal="pendingCCPOApproval")) diff --git a/atst/routes/requests/index.py b/atst/routes/requests/index.py index 55967ecf..69f433ba 100644 --- a/atst/routes/requests/index.py +++ b/atst/routes/requests/index.py @@ -66,7 +66,7 @@ class RequestsIndex(object): def _workspace_link_for_request(self, request): if request.is_approved: return url_for( - "workspaces.workspace_projects", workspace_id=request.workspace.id + "workspaces.workspace_applications", workspace_id=request.workspace.id ) else: return None diff --git a/atst/routes/workspaces/__init__.py b/atst/routes/workspaces/__init__.py index 96aa24b5..9d822771 100644 --- a/atst/routes/workspaces/__init__.py +++ b/atst/routes/workspaces/__init__.py @@ -3,7 +3,7 @@ from flask import Blueprint, request as http_request, g, render_template workspaces_bp = Blueprint("workspaces", __name__) from . import index -from . import projects +from . import applications from . import members from . import invitations from . import task_orders diff --git a/atst/routes/workspaces/applications.py b/atst/routes/workspaces/applications.py new file mode 100644 index 00000000..c80374c0 --- /dev/null +++ b/atst/routes/workspaces/applications.py @@ -0,0 +1,102 @@ +from flask import ( + current_app as app, + g, + redirect, + render_template, + request as http_request, + url_for, +) + +from . import workspaces_bp +from atst.domain.environment_roles import EnvironmentRoles +from atst.domain.exceptions import UnauthorizedError +from atst.domain.applications import Applications +from atst.domain.workspaces import Workspaces +from atst.forms.application import NewApplicationForm, ApplicationForm + + +@workspaces_bp.route("/workspaces//applications") +def workspace_applications(workspace_id): + workspace = Workspaces.get(g.current_user, workspace_id) + return render_template("workspaces/applications/index.html", workspace=workspace) + + +@workspaces_bp.route("/workspaces//applications/new") +def new_application(workspace_id): + workspace = Workspaces.get_for_update_applications(g.current_user, workspace_id) + form = NewApplicationForm() + return render_template( + "workspaces/applications/new.html", workspace=workspace, form=form + ) + + +@workspaces_bp.route("/workspaces//applications/new", methods=["POST"]) +def create_application(workspace_id): + workspace = Workspaces.get_for_update_applications(g.current_user, workspace_id) + form = NewApplicationForm(http_request.form) + + if form.validate(): + application_data = form.data + Applications.create( + g.current_user, + workspace, + application_data["name"], + application_data["description"], + application_data["environment_names"], + ) + return redirect( + url_for("workspaces.workspace_applications", workspace_id=workspace.id) + ) + else: + return render_template( + "workspaces/applications/new.html", workspace=workspace, form=form + ) + + +@workspaces_bp.route("/workspaces//applications//edit") +def edit_application(workspace_id, application_id): + workspace = Workspaces.get_for_update_applications(g.current_user, workspace_id) + application = Applications.get(g.current_user, workspace, application_id) + form = ApplicationForm(name=application.name, description=application.description) + + return render_template( + "workspaces/applications/edit.html", + workspace=workspace, + application=application, + form=form, + ) + + +@workspaces_bp.route( + "/workspaces//applications//edit", methods=["POST"] +) +def update_application(workspace_id, application_id): + workspace = Workspaces.get_for_update_applications(g.current_user, workspace_id) + application = Applications.get(g.current_user, workspace, application_id) + form = ApplicationForm(http_request.form) + if form.validate(): + application_data = form.data + Applications.update(g.current_user, workspace, application, application_data) + + return redirect( + url_for("workspaces.workspace_applications", workspace_id=workspace.id) + ) + else: + return render_template( + "workspaces/applications/edit.html", + workspace=workspace, + application=application, + form=form, + ) + + +@workspaces_bp.route("/workspaces//environments//access") +def access_environment(workspace_id, environment_id): + env_role = EnvironmentRoles.get(g.current_user.id, environment_id) + if not env_role: + raise UnauthorizedError( + g.current_user, "access environment {}".format(environment_id) + ) + else: + token = app.csp.cloud.get_access_token(env_role) + return redirect(url_for("atst.csp_environment_access", token=token)) diff --git a/atst/routes/workspaces/index.py b/atst/routes/workspaces/index.py index 063f66f5..c9462bbf 100644 --- a/atst/routes/workspaces/index.py +++ b/atst/routes/workspaces/index.py @@ -32,7 +32,7 @@ def edit_workspace(workspace_id): if form.validate(): Workspaces.update(workspace, form.data) return redirect( - url_for("workspaces.workspace_projects", workspace_id=workspace.id) + url_for("workspaces.workspace_applications", workspace_id=workspace.id) ) else: return render_template("workspaces/edit.html", form=form, workspace=workspace) @@ -40,7 +40,9 @@ def edit_workspace(workspace_id): @workspaces_bp.route("/workspaces/") def show_workspace(workspace_id): - return redirect(url_for("workspaces.workspace_projects", workspace_id=workspace_id)) + return redirect( + url_for("workspaces.workspace_applications", workspace_id=workspace_id) + ) @workspaces_bp.route("/workspaces//reports") diff --git a/atst/routes/workspaces/members.py b/atst/routes/workspaces/members.py index 7c590153..b9a53452 100644 --- a/atst/routes/workspaces/members.py +++ b/atst/routes/workspaces/members.py @@ -4,7 +4,7 @@ from flask import render_template, request as http_request, g, redirect, url_for from . import workspaces_bp from atst.domain.exceptions import AlreadyExistsError -from atst.domain.projects import Projects +from atst.domain.applications import Applications from atst.domain.workspaces import Workspaces from atst.domain.workspace_roles import WorkspaceRoles, MEMBER_STATUS_CHOICES from atst.domain.environments import Environments @@ -101,7 +101,7 @@ def view_member(workspace_id, member_id): "edit this workspace user", ) member = WorkspaceRoles.get(workspace_id, member_id) - projects = Projects.get_all(g.current_user, member, workspace) + applications = Applications.get_all(g.current_user, member, workspace) form = EditMemberForm(workspace_role=member.role_name) editable = g.current_user == member.user can_revoke_access = Workspaces.can_revoke_access_for(workspace, member) @@ -113,7 +113,7 @@ def view_member(workspace_id, member_id): "workspaces/members/edit.html", workspace=workspace, member=member, - projects=projects, + applications=applications, form=form, choices=ENVIRONMENT_ROLES, env_role_modal_description=ENV_ROLE_MODAL_DESCRIPTION, diff --git a/atst/routes/workspaces/projects.py b/atst/routes/workspaces/projects.py deleted file mode 100644 index 67cc2931..00000000 --- a/atst/routes/workspaces/projects.py +++ /dev/null @@ -1,99 +0,0 @@ -from flask import ( - current_app as app, - g, - redirect, - render_template, - request as http_request, - url_for, -) - -from . import workspaces_bp -from atst.domain.environment_roles import EnvironmentRoles -from atst.domain.exceptions import UnauthorizedError -from atst.domain.projects import Projects -from atst.domain.workspaces import Workspaces -from atst.forms.project import NewProjectForm, ProjectForm - - -@workspaces_bp.route("/workspaces//projects") -def workspace_projects(workspace_id): - workspace = Workspaces.get(g.current_user, workspace_id) - return render_template("workspaces/projects/index.html", workspace=workspace) - - -@workspaces_bp.route("/workspaces//projects/new") -def new_project(workspace_id): - workspace = Workspaces.get_for_update_projects(g.current_user, workspace_id) - form = NewProjectForm() - return render_template( - "workspaces/projects/new.html", workspace=workspace, form=form - ) - - -@workspaces_bp.route("/workspaces//projects/new", methods=["POST"]) -def create_project(workspace_id): - workspace = Workspaces.get_for_update_projects(g.current_user, workspace_id) - form = NewProjectForm(http_request.form) - - if form.validate(): - project_data = form.data - Projects.create( - g.current_user, - workspace, - project_data["name"], - project_data["description"], - project_data["environment_names"], - ) - return redirect( - url_for("workspaces.workspace_projects", workspace_id=workspace.id) - ) - else: - return render_template( - "workspaces/projects/new.html", workspace=workspace, form=form - ) - - -@workspaces_bp.route("/workspaces//projects//edit") -def edit_project(workspace_id, project_id): - workspace = Workspaces.get_for_update_projects(g.current_user, workspace_id) - project = Projects.get(g.current_user, workspace, project_id) - form = ProjectForm(name=project.name, description=project.description) - - return render_template( - "workspaces/projects/edit.html", workspace=workspace, project=project, form=form - ) - - -@workspaces_bp.route( - "/workspaces//projects//edit", methods=["POST"] -) -def update_project(workspace_id, project_id): - workspace = Workspaces.get_for_update_projects(g.current_user, workspace_id) - project = Projects.get(g.current_user, workspace, project_id) - form = ProjectForm(http_request.form) - if form.validate(): - project_data = form.data - Projects.update(g.current_user, workspace, project, project_data) - - return redirect( - url_for("workspaces.workspace_projects", workspace_id=workspace.id) - ) - else: - return render_template( - "workspaces/projects/edit.html", - workspace=workspace, - project=project, - form=form, - ) - - -@workspaces_bp.route("/workspaces//environments//access") -def access_environment(workspace_id, environment_id): - env_role = EnvironmentRoles.get(g.current_user.id, environment_id) - if not env_role: - raise UnauthorizedError( - g.current_user, "access environment {}".format(environment_id) - ) - else: - token = app.csp.cloud.get_access_token(env_role) - return redirect(url_for("atst.csp_environment_access", token=token)) diff --git a/js/components/forms/edit_project_roles.js b/js/components/forms/edit_application_roles.js similarity index 92% rename from js/components/forms/edit_project_roles.js rename to js/components/forms/edit_application_roles.js index d1c5107c..ef7438d5 100644 --- a/js/components/forms/edit_project_roles.js +++ b/js/components/forms/edit_application_roles.js @@ -4,7 +4,7 @@ import toggler from '../toggler' import EditEnvironmentRole from './edit_environment_role' export default { - name: 'edit-project-roles', + name: 'edit-application-roles', mixins: [FormMixin, Modal], diff --git a/js/components/forms/edit_environment_role.js b/js/components/forms/edit_environment_role.js index 45a3506a..4bb8fb55 100644 --- a/js/components/forms/edit_environment_role.js +++ b/js/components/forms/edit_environment_role.js @@ -20,7 +20,7 @@ export default { props: { choices: Array, initialData: String, - projectId: String + applicationId: String }, data: function () { @@ -30,7 +30,7 @@ export default { }, mounted: function() { - this.$root.$on('revoke-' + this.projectId, this.revoke) + this.$root.$on('revoke-' + this.applicationId, this.revoke) }, methods: { diff --git a/js/components/forms/new_project.js b/js/components/forms/new_application.js similarity index 99% rename from js/components/forms/new_project.js rename to js/components/forms/new_application.js index ebcea13f..d607b101 100644 --- a/js/components/forms/new_project.js +++ b/js/components/forms/new_application.js @@ -4,7 +4,7 @@ import textinput from '../text_input' const createEnvironment = (name) => ({ name }) export default { - name: 'new-project', + name: 'new-application', mixins: [FormMixin], diff --git a/js/components/requests_list.js b/js/components/requests_list.js index 735dcdd3..6f3adcb9 100644 --- a/js/components/requests_list.js +++ b/js/components/requests_list.js @@ -60,7 +60,7 @@ export default { sortFunc: defaultSort, }, { - displayName: 'Projected Annual Usage ($)', + displayName: 'Applicationed Annual Usage ($)', attr: 'annual_usage', sortFunc: defaultSort, }, diff --git a/js/components/tables/spend_table.js b/js/components/tables/spend_table.js index fd694ce7..c87028ab 100644 --- a/js/components/tables/spend_table.js +++ b/js/components/tables/spend_table.js @@ -5,7 +5,7 @@ export default { name: 'spend-table', props: { - projects: Object, + applications: Object, workspace: Object, environments: Object, currentMonthIndex: String, @@ -15,21 +15,21 @@ export default { data: function () { return { - projectsState: this.projects + applicationsState: this.applications } }, created: function () { - Object.keys(this.projects).forEach(project => { - set(this.projectsState[project], 'isVisible', false) + Object.keys(this.applications).forEach(application => { + set(this.applicationsState[application], 'isVisible', false) }) }, methods: { - toggle: function (e, projectName) { - this.projectsState = Object.assign(this.projectsState, { - [projectName]: Object.assign(this.projectsState[projectName],{ - isVisible: !this.projectsState[projectName].isVisible + toggle: function (e, applicationName) { + this.applicationsState = Object.assign(this.applicationsState, { + [applicationName]: Object.assign(this.applicationsState[applicationName],{ + isVisible: !this.applicationsState[applicationName].isVisible }) }) }, diff --git a/js/index.js b/js/index.js index 5bbee5d1..97fe3acc 100644 --- a/js/index.js +++ b/js/index.js @@ -13,9 +13,9 @@ import DetailsOfUse from './components/forms/details_of_use' import poc from './components/forms/poc' import financial from './components/forms/financial' import toggler from './components/toggler' -import NewProject from './components/forms/new_project' +import NewApplication from './components/forms/new_application' import EditEnvironmentRole from './components/forms/edit_environment_role' -import EditProjectRoles from './components/forms/edit_project_roles' +import EditApplicationRoles from './components/forms/edit_application_roles' import funding from './components/forms/funding' import Modal from './mixins/modal' import selector from './components/selector' @@ -44,7 +44,7 @@ const app = new Vue({ DetailsOfUse, poc, financial, - NewProject, + NewApplication, selector, BudgetChart, SpendTable, @@ -52,7 +52,7 @@ const app = new Vue({ MembersList, LocalDatetime, EditEnvironmentRole, - EditProjectRoles, + EditApplicationRoles, RequestsList, ConfirmationPopover, funding, diff --git a/script/seed_sample.py b/script/seed_sample.py index 8d62759a..84ef41f4 100644 --- a/script/seed_sample.py +++ b/script/seed_sample.py @@ -10,7 +10,7 @@ from atst.app import make_config, make_app from atst.domain.users import Users from atst.domain.requests import Requests from atst.domain.workspaces import Workspaces -from atst.domain.projects import Projects +from atst.domain.applications import Applications from atst.domain.workspace_roles import WorkspaceRoles from atst.models.invitation import Status as InvitationStatus from atst.domain.exceptions import AlreadyExistsError @@ -122,7 +122,7 @@ def seed_db(): db.session.commit() - Projects.create( + Applications.create( user, workspace=workspace, name="First Project", diff --git a/styles/atat.scss b/styles/atat.scss index 37389c5e..ce01d0e9 100644 --- a/styles/atat.scss +++ b/styles/atat.scss @@ -42,8 +42,8 @@ @import 'sections/login'; @import 'sections/home'; @import 'sections/request_approval'; -@import 'sections/projects_list'; -@import 'sections/project_edit'; +@import 'sections/application_list'; +@import 'sections/application_edit'; @import 'sections/member_edit'; @import 'sections/reports'; @import 'sections/task_order'; diff --git a/styles/components/_site_action.scss b/styles/components/_site_action.scss index 98af0b18..7b42a4ec 100644 --- a/styles/components/_site_action.scss +++ b/styles/components/_site_action.scss @@ -12,4 +12,4 @@ color: $color-primary !important; } -} \ No newline at end of file +} diff --git a/styles/elements/_kpi.scss b/styles/elements/_kpi.scss index f68fe30c..05c2ff78 100644 --- a/styles/elements/_kpi.scss +++ b/styles/elements/_kpi.scss @@ -22,4 +22,4 @@ padding-bottom: $gap / 2; } -} \ No newline at end of file +} diff --git a/styles/sections/_project_edit.scss b/styles/sections/_application_edit.scss similarity index 79% rename from styles/sections/_project_edit.scss rename to styles/sections/_application_edit.scss index ef1bc39a..ce05522f 100644 --- a/styles/sections/_project_edit.scss +++ b/styles/sections/_application_edit.scss @@ -1,4 +1,4 @@ -.project-edit__env-list-item { +.application-edit__env-list-item { display: flex; flex-direction: row; align-items: flex-end; @@ -8,7 +8,7 @@ flex-grow: 1; } - .project-edit__env-list-item__remover { + .application-edit__env-list-item__remover { @include icon-link; @include icon-link-vertical; @include icon-link-color($color-red, $color-red-lightest); diff --git a/styles/sections/_projects_list.scss b/styles/sections/_application_list.scss similarity index 65% rename from styles/sections/_projects_list.scss rename to styles/sections/_application_list.scss index e48bb7af..0294d646 100644 --- a/styles/sections/_projects_list.scss +++ b/styles/sections/_application_list.scss @@ -1,5 +1,5 @@ -.project-list-item { - .project-list-item__environment { +.application-list-item { + .application-list-item__environment { display: flex; flex-direction: row; justify-content: space-between; @@ -8,12 +8,12 @@ margin: 0; } - .project-list-item__environment__link { + .application-list-item__environment__link { @include icon-link; @include icon-link-large; } - .project-list-item__environment__members { + .application-list-item__environment__members { display: flex; flex-direction: row; align-items: center; diff --git a/styles/sections/_home.scss b/styles/sections/_home.scss index 176c197a..9098b268 100644 --- a/styles/sections/_home.scss +++ b/styles/sections/_home.scss @@ -67,4 +67,4 @@ } } -} \ No newline at end of file +} diff --git a/styles/sections/_reports.scss b/styles/sections/_reports.scss index 39abad06..ccd5a5f8 100644 --- a/styles/sections/_reports.scss +++ b/styles/sections/_reports.scss @@ -289,8 +289,8 @@ } } - .spend-table__project { - .spend-table__project__toggler { + .spend-table__application { + .spend-table__application__toggler { @include icon-link-color($color-black-light, $color-gray-lightest); margin-left: -$gap; @@ -300,7 +300,7 @@ } } - .spend-table__project__env { + .spend-table__application__env { margin-left: $gap; &:last-child { diff --git a/templates/audit_log/events/project.html b/templates/audit_log/events/application.html similarity index 100% rename from templates/audit_log/events/project.html rename to templates/audit_log/events/application.html diff --git a/templates/audit_log/events/environment_role.html b/templates/audit_log/events/environment_role.html index ea25fd6f..f59b8fe0 100644 --- a/templates/audit_log/events/environment_role.html +++ b/templates/audit_log/events/environment_role.html @@ -12,7 +12,7 @@
in Environment {{ event.event_details["environment_id"] }} ({{ event.event_details["environment"] }})
- in Application {{ event.event_details["project_id"] }} ({{ event.event_details["project"] }}) + in Application {{ event.event_details["application_id"] }} ({{ event.event_details["application"] }})
in Portfolio {{ event.event_details["workspace_id"] }} ({{ event.event_details["workspace"] }}) {% endif %} diff --git a/templates/fragments/edit_project_form.html b/templates/fragments/edit_application_form.html similarity index 53% rename from templates/fragments/edit_project_form.html rename to templates/fragments/edit_application_form.html index f490a7e8..9b9e1bbc 100644 --- a/templates/fragments/edit_project_form.html +++ b/templates/fragments/edit_application_form.html @@ -1,6 +1,6 @@ {% from "components/text_input.html" import TextInput %} -{% set title_text = ('fragments.edit_project_form.existing_project_title' | translate({ "project_name": project.name })) if project else ('fragments.edit_project_form.new_project_title' | translate) %} +{% set title_text = ('fragments.edit_application_form.existing_application_title' | translate({ "application_name": application.name })) if application else ('fragments.edit_application_form.new_application_title' | translate) %} {{ form.csrf_token }}
@@ -10,7 +10,7 @@

- {{ "fragments.edit_project_form.explain" | translate }} + {{ "fragments.edit_application_form.explain" | translate }}

{{ TextInput(form.name) }} {{ TextInput(form.description, paragraph=True) }} diff --git a/templates/help/docs/getting-started.html b/templates/help/docs/getting-started.html index 61065f83..deb9b935 100644 --- a/templates/help/docs/getting-started.html +++ b/templates/help/docs/getting-started.html @@ -3,7 +3,7 @@ {% set subnav = [ {"label":"Financial Verification", "href":"#financial-verification"}, {"label":"ID/IQ CLINs", "href":"#idiq-clins"}, - {"label":"JEDI Cloud Projects", "href":"#jedi-cloud-projects"}, + {"label":"JEDI Cloud Applications", "href":"#jedi-cloud-applications"}, ] %} {% block doc_content %} @@ -122,13 +122,13 @@
-

JEDI Cloud Applications

+

JEDI Cloud Applications

How are applications organized in the JEDI Cloud?

Application Structure for JEDI Cloud

-

Separate your portfolio into applications and environments; this allows your team to manage user access to systems more securely and track expenditures for each project.

+

Separate your portfolio into applications and environments; this allows your team to manage user access to systems more securely and track expenditures for each application.

Here’s an example:
Application A has a development environment, production environment, and sandbox environment. The cloud resources in the development environment are grouped and accessed separately from the production environment and sandbox environment.

diff --git a/templates/navigation/workspace_navigation.html b/templates/navigation/workspace_navigation.html index 9ef4dd86..2edacc44 100644 --- a/templates/navigation/workspace_navigation.html +++ b/templates/navigation/workspace_navigation.html @@ -3,14 +3,14 @@