Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • ssa/workspaces
1 result
Show changes
Commits on Source (2)
Showing
with 247 additions and 156 deletions
$container-shadow-color: #909090;
$timestamp-text-color: #757575;
$request-status-page-border-light: #d4d4d4;
$request-status-page-border-dark: #bdbdbd;
......@@ -20,7 +22,6 @@ $running-status-bg-gradient: linear-gradient(
$running-status-bg-light 100%
);
// QUEUED status color
$running-status-bg-light: #5fb2f4;
$running-status-bg-dark: #2196f3;
......@@ -29,3 +30,7 @@ $running-status-bg-gradient: linear-gradient(
$running-status-bg-dark 0%,
$running-status-bg-light 100%
);
// Capability request parameters
$parameters-container-bg: #f7f7f7;
$parameters-pill-color: #d9d8d8;
<header>
<app-request-header></app-request-header>
</header>
<div id="definition-parameters-and-versions">
<div id="capability-definition-container">
<span id="capability-label">Capability</span>
<app-capability-definition id="capability-definition" class="w-100"></app-capability-definition>
<div id="definition-parameters-and-versions" class="container-fluid">
<div class="row">
<div id="capability-definition-container" class="col">
<span id="capability-label">Capability</span>
<app-capability-definition id="capability-definition"></app-capability-definition>
</div>
<div id="parameters-container" class="col">
<span id="parameters-label">Parameters</span>
<app-parameters id="parameters"></app-parameters>
</div>
</div>
<h1 id="parameters">PARAMETERS!!!!!!!!!!!!</h1>
</div>
@import "src/variables.scss";
div {
width: 100%;
#definition-parameters-and-versions {
padding: 0.5rem 3rem;
display: inline-grid;
}
#definition-parameters-and-versions {
display: grid;
grid-template-columns: repeat(2, 1fr);
background: $capability-request-bg-gradient;
}
#capability-definition {
justify-self: left;
}
#parameters {
justify-self: right;
}
......@@ -4,13 +4,13 @@
<div id="step-sequence">
<div class="container-fluid">
<div class="row bg-primary rounded rounded-lg mb-1 text-white py-2 align-items-center">
<div class="col">Step 1</div>
<div class="col">RUN WORKFLOW null</div>
<div class="col-auto">
<span class="fas fa-play-circle fa-2x"></span>
</div>
</div>
<div class="row bg-secondary rounded rounded-lg mb-1 text-white py-2 align-items-center">
<div class="col">Step 2</div>
<div class="col">AWAIT WORKFLOW</div>
<div class="col-auto"><span class="fa fa-circle fa-2x"></span></div>
</div>
</div>
......
@import "src/variables";
#definition-container {
background-color: #ffffff;
background-color: white;
border-radius: 10px;
border-top: 4px #909090 solid;
border-top: 4px $container-shadow-color solid;
padding: 0.25rem 0.5rem;
}
<div id="parameters-container" class="container-fluid rounded-top rounded-3 p-3">
<div class="row">
<div class="col">
<span class="rounded-pill px-2 py-1 argument-key">arguments</span>
<span class="px-1"> = </span>
<span class="rounded-pill px-2 py-1 argument-value">null</span>
</div>
</div>
</div>
@import "src/variables";
.argument-key,
.argument-value {
background-color: $parameters-pill-color;
}
#parameters-container {
background-color: $parameters-container-bg;
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ParametersComponent } from './parameters.component';
describe('ParametersComponent', () => {
let component: ParametersComponent;
let fixture: ComponentFixture<ParametersComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ParametersComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ParametersComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from "@angular/core";
@Component({
selector: "app-parameters",
templateUrl: "./parameters.component.html",
styleUrls: ["./parameters.component.scss"],
})
export class ParametersComponent implements OnInit {
constructor() {}
ngOnInit(): void {}
}
......@@ -7,6 +7,7 @@ import { CapabilityRequestComponent } from "./components/capability-request/capa
import { RequestHeaderComponent } from "./components/capability-request/components/request-header/request-header.component";
import { RunningStatusButtonComponent } from "./components/capability-request/components/status-buttons/running-status-button/running-status-button.component";
import { CapabilityDefinitionComponent } from "./components/capability-request/components/capability-definition/capability-definition.component";
import { ParametersComponent } from "./components/capability-request/components/parameters/parameters.component";
@NgModule({
declarations: [
......@@ -15,6 +16,7 @@ import { CapabilityDefinitionComponent } from "./components/capability-request/c
RequestHeaderComponent,
RunningStatusButtonComponent,
CapabilityDefinitionComponent,
ParametersComponent,
],
imports: [CommonModule, WorkspacesRoutingModule, ReactiveFormsModule, FormsModule],
})
......
......@@ -98,42 +98,14 @@ def send_notification(request: Request) -> Response:
"""
json_packet = request.json_body
print(json_packet)
smtp_server = "smtp.aoc.nrao.edu"
sender_email = "do-not-reply@nrao.edu" # Enter your address
receiver_email = json_packet["destination_email"] # Enter receiver address
template_name = request.matchdict["template_name"]
try:
template = request.notification_info.lookup_template(template_name)
except Exception as e:
return Response(
"An error occurred looking up template: "
+ template_name
+ "\nerror: "
+ str(e)
)
if template is None:
return Response("No template '" + template_name + "' found.")
args = {"template": template.content, "data": json_packet}
try:
contents = chevron.render(**args)
except Exception as e:
return Response(
"An error occurred rendering template: "
+ str(template)
+ "\nerror: "
+ str(e)
)
print(json_packet)
with smtplib.SMTP(smtp_server) as server:
r = server.sendmail(sender_email, receiver_email, contents)
r = request.notification_service.send_email(template_name, json_packet)
if r == {}:
return Response("Email sent.\n" + contents)
return Response("Email sent.\n")
else:
return Response("Unable to send to address(es): " + r)
import pytest
from mock_alchemy.mocking import UnifiedAlchemyMagicMock
from pyramid.config import Configurator
from pyramid.testing import DummyRequest, setUp, tearDown
from unittest.mock import MagicMock, patch
from unittest import mock
from workspaces.notification.schema import NotificationTemplate
from workspaces.notification.services.notification_info import NotificationInfo
pytest_plugins = ["testing.utils.conftest"]
@pytest.fixture(scope="module")
def test_config() -> Configurator:
"""
Returns a dummy Configurator object for testing
:return: Dummy Configurator
"""
request = DummyRequest()
config = setUp(request=request)
config.add_request_method(
lambda lookup: MagicMock(),
"notification_info",
reify=True
)
yield config
tearDown()
@pytest.fixture("module")
def dummy_send(mock_notification_info, mock_notification_service) -> DummyRequest:
"""
returns a dummy request with a mocked notification info
:return: DummyRequest with mock NotificationInfo
"""
request = DummyRequest(
notification_info=mock_notification_info,
notification_service=mock_notification_service
)
return request
import pytest
from typing import List
from pyramid.config import Configurator
from pyramid.interfaces import IRoutesMapper
class TestNotificationServer:
RouteList = List[str]
@pytest.fixture()
def notification_routes(self) -> RouteList:
return [
"home",
"view_notifications",
"create_notification",
"delete_notification",
"send_notification"
]
def test_routes_exist(self, test_config: Configurator, notification_routes: RouteList):
"""
Test that the pyramid config has the proper notification routes in place
:param test_config: Mock pyramid config for testing
:param notification_routes: List of expected routes
"""
test_config.include("notification.routes")
mapper = test_config.registry.queryUtility(IRoutesMapper)
route_names = [route.name for route in mapper.get_routes()]
assert notification_routes == route_names
import requests
import pytest
from pycapo import CapoConfig
def settings():
return CapoConfig().settings("edu.nrao.archive.workspaces.NotificationSettings").serviceUrl
@pytest.mark.skip("Because pipeline weird")
def test_create_template():
expected_result = "Template new_template created."
url = settings() + '/notify/create'
headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8', 'subject': 'testing'}
json_packet = {"name": "new_template", "description": "a test template", "template": "Subject: {{foo}}\n\n{{bar}}"}
r = requests.post(url, headers=headers, json=json_packet)
actual_result = r.text
assert actual_result == expected_result
@pytest.mark.skip("Because pipeline weird")
def test_get_templates():
expected_result = "Available notification templates:\nemail\nsubmitted_email\ncomplete_email\nnew_template\n"
url = settings() + '/notify'
headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'}
r = requests.get(url, headers=headers)
print(r)
actual_result = r.text
assert actual_result == expected_result
@pytest.mark.skip(
reason="Test succeeds, but we don't want to spam ourselves whenever the tests run... "
"See about getting a dummy account?"
)
def test_send_email():
"""check that calling the send email function returns without error"""
expected_result = """Email sent.\nSubject: first thing\n\nsecond thing"""
url = settings() + '/notify/email/send'
headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8', 'subject': 'testing'}
json_packet = {"destination_email": "your-email@nrao.edu", "subject": "first thing", "message": "second thing"}
r = requests.post(url, headers=headers, json=json_packet)
print(r)
actual_result = r.text
assert actual_result == expected_result
@pytest.mark.skip("Because pipeline weird")
def test_bad_template_name():
"""check that calling with a non-existant template name gives an error message"""
expected_result = """No template 'nonsense' found."""
url = settings() + '/notify/nonsense/send'
headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8', 'subject': 'testing'}
json_packet = {"destination_email": "your-email@nrao.edu", "stuff": "first thing", "more": "second thing"}
r = requests.post(url, headers=headers, json=json_packet)
print(r)
actual_result = r.text
assert actual_result == expected_result
@pytest.mark.skip("Because pipeline weird")
def test_delete_template():
expected_result = "Template new_template deleted."
url = settings() + '/notify/new_template/delete'
headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8', 'subject': 'testing'}
r = requests.delete(url, headers=headers)
actual_result = r.text
assert actual_result == expected_result
"""
Tests for the functionality of the Notification REST API.
The logic can be found in `notification/views/notify/py`.
"""
from unittest.mock import patch
import pytest
from pyramid.testing import DummyRequest
import notification.views.notify
from workspaces.notification.services.notification_service import NotificationService
@pytest.mark.usefixtures("dummy_send", "mock_notification_service")
class TestNotifyViews:
def test_view_notifications(self, dummy_send: DummyRequest):
from notification.views.notify import list_notification_templates
expected_response = "Available notification templates:\nemail\n"
response = list_notification_templates(dummy_send)
assert response.status_code == 200
assert response.text == expected_response
def test_send_notification(self, dummy_send: DummyRequest, mock_notification_service: NotificationService):
from notification.views.notify import send_notification
dummy_send.json_body = {
"destination_email": "chausman@nrao.edu",
"subject": "first thing",
"message": "second thing"
}
dummy_send.matchdict["template_name"] = "email"
with patch("workspaces.notification.services.notification_service.NotificationService.send_email"):
response = send_notification(dummy_send)
assert response.status_code == 200
def test_create_notification(self, dummy_send: DummyRequest):
from notification.views.notify import create_notification_template
expected_response = "Template new_template created."
dummy_send.json_body = {"name": "new_template",
"description": "a test template",
"template": "Subject: {{foo}}\n\n{{bar}}"}
response = create_notification_template(dummy_send)
assert response.status_code == 200
assert response.text == expected_response
def test_delete_notification(self, dummy_send: DummyRequest):
from notification.views.notify import delete_notification_template
expected_response = "Template new_template deleted."
dummy_send.matchdict["template_name"] = "new_template"
response = delete_notification_template(dummy_send)
assert response.status_code == 200
assert response.text == expected_response
import logging
import chevron
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from typing import Dict
from workspaces.system.schema import AbstractFile
logger = logging.getLogger(__name__)
Base = declarative_base()
......@@ -19,17 +22,16 @@ class NotificationTemplate(Base):
def render(self, argument: Dict) -> AbstractFile:
"""
Render a given Mustache-formatted Workflow Template with the specified arguments.
Render a given Mustache-formatted Notifiation Template with the specified arguments.
:param argument: argument(s) needed for template render
:return: an AbstractFile rendered from mustache template
"""
# TODO: if template has more than one tag, figure out how pass argument parameter
# in as JSON and replace 'data' section accordingly
args = {
"template": self.content.decode("ascii"),
"data": {"arguments": "".join(map(str, argument))},
}
contents = chevron.render(**args)
return contents
contents = chevron.render(self.content, argument)
logger.info(
"rendering %s with argument %s to %s",
self.content,
argument,
contents,
)
return AbstractFile(self.name, contents)
import logging
import requests
import smtplib
from pycapo import CapoConfig
from workspaces.notification.services.interfaces import NotificationServiceIF, NotificationInfoIF
......@@ -54,3 +55,21 @@ class NotificationServiceRESTClient(NotificationServiceIF):
class NotificationService(NotificationServiceIF):
def __init__(self, info: NotificationInfoIF):
self.info = info
def send_email(self, template_name, parameters: dict):
smtp_server = "smtp.aoc.nrao.edu"
sender_email = "do-not-reply@nrao.edu"
receiver_email = parameters["destination_email"]
template = self.info.lookup_template(template_name)
if template is None:
logger.info("No template '" + template_name + "' found.")
# args = {"template": template.content, "data": parameters}
file = template.render(parameters)
with smtplib.SMTP(smtp_server) as server:
r = server.sendmail(sender_email, receiver_email, file.content)
return r
......@@ -159,13 +159,9 @@ def mock_notification_info() -> NotificationInfo:
description="A generic email template, requiring subject and message.",
content="Subject: {{subject}}\n\n{{message}}")
email_notification_query = (
[mock.call.query(NotificationTemplate), mock.call.filter_by(name="email")],
[email_notification]
)
mock_session = UnifiedAlchemyMagicMock(data=[email_notification_query])
mock_session = UnifiedAlchemyMagicMock()
mock_notification_info = NotificationInfo(mock_session)
mock_session.add(email_notification)
with patch("workspaces.notification.services.notification_info.NotificationInfo.save_entity"):
return mock_notification_info
......