Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

Commit

Permalink
Merge remote-tracking branch 'origin/development'
Browse files Browse the repository at this point in the history
  • Loading branch information
Justin Tervala authored and Justin Tervala committed Jan 18, 2018
2 parents 3cb1213 + 5c57bc2 commit 84f0509
Show file tree
Hide file tree
Showing 26 changed files with 688 additions and 615 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,24 @@
# Changelog
<!-- Use the tags Added, Changed, Deprecated, Removed, Fixed, Security, and
Contributor to describe changes -->
## [0.6.3]
###### 2018-01-18

### Added
* Added a simple action in the Utilities app named "request user approval"
which sends a message with some text to a user and has an accept/decline
component.

### Changed
* Refactoring of AppCache to use multiple objects. We had been storing it as
a large dict which was becoming difficult to reason about. This is the
first step of a larger planned refactoring of how apps are cached and
validated

### Fixed
* Bug on UI when arguments using an array type without item types specified
* Fixed issue with workflow migration caused to erroneously deleting a script


## [0.6.2]
###### 2018-01-05
Expand Down
5 changes: 3 additions & 2 deletions README.md
@@ -1,5 +1,6 @@
Linux [![Build Status](https://travis-ci.org/iadgov/WALKOFF.svg?branch=master)](https://travis-ci.org/iadgov/WALKOFF) Windows [![Build status](https://ci.appveyor.com/api/projects/status/wsuo57tb1u593hox/branch/development?svg=true)](https://ci.appveyor.com/project/JustinTervala/walkoff-u1gc1/branch/master)
[![Maintainability](https://api.codeclimate.com/v1/badges/330249e13845a07a69a2/maintainability)](https://codeclimate.com/github/iadgov/WALKOFF/maintainability)
[![Build Status](https://img.shields.io/travis/iadgov/WALKOFF/master.svg?maxAge=3600&label=Linux)](https://travis-ci.org/iadgov/WALKOFF) Windows [![Build status](https://ci.appveyor.com/api/projects/status/wsuo57tb1u593hox/branch/development?svg=true)](https://ci.appveyor.com/project/JustinTervala/walkoff-u1gc1/branch/master)
[![Maintainability](https://api.codeclimate.com/v1/badges/330249e13845a07a69a2/maintainability)](https://codeclimate.com/github/iadgov/WALKOFF/maintainability)[![GitHub (pre-)release](https://img.shields.io/github/release/iadgov/WALKOFF/all.svg?style=flat)](release)


<img src="https://iadgov.github.io/WALKOFF/files/images/flyingLogoWithTextSmall.png">

Expand Down
9 changes: 9 additions & 0 deletions apps/Utilities/actions.py
Expand Up @@ -98,6 +98,15 @@ def send_text_message(subject, message, users=None, roles=None):
text = Text(message)
message = Message(subject=subject, body=[text])
send_message(message, users=users, roles=roles)
return 'success'


@action
def basic_request_user_approval(users=None, roles=None):
text = Text('A workflow requires your authentication')
message = Message(subject='Workflow awaiting approval', body=[text, AcceptDecline()])
send_message(message, users=users, roles=roles)
return 'success'


@action
Expand Down
16 changes: 16 additions & 0 deletions apps/Utilities/api.yaml
Expand Up @@ -234,6 +234,22 @@ actions:
schema:
type: string
enum: ['success']
request user approval:
run: actions.basic_request_user_approval
parameters:
- name: users
type: array
items:
type: user
- name: roles
type: array
items:
type: role
returns:
Success:
schema:
type: string
enum: ['success']
create text message component:
run: actions.create_text_message_component
description: Creates a text component for a message
Expand Down
46 changes: 27 additions & 19 deletions interfaces/dispatchers.py
Expand Up @@ -308,12 +308,15 @@ def register_events(self, func, events, sender_uids=None, names=None, weak=True)
if not entry_ids:
raise ValueError('Either sender_uid or name must specified')
for entry_id in entry_ids:
if entry_id not in self._router:
self._router[entry_id] = {}
for event in events:
if event not in self._router[entry_id]:
self._router[entry_id][event] = CallbackContainer()
self._router[entry_id][event].register(func, weak=weak)
self.__register_entry(entry_id, events, func, weak)

def __register_entry(self, entry_id, events, func, weak):
if entry_id not in self._router:
self._router[entry_id] = {}
for event in events:
if event not in self._router[entry_id]:
self._router[entry_id][event] = CallbackContainer()
self._router[entry_id][event].register(func, weak=weak)

def dispatch(self, event_, data):
"""Dispatches an event to all its registered callbacks
Expand All @@ -325,21 +328,24 @@ def dispatch(self, event_, data):
event_ (WalkoffEvent): The event to dispatch
data (dict): The data to send to all the events
"""
sender_name, sender_uid = self.__get_sender_ids(data, event_)
callbacks = self._get_callbacks(sender_uid, sender_name, event_)
for func in callbacks:
try:
args = (data,) if event_.event_type != EventType.controller else tuple()
func(*args)
except Exception as e:
_logger.exception('Error calling interface event handler: {}'.format(e))

@staticmethod
def __get_sender_ids(data, event_):
if event_.event_type != EventType.controller:
sender_uid = data['sender_uid']
sender_name = data['sender_name'] if 'sender_name' in data else None
else:
sender_uid = EventType.controller.name
sender_name = None
callbacks = self._get_callbacks(sender_uid, sender_name, event_)
for func in callbacks:
try:
if event_.event_type != EventType.controller:
func(data)
else:
func()
except Exception as e:
_logger.exception('Error calling interface event handler: {}'.format(e))
return sender_name, sender_uid

def _get_callbacks(self, sender_uid, sender_name, event):
"""Gets all the callbacks associated with a given sender UID, name, and event
Expand All @@ -353,13 +359,15 @@ def _get_callbacks(self, sender_uid, sender_name, event):
set(func): The callbacks registered
"""
all_callbacks = set()
if sender_uid in self._router and event in self._router[sender_uid]:
all_callbacks |= set(self._router[sender_uid][event])
for sender_id in (sender_uid, sender_name):
if self.__is_event_registered_to_sender(sender_id, event):
all_callbacks |= set(self._router[sender_id][event])

if sender_name is not None and sender_name in self._router and event in self._router[sender_name]:
all_callbacks |= set(self._router[sender_name][event])
return all_callbacks

def __is_event_registered_to_sender(self, sender_id, event):
return sender_id is not None and sender_id in self._router and event in self._router[sender_id]

def is_registered(self, entry, event, func):
"""Is a function registered for a given entry ID and event?
Expand Down
130 changes: 78 additions & 52 deletions scripts/make_app.py
Expand Up @@ -34,36 +34,38 @@ def make_template(app_name):
os.makedirs(new_app_dir)
for (dirpath, dirnames, filenames) in walk(skeleton_app_dir):
if '__pycache__' not in dirpath:
for fn in filenames:
if dirpath == skeleton_app_dir:
write_fp = new_app_dir + '/' + fn
else:
sub_dir = dirpath.replace(skeleton_app_dir, new_app_dir)
if not os.path.exists(sub_dir):
os.makedirs(sub_dir)
write_fp = sub_dir + '/' + fn
with open(dirpath + '/' + fn) as read_from_file:
with open(write_fp, 'w') as write_to_file:
for line in read_from_file:
if 'SkeletonApp' in line:
line = line.replace('SkeletonApp', app_name)
elif 'Skeleton App' in line:
line = line.replace('Skeleton App', _separate_camelcase(app_name))
write_to_file.write(line)
for filename in filenames:
write_fp = get_write_filepath(dirpath, filename, new_app_dir, skeleton_app_dir)
write_app_to_file(app_name, dirpath, filename, write_fp)
print('Finished generating the template at', new_app_dir)
else:
print('Template already exist at', new_app_dir)


def generate_methods(input_fp):
actual_fp = ''
if input_fp.count('/') > 0:
if os.path.isfile(input_fp):
actual_fp = input_fp
def write_app_to_file(app_name, dirpath, filename, write_fp):
with open(dirpath + '/' + filename) as read_from_file:
with open(write_fp, 'w') as write_to_file:
for line in read_from_file:
if 'SkeletonApp' in line:
line = line.replace('SkeletonApp', app_name)
elif 'Skeleton App' in line:
line = line.replace('Skeleton App', _separate_camelcase(app_name))
write_to_file.write(line)


def get_write_filepath(dirpath, filename, new_app_dir, skeleton_app_dir):
if dirpath == skeleton_app_dir:
write_fp = new_app_dir + '/' + filename
else:
for (dirpath, dirnames, filenames) in walk(main_dir):
if input_fp in filenames:
actual_fp = dirpath + '/' + input_fp
sub_dir = dirpath.replace(skeleton_app_dir, new_app_dir)
if not os.path.exists(sub_dir):
os.makedirs(sub_dir)
write_fp = sub_dir + '/' + filename
return write_fp


def generate_methods(input_fp):
actual_fp = get_actual_filepath(input_fp)
if actual_fp == '':
print('Unable to find file. Please check that the yaml file exist at', input_fp)
return
Expand All @@ -89,34 +91,8 @@ def generate_methods(input_fp):
write_to_file.writelines(init_lines[:max(loc for loc, val in enumerate(init_lines) if val == '\n') + 1])
with open(main_py, 'a') as write_to_file:
actions = yaml_dict['actions']
for action in actions:
action_val = actions[action]
lines = []
if 'description' in action_val:
lines.append("'''")
lines.append(action_val['description'])
parameters_args = ''
if 'parameters' in action_val:
parameters0 = action_val['parameters'][0]
lines.append('Inputs:')
parameters_str = '{0} ({1}): {2}'.format(parameters0['name'], parameters0['type'],
parameters0['description'])
parameters_args = ', ' + parameters0['name']
required_val = parameters0['required']
if not required_val:
parameters_str += ' (Optional)'
parameters_args += '=None'
lines.append(parameters_str)
if 'returns' in action_val:
success_val = action_val['returns']['Success']
lines.append('Output:')
lines.append('{0}: {1}'.format(success_val['schema']['type'], success_val['description']))
lines.append("'''")
lines.append('@action')
lines.append('def {0}(self{1}):'.format(action.replace(' ', '_'), parameters_args))
lines.append('\tpass')
lines.append('\n')
write_to_file.writelines('\n'.join(['\t' + line for line in lines]))
for action, action_val in actions.items():
write_action(action, action_val, write_to_file)
cur_fp = new_app_dir + '/api.yaml'
if actual_fp != cur_fp:
with open(actual_fp) as yaml_file:
Expand All @@ -127,6 +103,56 @@ def generate_methods(input_fp):
print('Successfully generated methods for {0} at {1}'.format(_separate_camelcase(app_name), new_app_dir))


def get_actual_filepath(input_fp):
actual_fp = ''
if input_fp.count('/') > 0:
if os.path.isfile(input_fp):
actual_fp = input_fp
else:
for (dirpath, dirnames, filenames) in walk(main_dir):
if input_fp in filenames:
actual_fp = dirpath + '/' + input_fp
return actual_fp


def write_action(action, action_val, write_to_file):
lines = []
if 'description' in action_val:
lines.append("'''")
lines.append(action_val['description'])
parameters_args = ''
if 'parameters' in action_val:
parameters_args = add_parameters(action_val, lines)
if 'returns' in action_val:
add_returns(action_val, lines)
lines.append('@action')
lines.append('def {0}(self{1}):'.format(action.replace(' ', '_'), parameters_args))
lines.append('\tpass')
lines.append('\n')
write_to_file.writelines('\n'.join(['\t' + line for line in lines]))


def add_returns(action_val, lines):
success_val = action_val['returns']['Success']
lines.append('Output:')
lines.append('{0}: {1}'.format(success_val['schema']['type'], success_val['description']))
lines.append("'''")


def add_parameters(action_val, lines):
parameters0 = action_val['parameters'][0]
lines.append('Inputs:')
parameters_str = '{0} ({1}): {2}'.format(parameters0['name'], parameters0['type'],
parameters0['description'])
parameters_args = ', ' + parameters0['name']
required_val = parameters0['required']
if not required_val:
parameters_str += ' (Optional)'
parameters_args += '=None'
lines.append(parameters_str)
return parameters_args


help_msg_app_name = 'make template for app with name provided by app_name. The new app is stored under the "apps" ' \
'directory.'
help_msg_yaml = 'generate methods for app using yaml file at the filepath provided. If YAML_FILE is a filename, ' \
Expand Down

0 comments on commit 84f0509

Please sign in to comment.