project -> application everywhere
This commit is contained in:
parent
9ad3c45200
commit
3fc323d785
@ -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
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
)
|
||||
),
|
||||
],
|
@ -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.<br/><br/>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.<br/><br/>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",
|
||||
),
|
||||
]
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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 "<Project(name='{}', description='{}', workspace='{}', id='{}')>".format(
|
||||
return "<Application(name='{}', description='{}', workspace='{}', id='{}')>".format(
|
||||
self.name, self.description, self.workspace.name, self.id
|
||||
)
|
@ -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 "<Environment(name='{}', num_users='{}', project='{}', workspace='{}', id='{}')>".format(
|
||||
return "<Environment(name='{}', num_users='{}', application='{}', workspace='{}', id='{}')>".format(
|
||||
self.name,
|
||||
self.num_users,
|
||||
self.project.name,
|
||||
self.project.workspace.name,
|
||||
self.application.name,
|
||||
self.application.workspace.name,
|
||||
self.id,
|
||||
)
|
||||
|
@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
)
|
||||
|
@ -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"))
|
||||
|
@ -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"))
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
102
atst/routes/workspaces/applications.py
Normal file
102
atst/routes/workspaces/applications.py
Normal file
@ -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/<workspace_id>/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/<workspace_id>/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/<workspace_id>/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/<workspace_id>/applications/<application_id>/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/<workspace_id>/applications/<application_id>/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/<workspace_id>/environments/<environment_id>/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))
|
@ -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/<workspace_id>")
|
||||
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/<workspace_id>/reports")
|
||||
|
@ -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,
|
||||
|
@ -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/<workspace_id>/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/<workspace_id>/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/<workspace_id>/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/<workspace_id>/projects/<project_id>/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/<workspace_id>/projects/<project_id>/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/<workspace_id>/environments/<environment_id>/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))
|
@ -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],
|
||||
|
@ -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: {
|
||||
|
@ -4,7 +4,7 @@ import textinput from '../text_input'
|
||||
const createEnvironment = (name) => ({ name })
|
||||
|
||||
export default {
|
||||
name: 'new-project',
|
||||
name: 'new-application',
|
||||
|
||||
mixins: [FormMixin],
|
||||
|
@ -60,7 +60,7 @@ export default {
|
||||
sortFunc: defaultSort,
|
||||
},
|
||||
{
|
||||
displayName: 'Projected Annual Usage ($)',
|
||||
displayName: 'Applicationed Annual Usage ($)',
|
||||
attr: 'annual_usage',
|
||||
sortFunc: defaultSort,
|
||||
},
|
||||
|
@ -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
|
||||
})
|
||||
})
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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';
|
||||
|
@ -12,4 +12,4 @@
|
||||
color: $color-primary !important;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -22,4 +22,4 @@
|
||||
padding-bottom: $gap / 2;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
@ -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;
|
@ -67,4 +67,4 @@
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -12,7 +12,7 @@
|
||||
<br>
|
||||
in Environment <code>{{ event.event_details["environment_id"] }}</code> ({{ event.event_details["environment"] }})
|
||||
<br>
|
||||
in Application <code>{{ event.event_details["project_id"] }}</code> ({{ event.event_details["project"] }})
|
||||
in Application <code>{{ event.event_details["application_id"] }}</code> ({{ event.event_details["application"] }})
|
||||
<br>
|
||||
in Portfolio <code>{{ event.event_details["workspace_id"] }}</code> ({{ event.event_details["workspace"] }})
|
||||
{% endif %}
|
||||
|
@ -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 }}
|
||||
<div class="panel">
|
||||
@ -10,7 +10,7 @@
|
||||
|
||||
<div class="panel__content">
|
||||
<p>
|
||||
{{ "fragments.edit_project_form.explain" | translate }}
|
||||
{{ "fragments.edit_application_form.explain" | translate }}
|
||||
</p>
|
||||
{{ TextInput(form.name) }}
|
||||
{{ TextInput(form.description, paragraph=True) }}
|
@ -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 @@
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id='jedi-cloud-projects'>JEDI Cloud Applications</h2>
|
||||
<h2 id='jedi-cloud-applications'>JEDI Cloud Applications</h2>
|
||||
|
||||
<h3>How are applications organized in the JEDI Cloud?</h3>
|
||||
|
||||
<h4>Application Structure for JEDI Cloud</h4>
|
||||
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<p>Here’s an example:<br>
|
||||
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.</p>
|
||||
|
||||
|
@ -3,14 +3,14 @@
|
||||
<nav class='sidenav workspace-navigation'>
|
||||
<ul>
|
||||
{{ SidenavItem(
|
||||
("navigation.workspace_navigation.projects" | translate),
|
||||
href=url_for("workspaces.workspace_projects", workspace_id=workspace.id),
|
||||
active=request.url_rule.rule.startswith('/workspaces/<workspace_id>/projects'),
|
||||
("navigation.workspace_navigation.applications" | translate),
|
||||
href=url_for("workspaces.workspace_applications", workspace_id=workspace.id),
|
||||
active=request.url_rule.rule.startswith('/workspaces/<workspace_id>/applications'),
|
||||
subnav=None if not user_can(permissions.ADD_APPLICATION_IN_WORKSPACE) else [
|
||||
{
|
||||
"label": ("navigation.workspace_navigation.add_new_project_label" | translate),
|
||||
"href": url_for('workspaces.new_project', workspace_id=workspace.id),
|
||||
"active": g.matchesPath('\/workspaces\/[A-Za-z0-9-]*\/projects'),
|
||||
"label": ("navigation.workspace_navigation.add_new_application_label" | translate),
|
||||
"href": url_for('workspaces.new_application', workspace_id=workspace.id),
|
||||
"active": g.matchesPath('\/workspaces\/[A-Za-z0-9-]*\/applications'),
|
||||
"icon": "plus"
|
||||
}
|
||||
]
|
||||
|
@ -17,7 +17,7 @@
|
||||
<p>The Portfolio Owner is the primary point of contact and technical administrator of the JEDI Cloud Portfolio and will have the
|
||||
following responsibilities:</p>
|
||||
<ul>
|
||||
<li>Organize your cloud-hosted systems into projects and environments</li>
|
||||
<li>Organize your cloud-hosted systems into applications and environments</li>
|
||||
<li>Add users to this portfolio and manage members</li>
|
||||
<li>Manage access to the JEDI Cloud service provider’s portal</li>
|
||||
</ul>
|
||||
|
@ -4,11 +4,11 @@
|
||||
|
||||
{% block workspace_content %}
|
||||
|
||||
<form method="POST" action="{{ url_for('workspaces.edit_project', workspace_id=workspace.id, project_id=project.id) }}">
|
||||
<form method="POST" action="{{ url_for('workspaces.edit_application', workspace_id=workspace.id, application_id=application.id) }}">
|
||||
|
||||
{% include "fragments/edit_project_form.html" %}
|
||||
{% include "fragments/edit_application_form.html" %}
|
||||
|
||||
<div class="block-list project-list-item">
|
||||
<div class="block-list application-list-item">
|
||||
<header class="block-list__header block-list__header--grow">
|
||||
<h2 class="block-list__title">Application Environments</h2>
|
||||
<p>
|
||||
@ -17,8 +17,8 @@
|
||||
</header>
|
||||
|
||||
<ul>
|
||||
{% for environment in project.environments %}
|
||||
<li class="block-list__item project-edit__env-list-item">
|
||||
{% for environment in application.environments %}
|
||||
<li class="block-list__item application-edit__env-list-item">
|
||||
<div class="usa-input">
|
||||
<label>Environment Name</label>
|
||||
<input type="text" value="{{ environment.name }}" readonly />
|
55
templates/workspaces/applications/index.html
Normal file
55
templates/workspaces/applications/index.html
Normal file
@ -0,0 +1,55 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/empty_state.html" import EmptyState %}
|
||||
|
||||
{% extends "workspaces/base.html" %}
|
||||
|
||||
|
||||
{% block workspace_content %}
|
||||
|
||||
{% if not workspace.applications %}
|
||||
|
||||
{% set can_create_applications = user_can(permissions.ADD_APPLICATION_IN_WORKSPACE) %}
|
||||
|
||||
{{ EmptyState(
|
||||
'This portfolio doesn’t have any applications yet.',
|
||||
action_label='Add a New Application' if can_create_applications else None,
|
||||
action_href=url_for('workspaces.new_application', workspace_id=workspace.id) if can_create_applications else None,
|
||||
icon='cloud',
|
||||
sub_message=None if can_create_applications else 'Please contact your JEDI Cloud portfolio administrator to set up a new application.'
|
||||
) }}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% for application in workspace.applications %}
|
||||
<div v-cloak class='block-list application-list-item'>
|
||||
<header class='block-list__header'>
|
||||
<h2 class='block-list__title'>{{ application.name }} ({{ application.environments|length }} environments)</h2>
|
||||
{% if user_can(permissions.RENAME_APPLICATION_IN_WORKSPACE) %}
|
||||
<a class='icon-link' href='{{ url_for("workspaces.edit_application", workspace_id=workspace.id, application_id=application.id) }}'>
|
||||
{{ Icon('edit') }}
|
||||
<span>edit</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</header>
|
||||
<ul>
|
||||
{% for environment in application.environments %}
|
||||
<li class='block-list__item application-list-item__environment'>
|
||||
<a href='{{ url_for("workspaces.access_environment", workspace_id=workspace.id, environment_id=environment.id)}}' target='_blank' rel='noopener noreferrer' class='application-list-item__environment__link'>
|
||||
{{ Icon('link') }}
|
||||
<span>{{ environment.name }}</span>
|
||||
</a>
|
||||
|
||||
<div class='application-list-item__environment__members'>
|
||||
<div class='label'>{{ environment.num_users }}</div>
|
||||
<span>members</span>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
@ -7,11 +7,11 @@
|
||||
|
||||
{% block workspace_content %}
|
||||
|
||||
{% set modalName = "newProjectConfirmation" %}
|
||||
{% set modalName = "newApplicationConfirmation" %}
|
||||
{% include "fragments/flash.html" %}
|
||||
|
||||
<new-project inline-template v-bind:initial-data='{{ form.data|tojson }}' modal-name='{{ modalName }}'>
|
||||
<form method="POST" action="{{ url_for('workspaces.create_project', workspace_id=workspace.id) }}" v-on:submit="handleSubmit">
|
||||
<new-application inline-template v-bind:initial-data='{{ form.data|tojson }}' modal-name='{{ modalName }}'>
|
||||
<form method="POST" action="{{ url_for('workspaces.create_application', workspace_id=workspace.id) }}" v-on:submit="handleSubmit">
|
||||
|
||||
{% call Modal(name=modalName, dismissable=False) %}
|
||||
<h1>Create application !{ name }</h1>
|
||||
@ -21,7 +21,7 @@
|
||||
<span v-for="(environment, index) in environments">
|
||||
<strong>!{environment.name}</strong><template v-if="index < (environments.length - 1)">, </template>
|
||||
</span>
|
||||
will be created as individual cloud resource groups under <strong>!{ name }</strong> project.
|
||||
will be created as individual cloud resource groups under <strong>!{ name }</strong> application.
|
||||
</p>
|
||||
|
||||
<div class='action-group'>
|
||||
@ -30,7 +30,7 @@
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{% include "fragments/edit_project_form.html" %}
|
||||
{% include "fragments/edit_application_form.html" %}
|
||||
|
||||
<div> {# this extra div prevents this bug: https://www.pivotaltracker.com/story/show/160768940 #}
|
||||
<div v-cloak v-for="title in errors" :key="title">
|
||||
@ -38,7 +38,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-list project-list-item">
|
||||
<div class="block-list application-list-item">
|
||||
<header class="block-list__header block-list__header--grow">
|
||||
<h2 class="block-list__title">Application Environments</h2>
|
||||
<p>
|
||||
@ -47,13 +47,13 @@
|
||||
</header>
|
||||
|
||||
<ul>
|
||||
<li v-for="(environment, i) in environments" class="block-list__item project-edit__env-list-item">
|
||||
<li v-for="(environment, i) in environments" class="block-list__item application-edit__env-list-item">
|
||||
<div class="usa-input">
|
||||
<label :for="'environment_names-' + i">Environment Name</label>
|
||||
<input type="text" :id="'environment_names-' + i" v-model="environment.name" placeholder="e.g. Development, Staging, Production"/>
|
||||
<input type="hidden" :name="'environment_names-' + i" v-model="environment.name"/>
|
||||
</div>
|
||||
<button v-on:click="removeEnvironment(i)" v-if="environments.length > 1" type="button" class='project-edit__env-list-item__remover'>
|
||||
<button v-on:click="removeEnvironment(i)" v-if="environments.length > 1" type="button" class='application-edit__env-list-item__remover'>
|
||||
{{ Icon('trash') }}
|
||||
<span>Remove</span>
|
||||
</button>
|
||||
@ -69,6 +69,6 @@
|
||||
<button class="usa-button usa-button-primary" tabindex="0" type="submit">Create Application</button>
|
||||
</div>
|
||||
</form>
|
||||
</new-project>
|
||||
</new-application>
|
||||
|
||||
{% endblock %}
|
@ -24,7 +24,7 @@
|
||||
|
||||
<div class='action-group'>
|
||||
<button type="submit" class="usa-button usa-button-big usa-button-primary" tabindex="0">Save</button>
|
||||
<a href='{{ url_for("workspaces.workspace_projects", workspace_id=workspace.id) }}' class='action-group__action icon-link'>
|
||||
<a href='{{ url_for("workspaces.workspace_applications", workspace_id=workspace.id) }}' class='action-group__action icon-link'>
|
||||
{{ Icon('x') }}
|
||||
<span>Cancel</span>
|
||||
</a>
|
||||
|
@ -14,7 +14,7 @@
|
||||
{% for workspace in workspaces %}
|
||||
<tr>
|
||||
<td>
|
||||
<a class='icon-link icon-link--large' href="/workspaces/{{ workspace.id }}/projects">{{ workspace.name }}</a><br>
|
||||
<a class='icon-link icon-link--large' href="/workspaces/{{ workspace.id }}/applications">{{ workspace.name }}</a><br>
|
||||
</td>
|
||||
<td>
|
||||
#{{ workspace.legacy_task_order.number }}
|
||||
|
@ -69,24 +69,24 @@
|
||||
|
||||
<div class='search-bar'>
|
||||
<div class='usa-input search-input'>
|
||||
<label for='project-search'>Search by application name</label>
|
||||
<input type='search' id='project-search' name='project-search' placeholder="Search by application name"/>
|
||||
<label for='application-search'>Search by application name</label>
|
||||
<input type='search' id='application-search' name='application-search' placeholder="Search by application name"/>
|
||||
<button type="submit">
|
||||
<span class="hide">Search</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for project in projects %}
|
||||
{% set revoke_modal_name = (project.id|string) + 'RevokeModal' %}
|
||||
<edit-project-roles inline-template name="{{ project.name }}" id="{{ project.id }}">
|
||||
<div is='toggler' default-visible class='block-list project-list-item'>
|
||||
{% for application in applications %}
|
||||
{% set revoke_modal_name = (application.id|string) + 'RevokeModal' %}
|
||||
<edit-application-roles inline-template name="{{ application.name }}" id="{{ application.id }}">
|
||||
<div is='toggler' default-visible class='block-list application-list-item'>
|
||||
<template slot-scope='props'>
|
||||
<header class='block-list__header'>
|
||||
<button v-on:click='props.toggle' class='icon-link icon-link--large icon-link--default spend-table__project__toggler'>
|
||||
<button v-on:click='props.toggle' class='icon-link icon-link--large icon-link--default spend-table__application__toggler'>
|
||||
<template v-if='props.isVisible'>{{ Icon('caret_down') }}</template>
|
||||
<template v-else>{{ Icon('caret_right') }}</template>
|
||||
<h3 class="block-list__title">{{ project.name }}</h3>
|
||||
<h3 class="block-list__title">{{ application.name }}</h3>
|
||||
</button>
|
||||
<span><a v-on:click="openModal('{{ revoke_modal_name }}')" class="icon-link icon-link--danger">revoke all access</a></span>
|
||||
</header>
|
||||
@ -94,7 +94,7 @@
|
||||
<div>
|
||||
<h1>Revoke Access</h1>
|
||||
<p>
|
||||
Confirming will revoke access for {{ member.user.full_name }} to any environments associated with {{ project.name }}.
|
||||
Confirming will revoke access for {{ member.user.full_name }} to any environments associated with {{ application.name }}.
|
||||
</p>
|
||||
<div class='action-group'>
|
||||
<a v-on:click="doRevoke(); closeModal('{{ revoke_modal_name }}')" class='action-group__action usa-button'>Confirm</a>
|
||||
@ -103,19 +103,19 @@
|
||||
</div>
|
||||
{% endcall %}
|
||||
<ul v-show='props.isVisible'>
|
||||
{% for env in project.environments %}
|
||||
{% for env in application.environments %}
|
||||
|
||||
{% set role = EnvironmentRoles.get(member.user_id, env.id).role %}
|
||||
{% set env_modal_name = (env.id|string) + 'RolesModal' %}
|
||||
|
||||
<li class='block-list__item'>
|
||||
<edit-environment-role inline-template initial-data='{{ role or "" }}' v-bind:choices='{{ choices | tojson }}' v-bind:project-id="'{{ project.id }}'">
|
||||
<div class='project-list-item__environment'>
|
||||
<span class='project-list-item__environment__link'>
|
||||
<edit-environment-role inline-template initial-data='{{ role or "" }}' v-bind:choices='{{ choices | tojson }}' v-bind:application-id="'{{ application.id }}'">
|
||||
<div class='application-list-item__environment'>
|
||||
<span class='application-list-item__environment__link'>
|
||||
{{ env.name }}
|
||||
</span>
|
||||
|
||||
<div class='project-list-item__environment__actions'>
|
||||
<div class='application-list-item__environment__actions'>
|
||||
<span v-bind:class="label_class" v-html:on=displayName></span>
|
||||
<button v-on:click="openModal('{{env_modal_name}}')" type="button" class="icon-link">set role</button>
|
||||
{% call Modal(name=env_modal_name, dismissable=False) %}
|
||||
@ -170,7 +170,7 @@
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
</edit-project-roles>
|
||||
</edit-application-roles>
|
||||
{% endfor %}
|
||||
|
||||
<div class='action-group'>
|
||||
|
@ -1,55 +0,0 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/empty_state.html" import EmptyState %}
|
||||
|
||||
{% extends "workspaces/base.html" %}
|
||||
|
||||
|
||||
{% block workspace_content %}
|
||||
|
||||
{% if not workspace.projects %}
|
||||
|
||||
{% set can_create_projects = user_can(permissions.ADD_APPLICATION_IN_WORKSPACE) %}
|
||||
|
||||
{{ EmptyState(
|
||||
'This portfolio doesn’t have any applications yet.',
|
||||
action_label='Add a New Application' if can_create_projects else None,
|
||||
action_href=url_for('workspaces.new_project', workspace_id=workspace.id) if can_create_projects else None,
|
||||
icon='cloud',
|
||||
sub_message=None if can_create_projects else 'Please contact your JEDI Cloud portfolio administrator to set up a new project.'
|
||||
) }}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% for project in workspace.projects %}
|
||||
<div v-cloak class='block-list project-list-item'>
|
||||
<header class='block-list__header'>
|
||||
<h2 class='block-list__title'>{{ project.name }} ({{ project.environments|length }} environments)</h2>
|
||||
{% if user_can(permissions.RENAME_APPLICATION_IN_WORKSPACE) %}
|
||||
<a class='icon-link' href='{{ url_for("workspaces.edit_project", workspace_id=workspace.id, project_id=project.id) }}'>
|
||||
{{ Icon('edit') }}
|
||||
<span>edit</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</header>
|
||||
<ul>
|
||||
{% for environment in project.environments %}
|
||||
<li class='block-list__item project-list-item__environment'>
|
||||
<a href='{{ url_for("workspaces.access_environment", workspace_id=workspace.id, environment_id=environment.id)}}' target='_blank' rel='noopener noreferrer' class='project-list-item__environment__link'>
|
||||
{{ Icon('link') }}
|
||||
<span>{{ environment.name }}</span>
|
||||
</a>
|
||||
|
||||
<div class='project-list-item__environment__members'>
|
||||
<div class='label'>{{ environment.num_users }}</div>
|
||||
<span>members</span>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
@ -113,18 +113,18 @@
|
||||
{% set two_months_ago_index = two_months_ago.strftime('%m/%Y') %}
|
||||
{% set reports_url = url_for("workspaces.workspace_reports", workspace_id=workspace.id) %}
|
||||
|
||||
{% if not workspace.projects %}
|
||||
{% if not workspace.applications %}
|
||||
|
||||
{% set can_create_projects = user_can(permissions.ADD_APPLICATION_IN_WORKSPACE) %}
|
||||
{% set can_create_applications = user_can(permissions.ADD_APPLICATION_IN_WORKSPACE) %}
|
||||
{% set message = 'This portfolio has no cloud environments set up, so there is no spending data to report. Create an application with some cloud environments to get started.'
|
||||
if can_create_projects
|
||||
if can_create_applications
|
||||
else 'This portfolio has no cloud environments set up, so there is no spending data to report. Contact the portfolio owner to set up some cloud environments.'
|
||||
%}
|
||||
|
||||
{{ EmptyState(
|
||||
'Nothing to report',
|
||||
action_label='Add a New Application' if can_create_projects else None,
|
||||
action_href=url_for('workspaces.new_project', workspace_id=workspace.id) if can_create_projects else None,
|
||||
action_label='Add a New Application' if can_create_applications else None,
|
||||
action_href=url_for('workspaces.new_application', workspace_id=workspace.id) if can_create_applications else None,
|
||||
icon='chart',
|
||||
sub_message=message
|
||||
) }}
|
||||
@ -353,7 +353,7 @@
|
||||
</div>
|
||||
|
||||
<spend-table
|
||||
v-bind:projects='{{ monthly_totals['projects'] | tojson }}'
|
||||
v-bind:applications='{{ monthly_totals['applications'] | tojson }}'
|
||||
v-bind:workspace='{{ workspace_totals | tojson }}'
|
||||
v-bind:environments='{{ monthly_totals['environments'] | tojson }}'
|
||||
current-month-index='{{ current_month_index }}'
|
||||
@ -383,40 +383,40 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody v-for='(project, name) in projectsState' class='spend-table__project'>
|
||||
<tbody v-for='(application, name) in applicationsState' class='spend-table__application'>
|
||||
<tr>
|
||||
<th scope='rowgroup'>
|
||||
<button v-on:click='toggle($event, name)' class='icon-link icon-link--large spend-table__project__toggler'>
|
||||
<template v-if='project.isVisible'>{{ Icon('caret_down') }}</template>
|
||||
<button v-on:click='toggle($event, name)' class='icon-link icon-link--large spend-table__application__toggler'>
|
||||
<template v-if='application.isVisible'>{{ Icon('caret_down') }}</template>
|
||||
<template v-else>{{ Icon('caret_right') }}</template>
|
||||
<span v-html='name'></span>
|
||||
</button>
|
||||
</th>
|
||||
<td class='table-cell--align-right previous-month'>
|
||||
<span v-html='formatDollars(project[twoMonthsAgoIndex] || 0)'></span>
|
||||
<span v-html='formatDollars(application[twoMonthsAgoIndex] || 0)'></span>
|
||||
</td>
|
||||
|
||||
<td class='table-cell--align-right previous-month'>
|
||||
<span v-html='formatDollars(project[prevMonthIndex] || 0)'></span>
|
||||
<span v-html='formatDollars(application[prevMonthIndex] || 0)'></span>
|
||||
</td>
|
||||
|
||||
<td class='table-cell--align-right current-month'>
|
||||
<span v-html='formatDollars(project[currentMonthIndex] || 0)'></span>
|
||||
<span v-html='formatDollars(application[currentMonthIndex] || 0)'></span>
|
||||
</td>
|
||||
|
||||
<td class='table-cell--expand current-month meter-cell'>
|
||||
<span class='spend-table__meter-value'>
|
||||
<span v-html='round( 100 * ((project[currentMonthIndex] || 0) / (workspace[currentMonthIndex] || 1) )) + "%"'></span>
|
||||
<span v-html='round( 100 * ((application[currentMonthIndex] || 0) / (workspace[currentMonthIndex] || 1) )) + "%"'></span>
|
||||
</span>
|
||||
<meter v-bind:value='project[currentMonthIndex] || 0' min='0' v-bind:max='workspace[currentMonthIndex] || 1'>
|
||||
<div class='meter__fallback' v-bind:style='"width:" + round( 100 * ((project[currentMonthIndex] || 0) / (workspace[currentMonthIndex] || 1) )) + "%;"'></div>
|
||||
<meter v-bind:value='application[currentMonthIndex] || 0' min='0' v-bind:max='workspace[currentMonthIndex] || 1'>
|
||||
<div class='meter__fallback' v-bind:style='"width:" + round( 100 * ((application[currentMonthIndex] || 0) / (workspace[currentMonthIndex] || 1) )) + "%;"'></div>
|
||||
</meter>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr v-for='(environment, envName) in environments[name]' v-show='project.isVisible' class='spend-table__project__env'>
|
||||
<tr v-for='(environment, envName) in environments[name]' v-show='application.isVisible' class='spend-table__application__env'>
|
||||
<th scope='rowgroup'>
|
||||
<a href='#' class='icon-link spend-table__project__env'>
|
||||
<a href='#' class='icon-link spend-table__application__env'>
|
||||
{{ Icon('link') }}
|
||||
<span v-html='envName'></span>
|
||||
</a>
|
||||
|
58
tests/domain/test_applications.py
Normal file
58
tests/domain/test_applications.py
Normal file
@ -0,0 +1,58 @@
|
||||
from atst.domain.applications import Applications
|
||||
from tests.factories import RequestFactory, UserFactory, WorkspaceFactory
|
||||
from atst.domain.workspaces import Workspaces
|
||||
|
||||
|
||||
def test_create_application_with_multiple_environments():
|
||||
request = RequestFactory.create()
|
||||
workspace = Workspaces.create_from_request(request)
|
||||
application = Applications.create(
|
||||
workspace.owner, workspace, "My Test Application", "Test", ["dev", "prod"]
|
||||
)
|
||||
|
||||
assert application.workspace == workspace
|
||||
assert application.name == "My Test Application"
|
||||
assert application.description == "Test"
|
||||
assert sorted(e.name for e in application.environments) == ["dev", "prod"]
|
||||
|
||||
|
||||
def test_workspace_owner_can_view_environments():
|
||||
owner = UserFactory.create()
|
||||
workspace = WorkspaceFactory.create(
|
||||
owner=owner,
|
||||
applications=[{"environments": [{"name": "dev"}, {"name": "prod"}]}],
|
||||
)
|
||||
application = Applications.get(owner, workspace, workspace.applications[0].id)
|
||||
|
||||
assert len(application.environments) == 2
|
||||
|
||||
|
||||
def test_can_only_update_name_and_description():
|
||||
owner = UserFactory.create()
|
||||
workspace = WorkspaceFactory.create(
|
||||
owner=owner,
|
||||
applications=[
|
||||
{
|
||||
"name": "Application 1",
|
||||
"description": "a application",
|
||||
"environments": [{"name": "dev"}],
|
||||
}
|
||||
],
|
||||
)
|
||||
application = Applications.get(owner, workspace, workspace.applications[0].id)
|
||||
env_name = application.environments[0].name
|
||||
Applications.update(
|
||||
owner,
|
||||
workspace,
|
||||
application,
|
||||
{
|
||||
"name": "New Name",
|
||||
"description": "a new application",
|
||||
"environment_name": "prod",
|
||||
},
|
||||
)
|
||||
|
||||
assert application.name == "New Name"
|
||||
assert application.description == "a new application"
|
||||
assert len(application.environments) == 1
|
||||
assert application.environments[0].name == env_name
|
@ -8,7 +8,7 @@ from tests.factories import (
|
||||
UserFactory,
|
||||
WorkspaceFactory,
|
||||
WorkspaceRoleFactory,
|
||||
ProjectFactory,
|
||||
ApplicationFactory,
|
||||
)
|
||||
|
||||
|
||||
@ -81,10 +81,10 @@ def test_other_users_cannot_view_ws_audit_log():
|
||||
|
||||
def test_paginate_ws_audit_log():
|
||||
workspace = WorkspaceFactory.create()
|
||||
project = ProjectFactory.create(workspace=workspace)
|
||||
application = ApplicationFactory.create(workspace=workspace)
|
||||
for _ in range(100):
|
||||
AuditLog.log_system_event(
|
||||
resource=project, action="create", workspace=workspace
|
||||
resource=application, action="create", workspace=workspace
|
||||
)
|
||||
|
||||
events = AuditLog.get_workspace_events(
|
||||
@ -98,8 +98,8 @@ def test_ws_audit_log_only_includes_current_ws_events():
|
||||
workspace = WorkspaceFactory.create(owner=owner)
|
||||
other_workspace = WorkspaceFactory.create(owner=owner)
|
||||
# Add some audit events
|
||||
project_1 = ProjectFactory.create(workspace=workspace)
|
||||
project_2 = ProjectFactory.create(workspace=other_workspace)
|
||||
application_1 = ApplicationFactory.create(workspace=workspace)
|
||||
application_2 = ApplicationFactory.create(workspace=other_workspace)
|
||||
|
||||
events = AuditLog.get_workspace_events(workspace.owner, workspace)
|
||||
for event in events:
|
||||
|
@ -2,12 +2,12 @@ from atst.domain.environments import Environments
|
||||
from atst.domain.environment_roles import EnvironmentRoles
|
||||
from atst.domain.workspace_roles import WorkspaceRoles
|
||||
|
||||
from tests.factories import ProjectFactory, UserFactory, WorkspaceFactory
|
||||
from tests.factories import ApplicationFactory, UserFactory, WorkspaceFactory
|
||||
|
||||
|
||||
def test_create_environments():
|
||||
project = ProjectFactory.create()
|
||||
environments = Environments.create_many(project, ["Staging", "Production"])
|
||||
application = ApplicationFactory.create()
|
||||
environments = Environments.create_many(application, ["Staging", "Production"])
|
||||
for env in environments:
|
||||
assert env.cloud_id is not None
|
||||
|
||||
@ -19,10 +19,12 @@ def test_create_environment_role_creates_cloud_id(session):
|
||||
workspace = WorkspaceFactory.create(
|
||||
owner=owner,
|
||||
members=[{"user": developer, "role_name": "developer"}],
|
||||
projects=[{"name": "project1", "environments": [{"name": "project1 prod"}]}],
|
||||
applications=[
|
||||
{"name": "application1", "environments": [{"name": "application1 prod"}]}
|
||||
],
|
||||
)
|
||||
|
||||
env = workspace.projects[0].environments[0]
|
||||
env = workspace.applications[0].environments[0]
|
||||
new_role = [{"id": env.id, "role": "developer"}]
|
||||
|
||||
workspace_role = workspace.members[0]
|
||||
@ -41,26 +43,26 @@ def test_update_environment_roles():
|
||||
workspace = WorkspaceFactory.create(
|
||||
owner=owner,
|
||||
members=[{"user": developer, "role_name": "developer"}],
|
||||
projects=[
|
||||
applications=[
|
||||
{
|
||||
"name": "project1",
|
||||
"name": "application1",
|
||||
"environments": [
|
||||
{
|
||||
"name": "project1 dev",
|
||||
"name": "application1 dev",
|
||||
"members": [{"user": developer, "role_name": "devlops"}],
|
||||
},
|
||||
{
|
||||
"name": "project1 staging",
|
||||
"name": "application1 staging",
|
||||
"members": [{"user": developer, "role_name": "developer"}],
|
||||
},
|
||||
{"name": "project1 prod"},
|
||||
{"name": "application1 prod"},
|
||||
],
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
dev_env = workspace.projects[0].environments[0]
|
||||
staging_env = workspace.projects[0].environments[1]
|
||||
dev_env = workspace.applications[0].environments[0]
|
||||
staging_env = workspace.applications[0].environments[1]
|
||||
new_ids_and_roles = [
|
||||
{"id": dev_env.id, "role": "billing_admin"},
|
||||
{"id": staging_env.id, "role": "developer"},
|
||||
@ -83,34 +85,34 @@ def test_remove_environment_role():
|
||||
workspace = WorkspaceFactory.create(
|
||||
owner=owner,
|
||||
members=[{"user": developer, "role_name": "developer"}],
|
||||
projects=[
|
||||
applications=[
|
||||
{
|
||||
"name": "project1",
|
||||
"name": "application1",
|
||||
"environments": [
|
||||
{
|
||||
"name": "project1 dev",
|
||||
"name": "application1 dev",
|
||||
"members": [{"user": developer, "role_name": "devops"}],
|
||||
},
|
||||
{
|
||||
"name": "project1 staging",
|
||||
"name": "application1 staging",
|
||||
"members": [{"user": developer, "role_name": "developer"}],
|
||||
},
|
||||
{
|
||||
"name": "project1 uat",
|
||||
"name": "application1 uat",
|
||||
"members": [
|
||||
{"user": developer, "role_name": "financial_auditor"}
|
||||
],
|
||||
},
|
||||
{"name": "project1 prod"},
|
||||
{"name": "application1 prod"},
|
||||
],
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
project = workspace.projects[0]
|
||||
now_ba = project.environments[0].id
|
||||
now_none = project.environments[1].id
|
||||
still_fa = project.environments[2].id
|
||||
application = workspace.applications[0]
|
||||
now_ba = application.environments[0].id
|
||||
now_none = application.environments[1].id
|
||||
still_fa = application.environments[2].id
|
||||
|
||||
new_environment_roles = [
|
||||
{"id": now_ba, "role": "billing_auditor"},
|
||||
@ -135,12 +137,12 @@ def test_no_update_to_environment_roles():
|
||||
workspace = WorkspaceFactory.create(
|
||||
owner=owner,
|
||||
members=[{"user": developer, "role_name": "developer"}],
|
||||
projects=[
|
||||
applications=[
|
||||
{
|
||||
"name": "project1",
|
||||
"name": "application1",
|
||||
"environments": [
|
||||
{
|
||||
"name": "project1 dev",
|
||||
"name": "application1 dev",
|
||||
"members": [{"user": developer, "role_name": "devops"}],
|
||||
}
|
||||
],
|
||||
@ -148,7 +150,7 @@ def test_no_update_to_environment_roles():
|
||||
],
|
||||
)
|
||||
|
||||
dev_env = workspace.projects[0].environments[0]
|
||||
dev_env = workspace.applications[0].environments[0]
|
||||
new_ids_and_roles = [{"id": dev_env.id, "role": "devops"}]
|
||||
|
||||
workspace_role = WorkspaceRoles.get(workspace.id, developer.id)
|
||||
@ -161,34 +163,34 @@ def test_get_scoped_environments(db):
|
||||
developer = UserFactory.create()
|
||||
workspace = WorkspaceFactory.create(
|
||||
members=[{"user": developer, "role_name": "developer"}],
|
||||
projects=[
|
||||
applications=[
|
||||
{
|
||||
"name": "project1",
|
||||
"name": "application1",
|
||||
"environments": [
|
||||
{
|
||||
"name": "project1 dev",
|
||||
"name": "application1 dev",
|
||||
"members": [{"user": developer, "role_name": "developer"}],
|
||||
},
|
||||
{"name": "project1 staging"},
|
||||
{"name": "project1 prod"},
|
||||
{"name": "application1 staging"},
|
||||
{"name": "application1 prod"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "project2",
|
||||
"name": "application2",
|
||||
"environments": [
|
||||
{"name": "project2 dev"},
|
||||
{"name": "application2 dev"},
|
||||
{
|
||||
"name": "project2 staging",
|
||||
"name": "application2 staging",
|
||||
"members": [{"user": developer, "role_name": "developer"}],
|
||||
},
|
||||
{"name": "project2 prod"},
|
||||
{"name": "application2 prod"},
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
project1_envs = Environments.for_user(developer, workspace.projects[0])
|
||||
assert [env.name for env in project1_envs] == ["project1 dev"]
|
||||
application1_envs = Environments.for_user(developer, workspace.applications[0])
|
||||
assert [env.name for env in application1_envs] == ["application1 dev"]
|
||||
|
||||
project2_envs = Environments.for_user(developer, workspace.projects[1])
|
||||
assert [env.name for env in project2_envs] == ["project2 staging"]
|
||||
application2_envs = Environments.for_user(developer, workspace.applications[1])
|
||||
assert [env.name for env in application2_envs] == ["application2 staging"]
|
||||
|
@ -1,57 +0,0 @@
|
||||
from atst.domain.projects import Projects
|
||||
from tests.factories import RequestFactory, UserFactory, WorkspaceFactory
|
||||
from atst.domain.workspaces import Workspaces
|
||||
|
||||
|
||||
def test_create_project_with_multiple_environments():
|
||||
request = RequestFactory.create()
|
||||
workspace = Workspaces.create_from_request(request)
|
||||
project = Projects.create(
|
||||
workspace.owner, workspace, "My Test Project", "Test", ["dev", "prod"]
|
||||
)
|
||||
|
||||
assert project.workspace == workspace
|
||||
assert project.name == "My Test Project"
|
||||
assert project.description == "Test"
|
||||
assert sorted(e.name for e in project.environments) == ["dev", "prod"]
|
||||
|
||||
|
||||
def test_workspace_owner_can_view_environments():
|
||||
owner = UserFactory.create()
|
||||
workspace = WorkspaceFactory.create(
|
||||
owner=owner, projects=[{"environments": [{"name": "dev"}, {"name": "prod"}]}]
|
||||
)
|
||||
project = Projects.get(owner, workspace, workspace.projects[0].id)
|
||||
|
||||
assert len(project.environments) == 2
|
||||
|
||||
|
||||
def test_can_only_update_name_and_description():
|
||||
owner = UserFactory.create()
|
||||
workspace = WorkspaceFactory.create(
|
||||
owner=owner,
|
||||
projects=[
|
||||
{
|
||||
"name": "Project 1",
|
||||
"description": "a project",
|
||||
"environments": [{"name": "dev"}],
|
||||
}
|
||||
],
|
||||
)
|
||||
project = Projects.get(owner, workspace, workspace.projects[0].id)
|
||||
env_name = project.environments[0].name
|
||||
Projects.update(
|
||||
owner,
|
||||
workspace,
|
||||
project,
|
||||
{
|
||||
"name": "New Name",
|
||||
"description": "a new project",
|
||||
"environment_name": "prod",
|
||||
},
|
||||
)
|
||||
|
||||
assert project.name == "New Name"
|
||||
assert project.description == "a new project"
|
||||
assert len(project.environments) == 1
|
||||
assert project.environments[0].name == env_name
|
@ -25,7 +25,7 @@ def test_monthly_totals():
|
||||
monthly = Reports.monthly_totals(workspace)
|
||||
|
||||
assert not monthly["environments"]
|
||||
assert not monthly["projects"]
|
||||
assert not monthly["applications"]
|
||||
assert not monthly["workspace"]
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@ from uuid import uuid4
|
||||
from atst.domain.exceptions import NotFoundError, UnauthorizedError
|
||||
from atst.domain.workspaces import Workspaces, WorkspaceError
|
||||
from atst.domain.workspace_roles import WorkspaceRoles
|
||||
from atst.domain.projects import Projects
|
||||
from atst.domain.applications import Applications
|
||||
from atst.domain.environments import Environments
|
||||
from atst.models.workspace_role import Status as WorkspaceRoleStatus
|
||||
|
||||
@ -69,16 +69,16 @@ def test_workspaces_get_ensures_user_is_in_workspace(workspace, workspace_owner)
|
||||
Workspaces.get(outside_user, workspace.id)
|
||||
|
||||
|
||||
def test_get_for_update_projects_allows_owner(workspace, workspace_owner):
|
||||
Workspaces.get_for_update_projects(workspace_owner, workspace.id)
|
||||
def test_get_for_update_applications_allows_owner(workspace, workspace_owner):
|
||||
Workspaces.get_for_update_applications(workspace_owner, workspace.id)
|
||||
|
||||
|
||||
def test_get_for_update_projects_blocks_developer(workspace):
|
||||
def test_get_for_update_applications_blocks_developer(workspace):
|
||||
developer = UserFactory.create()
|
||||
WorkspaceRoles.add(developer, workspace.id, "developer")
|
||||
|
||||
with pytest.raises(UnauthorizedError):
|
||||
Workspaces.get_for_update_projects(developer, workspace.id)
|
||||
Workspaces.get_for_update_applications(developer, workspace.id)
|
||||
|
||||
|
||||
def test_can_create_workspace_role(workspace, workspace_owner):
|
||||
@ -183,45 +183,45 @@ def test_random_user_cannot_view_workspace_members(workspace):
|
||||
workspace = Workspaces.get_with_members(developer, workspace.id)
|
||||
|
||||
|
||||
def test_scoped_workspace_only_returns_a_users_projects_and_environments(
|
||||
def test_scoped_workspace_only_returns_a_users_applications_and_environments(
|
||||
workspace, workspace_owner
|
||||
):
|
||||
new_project = Projects.create(
|
||||
new_application = Applications.create(
|
||||
workspace_owner,
|
||||
workspace,
|
||||
"My Project",
|
||||
"My project",
|
||||
"My Application",
|
||||
"My application",
|
||||
["dev", "staging", "prod"],
|
||||
)
|
||||
Projects.create(
|
||||
Applications.create(
|
||||
workspace_owner,
|
||||
workspace,
|
||||
"My Project 2",
|
||||
"My project 2",
|
||||
"My Application 2",
|
||||
"My application 2",
|
||||
["dev", "staging", "prod"],
|
||||
)
|
||||
developer = UserFactory.from_atat_role("developer")
|
||||
dev_environment = Environments.add_member(
|
||||
new_project.environments[0], developer, "developer"
|
||||
new_application.environments[0], developer, "developer"
|
||||
)
|
||||
|
||||
scoped_workspace = Workspaces.get(developer, workspace.id)
|
||||
|
||||
# Should only return the project and environment in which the user has an
|
||||
# Should only return the application and environment in which the user has an
|
||||
# environment role.
|
||||
assert scoped_workspace.projects == [new_project]
|
||||
assert scoped_workspace.projects[0].environments == [dev_environment]
|
||||
assert scoped_workspace.applications == [new_application]
|
||||
assert scoped_workspace.applications[0].environments == [dev_environment]
|
||||
|
||||
|
||||
def test_scoped_workspace_returns_all_projects_for_workspace_admin(
|
||||
def test_scoped_workspace_returns_all_applications_for_workspace_admin(
|
||||
workspace, workspace_owner
|
||||
):
|
||||
for _ in range(5):
|
||||
Projects.create(
|
||||
Applications.create(
|
||||
workspace_owner,
|
||||
workspace,
|
||||
"My Project",
|
||||
"My project",
|
||||
"My Application",
|
||||
"My application",
|
||||
["dev", "staging", "prod"],
|
||||
)
|
||||
|
||||
@ -231,26 +231,26 @@ def test_scoped_workspace_returns_all_projects_for_workspace_admin(
|
||||
)
|
||||
scoped_workspace = Workspaces.get(admin, workspace.id)
|
||||
|
||||
assert len(scoped_workspace.projects) == 5
|
||||
assert len(scoped_workspace.projects[0].environments) == 3
|
||||
assert len(scoped_workspace.applications) == 5
|
||||
assert len(scoped_workspace.applications[0].environments) == 3
|
||||
|
||||
|
||||
def test_scoped_workspace_returns_all_projects_for_workspace_owner(
|
||||
def test_scoped_workspace_returns_all_applications_for_workspace_owner(
|
||||
workspace, workspace_owner
|
||||
):
|
||||
for _ in range(5):
|
||||
Projects.create(
|
||||
Applications.create(
|
||||
workspace_owner,
|
||||
workspace,
|
||||
"My Project",
|
||||
"My project",
|
||||
"My Application",
|
||||
"My application",
|
||||
["dev", "staging", "prod"],
|
||||
)
|
||||
|
||||
scoped_workspace = Workspaces.get(workspace_owner, workspace.id)
|
||||
|
||||
assert len(scoped_workspace.projects) == 5
|
||||
assert len(scoped_workspace.projects[0].environments) == 3
|
||||
assert len(scoped_workspace.applications) == 5
|
||||
assert len(scoped_workspace.applications[0].environments) == 3
|
||||
|
||||
|
||||
def test_for_user_returns_active_workspaces_for_user(workspace, workspace_owner):
|
||||
|
@ -12,7 +12,7 @@ from atst.models.request_revision import RequestRevision
|
||||
from atst.models.request_review import RequestReview
|
||||
from atst.models.request_status_event import RequestStatusEvent, RequestStatus
|
||||
from atst.models.pe_number import PENumber
|
||||
from atst.models.project import Project
|
||||
from atst.models.application import Application
|
||||
from atst.models.legacy_task_order import LegacyTaskOrder, Source, FundingType
|
||||
from atst.models.task_order import TaskOrder
|
||||
from atst.models.user import User
|
||||
@ -260,14 +260,15 @@ class WorkspaceFactory(Base):
|
||||
|
||||
@classmethod
|
||||
def _create(cls, model_class, *args, **kwargs):
|
||||
with_projects = kwargs.pop("projects", [])
|
||||
with_applications = kwargs.pop("applications", [])
|
||||
owner = kwargs.pop("owner", UserFactory.create())
|
||||
members = kwargs.pop("members", [])
|
||||
|
||||
workspace = super()._create(model_class, *args, **kwargs)
|
||||
|
||||
projects = [
|
||||
ProjectFactory.create(workspace=workspace, **p) for p in with_projects
|
||||
applications = [
|
||||
ApplicationFactory.create(workspace=workspace, **p)
|
||||
for p in with_applications
|
||||
]
|
||||
|
||||
workspace.request.creator = owner
|
||||
@ -288,29 +289,30 @@ class WorkspaceFactory(Base):
|
||||
status=WorkspaceRoleStatus.ACTIVE,
|
||||
)
|
||||
|
||||
workspace.projects = projects
|
||||
workspace.applications = applications
|
||||
return workspace
|
||||
|
||||
|
||||
class ProjectFactory(Base):
|
||||
class ApplicationFactory(Base):
|
||||
class Meta:
|
||||
model = Project
|
||||
model = Application
|
||||
|
||||
workspace = factory.SubFactory(WorkspaceFactory)
|
||||
name = factory.Faker("name")
|
||||
description = "A test project"
|
||||
description = "A test application"
|
||||
|
||||
@classmethod
|
||||
def _create(cls, model_class, *args, **kwargs):
|
||||
with_environments = kwargs.pop("environments", [])
|
||||
project = super()._create(model_class, *args, **kwargs)
|
||||
application = super()._create(model_class, *args, **kwargs)
|
||||
|
||||
environments = [
|
||||
EnvironmentFactory.create(project=project, **e) for e in with_environments
|
||||
EnvironmentFactory.create(application=application, **e)
|
||||
for e in with_environments
|
||||
]
|
||||
|
||||
project.environments = environments
|
||||
return project
|
||||
application.environments = environments
|
||||
return application
|
||||
|
||||
|
||||
class EnvironmentFactory(Base):
|
||||
|
@ -72,7 +72,7 @@ class TestDetailsOfUseForm:
|
||||
request_form = self._make_form(data)
|
||||
assert request_form.validate()
|
||||
|
||||
def test_sessions_required_for_large_projects(self):
|
||||
def test_sessions_required_for_large_applications(self):
|
||||
data = {**self.form_data, **self.migration_data}
|
||||
data["estimated_monthly_spend"] = "9999999"
|
||||
del data["number_user_sessions"]
|
||||
|
@ -1,6 +1,6 @@
|
||||
from atst.domain.environments import Environments
|
||||
from atst.domain.workspaces import Workspaces
|
||||
from atst.domain.projects import Projects
|
||||
from atst.domain.applications import Applications
|
||||
from tests.factories import RequestFactory, UserFactory
|
||||
|
||||
|
||||
@ -9,10 +9,14 @@ def test_add_user_to_environment():
|
||||
developer = UserFactory.from_atat_role("developer")
|
||||
|
||||
workspace = Workspaces.create_from_request(RequestFactory.create(creator=owner))
|
||||
project = Projects.create(
|
||||
owner, workspace, "my test project", "It's mine.", ["dev", "staging", "prod"]
|
||||
application = Applications.create(
|
||||
owner,
|
||||
workspace,
|
||||
"my test application",
|
||||
"It's mine.",
|
||||
["dev", "staging", "prod"],
|
||||
)
|
||||
dev_environment = project.environments[0]
|
||||
dev_environment = application.environments[0]
|
||||
|
||||
dev_environment = Environments.add_member(dev_environment, developer, "developer")
|
||||
assert developer in dev_environment.users
|
||||
|
@ -2,7 +2,7 @@ import datetime
|
||||
|
||||
from atst.domain.environments import Environments
|
||||
from atst.domain.workspaces import Workspaces
|
||||
from atst.domain.projects import Projects
|
||||
from atst.domain.applications import Applications
|
||||
from atst.models.workspace_role import Status
|
||||
from atst.models.role import Role
|
||||
from atst.models.invitation import Status as InvitationStatus
|
||||
@ -15,7 +15,7 @@ from tests.factories import (
|
||||
WorkspaceRoleFactory,
|
||||
EnvironmentFactory,
|
||||
EnvironmentRoleFactory,
|
||||
ProjectFactory,
|
||||
ApplicationFactory,
|
||||
WorkspaceFactory,
|
||||
)
|
||||
from atst.domain.workspace_roles import WorkspaceRoles
|
||||
@ -90,8 +90,10 @@ def test_has_no_env_role_history(session):
|
||||
owner = UserFactory.create()
|
||||
user = UserFactory.create()
|
||||
workspace = Workspaces.create_from_request(RequestFactory.create(creator=owner))
|
||||
project = ProjectFactory.create(workspace=workspace)
|
||||
environment = EnvironmentFactory.create(project=project, name="new environment!")
|
||||
application = ApplicationFactory.create(workspace=workspace)
|
||||
environment = EnvironmentFactory.create(
|
||||
application=application, name="new environment!"
|
||||
)
|
||||
|
||||
env_role = EnvironmentRoleFactory.create(
|
||||
user=user, environment=environment, role="developer"
|
||||
@ -110,8 +112,10 @@ def test_has_env_role_history(session):
|
||||
user = UserFactory.create()
|
||||
workspace = Workspaces.create_from_request(RequestFactory.create(creator=owner))
|
||||
workspace_role = WorkspaceRoleFactory.create(workspace=workspace, user=user)
|
||||
project = ProjectFactory.create(workspace=workspace)
|
||||
environment = EnvironmentFactory.create(project=project, name="new environment!")
|
||||
application = ApplicationFactory.create(workspace=workspace)
|
||||
environment = EnvironmentFactory.create(
|
||||
application=application, name="new environment!"
|
||||
)
|
||||
|
||||
env_role = EnvironmentRoleFactory.create(
|
||||
user=user, environment=environment, role="developer"
|
||||
@ -168,10 +172,16 @@ def test_has_environment_roles():
|
||||
|
||||
workspace = Workspaces.create_from_request(RequestFactory.create(creator=owner))
|
||||
workspace_role = Workspaces.create_member(owner, workspace, developer_data)
|
||||
project = Projects.create(
|
||||
owner, workspace, "my test project", "It's mine.", ["dev", "staging", "prod"]
|
||||
application = Applications.create(
|
||||
owner,
|
||||
workspace,
|
||||
"my test application",
|
||||
"It's mine.",
|
||||
["dev", "staging", "prod"],
|
||||
)
|
||||
Environments.add_member(
|
||||
application.environments[0], workspace_role.user, "developer"
|
||||
)
|
||||
Environments.add_member(project.environments[0], workspace_role.user, "developer")
|
||||
assert workspace_role.has_environment_roles
|
||||
|
||||
|
||||
@ -260,9 +270,9 @@ def test_can_resend_invitation_if_expired():
|
||||
|
||||
def test_can_list_all_environments():
|
||||
workspace = WorkspaceFactory.create(
|
||||
projects=[
|
||||
applications=[
|
||||
{
|
||||
"name": "project1",
|
||||
"name": "application1",
|
||||
"environments": [
|
||||
{"name": "dev"},
|
||||
{"name": "staging"},
|
||||
@ -270,7 +280,7 @@ def test_can_list_all_environments():
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "project2",
|
||||
"name": "application2",
|
||||
"environments": [
|
||||
{"name": "dev"},
|
||||
{"name": "staging"},
|
||||
@ -278,7 +288,7 @@ def test_can_list_all_environments():
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "project3",
|
||||
"name": "application3",
|
||||
"environments": [
|
||||
{"name": "dev"},
|
||||
{"name": "staging"},
|
||||
|
@ -61,7 +61,7 @@ def test_non_owner_user_with_no_workspaces_redirected_to_requests(client, user_s
|
||||
assert "/requests" in response.location
|
||||
|
||||
|
||||
def test_non_owner_user_with_one_workspace_redirected_to_workspace_projects(
|
||||
def test_non_owner_user_with_one_workspace_redirected_to_workspace_applications(
|
||||
client, user_session
|
||||
):
|
||||
user = UserFactory.create()
|
||||
@ -71,7 +71,7 @@ def test_non_owner_user_with_one_workspace_redirected_to_workspace_projects(
|
||||
user_session(user)
|
||||
response = client.get("/home", follow_redirects=False)
|
||||
|
||||
assert "/workspaces/{}/projects".format(workspace.id) in response.location
|
||||
assert "/workspaces/{}/applications".format(workspace.id) in response.location
|
||||
|
||||
|
||||
def test_non_owner_user_with_mulitple_workspaces_redirected_to_workspaces(
|
||||
|
@ -6,10 +6,10 @@ from tests.factories import (
|
||||
WorkspaceRoleFactory,
|
||||
EnvironmentRoleFactory,
|
||||
EnvironmentFactory,
|
||||
ProjectFactory,
|
||||
ApplicationFactory,
|
||||
)
|
||||
|
||||
from atst.domain.projects import Projects
|
||||
from atst.domain.applications import Applications
|
||||
from atst.domain.workspaces import Workspaces
|
||||
from atst.domain.roles import Roles
|
||||
from atst.models.workspace_role import Status as WorkspaceRoleStatus
|
||||
@ -18,7 +18,7 @@ from atst.models.workspace_role import Status as WorkspaceRoleStatus
|
||||
def test_user_with_permission_has_budget_report_link(client, user_session):
|
||||
workspace = WorkspaceFactory.create()
|
||||
user_session(workspace.owner)
|
||||
response = client.get("/workspaces/{}/projects".format(workspace.id))
|
||||
response = client.get("/workspaces/{}/applications".format(workspace.id))
|
||||
assert (
|
||||
'href="/workspaces/{}/reports"'.format(workspace.id).encode() in response.data
|
||||
)
|
||||
@ -31,7 +31,7 @@ def test_user_without_permission_has_no_budget_report_link(client, user_session)
|
||||
user, workspace, "developer", status=WorkspaceRoleStatus.ACTIVE
|
||||
)
|
||||
user_session(user)
|
||||
response = client.get("/workspaces/{}/projects".format(workspace.id))
|
||||
response = client.get("/workspaces/{}/applications".format(workspace.id))
|
||||
assert (
|
||||
'href="/workspaces/{}/reports"'.format(workspace.id).encode()
|
||||
not in response.data
|
||||
@ -50,20 +50,20 @@ def test_user_with_permission_has_activity_log_link(client, user_session):
|
||||
)
|
||||
|
||||
user_session(workspace.owner)
|
||||
response = client.get("/workspaces/{}/projects".format(workspace.id))
|
||||
response = client.get("/workspaces/{}/applications".format(workspace.id))
|
||||
assert (
|
||||
'href="/workspaces/{}/activity"'.format(workspace.id).encode() in response.data
|
||||
)
|
||||
|
||||
# logs out previous user before creating a new session
|
||||
user_session(admin)
|
||||
response = client.get("/workspaces/{}/projects".format(workspace.id))
|
||||
response = client.get("/workspaces/{}/applications".format(workspace.id))
|
||||
assert (
|
||||
'href="/workspaces/{}/activity"'.format(workspace.id).encode() in response.data
|
||||
)
|
||||
|
||||
user_session(ccpo)
|
||||
response = client.get("/workspaces/{}/projects".format(workspace.id))
|
||||
response = client.get("/workspaces/{}/applications".format(workspace.id))
|
||||
assert (
|
||||
'href="/workspaces/{}/activity"'.format(workspace.id).encode() in response.data
|
||||
)
|
||||
@ -80,116 +80,119 @@ def test_user_without_permission_has_no_activity_log_link(client, user_session):
|
||||
)
|
||||
|
||||
user_session(developer)
|
||||
response = client.get("/workspaces/{}/projects".format(workspace.id))
|
||||
response = client.get("/workspaces/{}/applications".format(workspace.id))
|
||||
assert (
|
||||
'href="/workspaces/{}/activity"'.format(workspace.id).encode()
|
||||
not in response.data
|
||||
)
|
||||
|
||||
|
||||
def test_user_with_permission_has_add_project_link(client, user_session):
|
||||
def test_user_with_permission_has_add_application_link(client, user_session):
|
||||
workspace = WorkspaceFactory.create()
|
||||
user_session(workspace.owner)
|
||||
response = client.get("/workspaces/{}/projects".format(workspace.id))
|
||||
response = client.get("/workspaces/{}/applications".format(workspace.id))
|
||||
assert (
|
||||
'href="/workspaces/{}/projects/new"'.format(workspace.id).encode()
|
||||
'href="/workspaces/{}/applications/new"'.format(workspace.id).encode()
|
||||
in response.data
|
||||
)
|
||||
|
||||
|
||||
def test_user_without_permission_has_no_add_project_link(client, user_session):
|
||||
def test_user_without_permission_has_no_add_application_link(client, user_session):
|
||||
user = UserFactory.create()
|
||||
workspace = WorkspaceFactory.create()
|
||||
Workspaces._create_workspace_role(user, workspace, "developer")
|
||||
user_session(user)
|
||||
response = client.get("/workspaces/{}/projects".format(workspace.id))
|
||||
response = client.get("/workspaces/{}/applications".format(workspace.id))
|
||||
assert (
|
||||
'href="/workspaces/{}/projects/new"'.format(workspace.id).encode()
|
||||
'href="/workspaces/{}/applications/new"'.format(workspace.id).encode()
|
||||
not in response.data
|
||||
)
|
||||
|
||||
|
||||
def test_view_edit_project(client, user_session):
|
||||
def test_view_edit_application(client, user_session):
|
||||
workspace = WorkspaceFactory.create()
|
||||
project = Projects.create(
|
||||
application = Applications.create(
|
||||
workspace.owner,
|
||||
workspace,
|
||||
"Snazzy Project",
|
||||
"A new project for me and my friends",
|
||||
"Snazzy Application",
|
||||
"A new application for me and my friends",
|
||||
{"env1", "env2"},
|
||||
)
|
||||
user_session(workspace.owner)
|
||||
response = client.get(
|
||||
"/workspaces/{}/projects/{}/edit".format(workspace.id, project.id)
|
||||
"/workspaces/{}/applications/{}/edit".format(workspace.id, application.id)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_user_with_permission_can_update_project(client, user_session):
|
||||
def test_user_with_permission_can_update_application(client, user_session):
|
||||
owner = UserFactory.create()
|
||||
workspace = WorkspaceFactory.create(
|
||||
owner=owner,
|
||||
projects=[
|
||||
applications=[
|
||||
{
|
||||
"name": "Awesome Project",
|
||||
"name": "Awesome Application",
|
||||
"description": "It's really awesome!",
|
||||
"environments": [{"name": "dev"}, {"name": "prod"}],
|
||||
}
|
||||
],
|
||||
)
|
||||
project = workspace.projects[0]
|
||||
application = workspace.applications[0]
|
||||
user_session(owner)
|
||||
response = client.post(
|
||||
url_for(
|
||||
"workspaces.update_project",
|
||||
"workspaces.update_application",
|
||||
workspace_id=workspace.id,
|
||||
project_id=project.id,
|
||||
application_id=application.id,
|
||||
),
|
||||
data={"name": "Really Cool Project", "description": "A very cool project."},
|
||||
data={
|
||||
"name": "Really Cool Application",
|
||||
"description": "A very cool application.",
|
||||
},
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert project.name == "Really Cool Project"
|
||||
assert project.description == "A very cool project."
|
||||
assert application.name == "Really Cool Application"
|
||||
assert application.description == "A very cool application."
|
||||
|
||||
|
||||
def test_user_without_permission_cannot_update_project(client, user_session):
|
||||
def test_user_without_permission_cannot_update_application(client, user_session):
|
||||
dev = UserFactory.create()
|
||||
owner = UserFactory.create()
|
||||
workspace = WorkspaceFactory.create(
|
||||
owner=owner,
|
||||
members=[{"user": dev, "role_name": "developer"}],
|
||||
projects=[
|
||||
applications=[
|
||||
{
|
||||
"name": "Great Project",
|
||||
"name": "Great Application",
|
||||
"description": "Cool stuff happening here!",
|
||||
"environments": [{"name": "dev"}, {"name": "prod"}],
|
||||
}
|
||||
],
|
||||
)
|
||||
project = workspace.projects[0]
|
||||
application = workspace.applications[0]
|
||||
user_session(dev)
|
||||
response = client.post(
|
||||
url_for(
|
||||
"workspaces.update_project",
|
||||
"workspaces.update_application",
|
||||
workspace_id=workspace.id,
|
||||
project_id=project.id,
|
||||
application_id=application.id,
|
||||
),
|
||||
data={"name": "New Name", "description": "A new description."},
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
assert project.name == "Great Project"
|
||||
assert project.description == "Cool stuff happening here!"
|
||||
assert application.name == "Great Application"
|
||||
assert application.description == "Cool stuff happening here!"
|
||||
|
||||
|
||||
def create_environment(user):
|
||||
workspace = WorkspaceFactory.create()
|
||||
workspace_role = WorkspaceRoleFactory.create(workspace=workspace, user=user)
|
||||
project = ProjectFactory.create(workspace=workspace)
|
||||
return EnvironmentFactory.create(project=project, name="new environment!")
|
||||
application = ApplicationFactory.create(workspace=workspace)
|
||||
return EnvironmentFactory.create(application=application, name="new environment!")
|
||||
|
||||
|
||||
def test_environment_access_with_env_role(client, user_session):
|
@ -99,7 +99,7 @@ def test_user_who_has_not_accepted_workspace_invite_cannot_view(client, user_ses
|
||||
|
||||
# user tries to view workspace before accepting invitation
|
||||
user_session(user)
|
||||
response = client.get("/workspaces/{}/projects".format(workspace.id))
|
||||
response = client.get("/workspaces/{}/applications".format(workspace.id))
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@ from tests.factories import (
|
||||
)
|
||||
from atst.domain.workspaces import Workspaces
|
||||
from atst.domain.workspace_roles import WorkspaceRoles
|
||||
from atst.domain.projects import Projects
|
||||
from atst.domain.applications import Applications
|
||||
from atst.domain.environments import Environments
|
||||
from atst.domain.environment_roles import EnvironmentRoles
|
||||
from atst.queue import queue
|
||||
@ -144,16 +144,16 @@ def test_update_member_environment_role(client, user_session):
|
||||
workspace = WorkspaceFactory.create()
|
||||
user = UserFactory.create()
|
||||
member = WorkspaceRoles.add(user, workspace.id, "developer")
|
||||
project = Projects.create(
|
||||
application = Applications.create(
|
||||
workspace.owner,
|
||||
workspace,
|
||||
"Snazzy Project",
|
||||
"A new project for me and my friends",
|
||||
"Snazzy Application",
|
||||
"A new application for me and my friends",
|
||||
{"env1", "env2"},
|
||||
)
|
||||
env1_id = project.environments[0].id
|
||||
env2_id = project.environments[1].id
|
||||
for env in project.environments:
|
||||
env1_id = application.environments[0].id
|
||||
env2_id = application.environments[1].id
|
||||
for env in application.environments:
|
||||
Environments.add_member(env, user, "developer")
|
||||
user_session(workspace.owner)
|
||||
response = client.post(
|
||||
@ -178,15 +178,15 @@ def test_update_member_environment_role_with_no_data(client, user_session):
|
||||
workspace = WorkspaceFactory.create()
|
||||
user = UserFactory.create()
|
||||
member = WorkspaceRoles.add(user, workspace.id, "developer")
|
||||
project = Projects.create(
|
||||
application = Applications.create(
|
||||
workspace.owner,
|
||||
workspace,
|
||||
"Snazzy Project",
|
||||
"A new project for me and my friends",
|
||||
"Snazzy Application",
|
||||
"A new application for me and my friends",
|
||||
{"env1"},
|
||||
)
|
||||
env1_id = project.environments[0].id
|
||||
for env in project.environments:
|
||||
env1_id = application.environments[0].id
|
||||
for env in application.environments:
|
||||
Environments.add_member(env, user, "developer")
|
||||
user_session(workspace.owner)
|
||||
response = client.post(
|
||||
@ -207,11 +207,11 @@ def test_revoke_active_member_access(client, user_session):
|
||||
member = WorkspaceRoleFactory.create(
|
||||
workspace=workspace, user=user, status=WorkspaceRoleStatus.ACTIVE
|
||||
)
|
||||
Projects.create(
|
||||
Applications.create(
|
||||
workspace.owner,
|
||||
workspace,
|
||||
"Snazzy Project",
|
||||
"A new project for me and my friends",
|
||||
"Snazzy Application",
|
||||
"A new application for me and my friends",
|
||||
{"env1"},
|
||||
)
|
||||
user_session(workspace.owner)
|
||||
|
@ -75,7 +75,7 @@ forms:
|
||||
exceptions:
|
||||
message: Form validation failed.
|
||||
financial:
|
||||
ba_code_description: 'BA Code is used to identify the purposes, projects, or types of activities financed by the appropriation fund. <br/><em>It should be two digits, followed by an optional letter.</em>'
|
||||
ba_code_description: 'BA Code is used to identify the purposes, applications, or types of activities financed by the appropriation fund. <br/><em>It should be two digits, followed by an optional letter.</em>'
|
||||
ba_code_label: Program Budget Activity (BA) Code
|
||||
clin_0001_description: 'Review your task order document, the amounts for each CLIN must match exactly here'
|
||||
clin_0001_label: <dl><dt>CLIN 0001</dt> - <dd>Unclassified IaaS and PaaS Amount</dd></dl>
|
||||
@ -119,7 +119,7 @@ forms:
|
||||
email_label: Email Address
|
||||
first_name_label: First Name
|
||||
last_name_label: Last Name
|
||||
workspace_role_description: 'The portfolio role controls whether a member is permitted to organize a portfolio into projects and environments, add members to this portfolio, and view billing information.'
|
||||
workspace_role_description: 'The portfolio role controls whether a member is permitted to organize a portfolio into applications and environments, add members to this portfolio, and view billing information.'
|
||||
workspace_role_label: Portfolio Role
|
||||
new_request:
|
||||
am_poc_label: I am the Portfolio Owner
|
||||
@ -157,12 +157,12 @@ forms:
|
||||
start_date_date_range_validation_message: Must be a date in the future.
|
||||
start_date_label: When do you expect to start using the JEDI Cloud (not for billing purposes)?
|
||||
technical_support_team_description: Are you working with a technical support team experienced in cloud migrations?
|
||||
project:
|
||||
application:
|
||||
description_label: Description
|
||||
environment_names_label: Environment Name
|
||||
environment_names_required_validation_message: Provide at least one environment name.
|
||||
environment_names_unique_validation_message: Environment names must be unique.
|
||||
name_label: Project Name
|
||||
name_label: Application Name
|
||||
task_order:
|
||||
portfolio_name_label: Organization Portfolio Name
|
||||
portfolio_name_description: The name of your office or organization. You can add multiple applications to your portfolio. Your task orders are used to pay for these applications and their environments.
|
||||
@ -212,10 +212,10 @@ forms:
|
||||
name_label: Portfolio Name
|
||||
name_length_validation_message: Portfolio names must be at least 4 and not more than 50 characters
|
||||
fragments:
|
||||
edit_project_form:
|
||||
existing_project_title: 'Edit {project_name} project'
|
||||
explain: 'AT-AT allows you to organize your portfolio into multiple projects, each of which may have environments.'
|
||||
new_project_title: Add a new project
|
||||
edit_application_form:
|
||||
existing_application_title: 'Edit {application_name} application'
|
||||
explain: 'AT-AT allows you to organize your portfolio into multiple applications, each of which may have environments.'
|
||||
new_application_title: Add a new application
|
||||
edit_user_form:
|
||||
date_last_training_tooltip: When was the last time you completed the IA training? <br> Information Assurance (IA) training is an important step in cyber awareness.
|
||||
save_details_button: Save Details
|
||||
@ -225,7 +225,7 @@ fragments:
|
||||
paragraph_2: 'While your request is being reviewed, your next step is to create a Task Order (TO) associated with the JEDI Cloud. Please contact a Contracting Officer (KO), Contracting Officer Representative (COR), or a Financial Manager to help with this step.'
|
||||
pending_ccpo_approval_modal:
|
||||
paragraph_1: The CCPO will review and respond to your Financial Verification submission in 3 business days. You will be notified via email or phone.
|
||||
paragraph_2: Once the financial verification is approved you will be invited to create your JEDI Portfolio and set-up your projects. Click here for more details.
|
||||
paragraph_2: Once the financial verification is approved you will be invited to create your JEDI Portfolio and set-up your applications. Click here for more details.
|
||||
pending_financial_verification:
|
||||
learn_more_link_text: Learn more about the JEDI Cloud Task Order and the Financial Verification process.
|
||||
paragraph_1: 'The next step is to create a Task Order associated with JEDI Cloud. Please contact a Contracting Officer (KO), Contracting Officer Representative (COR), or a Financial Manager to help with this step.'
|
||||
@ -249,11 +249,11 @@ navigation:
|
||||
request_workspace_link_text: Request a new JEDI Portfolio
|
||||
workspace_navigation:
|
||||
add_new_member_label: Add New Member
|
||||
add_new_project_label: Add New Project
|
||||
add_new_application_label: Add New Application
|
||||
budget_report: Budget Report
|
||||
activity_log: Activity Log
|
||||
members: Members
|
||||
projects: Projects
|
||||
applications: Applications
|
||||
task_orders: Task Orders
|
||||
workspace_settings: Portfolio Settings
|
||||
requests:
|
||||
@ -309,7 +309,7 @@ requests:
|
||||
no_requests_found: No requests found.
|
||||
no_workspaces_action_label: Create a new JEDI Cloud Request
|
||||
no_workspaces_label: You currently have no JEDI Cloud portfolios.
|
||||
no_workspaces_sub_message: A JEDI Cloud Portfolio is where you manage your projects and control user access to those projects.
|
||||
no_workspaces_sub_message: A JEDI Cloud Portfolio is where you manage your applications and control user access to those applications.
|
||||
pending_ccpo_action: Pending CCPO Action
|
||||
request_submitted_title: Request submitted!
|
||||
requests_in_progress: Requests in progress
|
||||
|
Loading…
x
Reference in New Issue
Block a user