class Annotations(object): """ Contains the structure for generating release notes by collating the issue ID, release note text and business representative for all issues assigned to the current fix version which are in state done but not in state rejected. """ CONFIGURATION = 'bio.json' _document_controller = None _document_ready = False _document_title = 'Sequence Annotation' def __init__(self): """ initialise the ReleaseNote object """ self._document_controller = DocumentController(self.CONFIGURATION) def build_document(self): """ Creates the document structure """ try: self._document_controller.build() self._document_ready = True except: self._document_ready = False raise def publish(self): """ publish the document """ if not self._document_ready: self.build_document() self._document_controller.save(self._document_title + ' ' + '.docx')
def test_thread_manager_builds_simple_configuration( self, mock_config, mock_parser): mock_config.return_value = [self._path] Configuration('config_simple.json') document = DocumentController() self.assertIsInstance(document.configuration, Configuration) self.assertIsInstance(document.threadmanager, ThreadManager) self.assertIsInstance(document.reportmanager, ReportManager) self.assertIsInstance(document.partfactory, DocumentPartFactory) with patch('docx.document.Document.add_heading') as mock_heading, \ patch('docx.document.Document.add_paragraph') as mock_paragraph, \ patch('docx.document.Document.save') as mock_save: document.build() document.save('demo.docx') heading_calls = [ call('Test document structure for WeeklyReport/Helicopter view', 0), call('Week 41', 1), call('hello world', 1), #call('Test title', 3), call('This has sub-sections', 1), call('this is sub section 1', 2), call('this is sub section 2', 2), call('This section uses a file path for its text', 1) ] self.assertEquals(mock_heading.call_count, len(heading_calls)) mock_heading.assert_has_calls(heading_calls) self.assertEquals(17, mock_paragraph.call_count) mock_save.assert_called_with('demo.docx')
def test_csv_with_broken_file(self, mock_dataframe, mock_config_locations, mock_parse): self.tearDown() mock_config_locations.return_value = [self._path] mock_dataframe.side_effect = DataProviders.get_csv_results() with patch('pyccata.core.managers.clients.docx.Docx') as docx: docx.__implements__ = (ReportingInterface, ) document = DocumentController('broken_csv.json') document.build() csvfiles = document._thread_manager.projectmanager._client._client self.assertIsInstance(csvfiles, CSVClient) self.assertEquals(len(csvfiles), 0) document.save('Test Document.docx')
def test_csv_with_multi_file(self, mock_dataframe, mock_config_locations, mock_parse): mock_config_locations.return_value = [self._path] mock_dataframe.side_effect = DataProviders.get_csv_results() with patch('pyccata.core.managers.clients.docx.Docx') as docx: docx.__implements__ = (ReportingInterface, ) document = DocumentController('csv_multi_file.json') document.build() self.assertEquals( None, document._thread_manager.projectmanager._client.server) self.assertIsInstance( document._thread_manager.projectmanager._client.projects(), list) csvfiles = document._thread_manager.projectmanager._client._client self.assertIsInstance(csvfiles, CSVClient) self.assertEquals(len(csvfiles), 2) for csv in csvfiles: self.assertIsInstance(csv._dataframe, pandas.DataFrame) self.assertEquals(len(csv._dataframe.index), 3) document.save('Test Document.docx')
class ReleaseNote(object): """ Contains the structure for generating release notes by collating the issue ID, release note text and business representative for all issues assigned to the current fix version which are in state done but not in state rejected. """ CONFIGURATION = 'releasenote.json' _document_controller = None _document_ready = False _document_title = 'MSS Platform Release' def __init__(self): """ initialise the ReleaseNote object """ self._document_controller = DocumentController(self.CONFIGURATION) def build_document(self): """ Creates the document structure """ try: self._document_controller.build() self._document_ready = True except: self._document_ready = False raise def publish(self): """ publish the document """ if not self._document_ready: self.build_document() date = datetime.strftime(datetime.now(), '%Y-%m-%d') self._document_controller.format_for_email() self._document_controller.save(self._document_title + ' ' + date + '.docx')
class ReleaseInstructions(object): """ Contains the structure for generating release notes by collating the issue ID, release note text and business representative for all issues assigned to the current fix version which are in state done but not in state rejected. """ CONFIGURATION = 'releaseinstructions.json' _document_controller = None _document_ready = False _document_title = 'MSS Platform Release Rollout and Rollback' _configuration = None _regex = re.compile( '^([0-9]*)?_?(\w+-\d+)_([a-zA-Z-]+)_(\w+)_(UP|DOWN)_([0-9]*)?_?(\w+)(.[a-zA-Z]+)?$', re.IGNORECASE ) def __init__(self): """ initialise the ReleaseNote object """ self._document_controller = DocumentController(self.CONFIGURATION) self._configuration = self._document_controller.configuration self._document_controller.add_callback('attachments', getattr(ReleaseInstructions, 'file_collator')) def build_document(self): """ Creates the document structure """ try: self._document_controller.build() final = Replacements().find('FINAL').value if final in ['1', 'True', 'TRUE', 'true']: pipelines = self._document_controller.threadmanager.find('Deployment Pipelines') if pipelines is not None or len(pipelines) > 0: for pipeline in pipelines: self.pipeline_trigger(pipeline) self._document_ready = True except: self._document_ready = False raise def publish(self): """ publish the document """ if not self._document_ready: self.build_document() date = datetime.strftime(datetime.now(), '%Y-%m-%d') self._document_controller.format_for_email() self._document_controller.save(self._document_title + ' ' + date + '.docx') def pipeline_trigger(self, pipeline): """ Triggers a pipeline on Jenkins FUTURE - move out to Jenkins API wrapper """ replacements = Replacements() pipeline = replacements.replace(pipeline) matches = re.search(r'(http.*\/).*', pipeline) if matches: pipeline = matches.groups(0)[0].rstrip('/') Logger().info('Triggering pipeline {0}'.format(pipeline)) params = {} auth = (self._configuration.jenkins.user, self._configuration.jenkins.password) pipeline = '{0}/{1}'.format(pipeline, replacements.replace('{TRIGGER_URI}')) response = requests.post(pipeline, auth=auth, params=params, verify=False) if response.status_code != 200: Logger().error( 'Error whilst triggering pipeline - server returned status {0}'.format( response.status_code ) ) Logger().info('Done {0}'.format(pipeline)) @staticmethod def file_collator(path, attachments): """ This method unpacks all zip files and copies the resulting SQL into a temporary location Once complete, it repackages the temporary location and moves it into the Workspace for attachment to the release instruction email """ Logger().info('Collating SQL files from attachments') package_name = Replacements().replace('mss-platform-release-{FIX_VERSION}').replace('/', '-') destination = os.path.join(path, package_name) up_dir = os.path.join(destination, 'up') down_dir = os.path.join(destination, 'down') package_dir = os.path.join(os.getcwd(), 'packages') if not os.path.exists(package_dir): create_directory(package_dir) create_directory(destination) create_directory(up_dir) create_directory(down_dir) for attachment in attachments: if hasattr(ReleaseInstructions, attachment.extension.lower() + '_handler'): filename = os.path.join(path, attachment.filename) Logger().debug( 'Calling handler \'' + attachment.extension + '_handler\' for file ' + filename ) try: getattr(ReleaseInstructions, attachment.extension.lower() + '_handler')( destination, os.path.join(path, attachment.filename) ) except Exception as exception: Logger().error('Failed to add file \'' + filename + '\' to archive') Logger().error('reason: ' + str(exception)) filename = mkzip(destination, os.path.join(path, package_name + '.zip')) shutil.move(os.path.join(destination, filename), os.path.join(package_dir, os.path.basename(filename))) return os.path.basename(filename) @staticmethod def zip_handler(destination, filepath): temporary_directory = os.path.join(destination, 'temp') create_directory(temporary_directory) unzip(filepath, temporary_directory, flatten=True) sql_files = [ filename for filename in os.listdir( temporary_directory ) if re.search( "^.*\.sql$", filename, re.IGNORECASE ) ] if len(sql_files) == 0: Logger().error('Got zip file but it contains no SQL files. Skipping...') for sql in sql_files: ReleaseInstructions.sql_handler(destination, os.path.join(temporary_directory, sql)) shutil.rmtree(temporary_directory) @staticmethod def sql_handler(destination, filepath): try: Logger().info('Adding file \'' + filepath + '\'') sql_file = ReleaseInstructions._sql_filename(os.path.basename(filepath)) output_directory = os.path.join( destination, sql_file.direction.lower(), sql_file.ticket_order, sql_file.ticket_id, sql_file.server, sql_file.database ) create_directory(output_directory) os.rename( filepath, os.path.join( output_directory, ( sql_file.operation_order + '_' if sql_file.operation_order is not None else '' ) + sql_file.operation + '.sql' ) ) # pylint: disable=broad-except # We are not bothering to handle invalidly named SQL files - these will be # ignored from the instruction sent to SysOps. # ANY error here and the file gets bounced back to the developer. except Exception as exception: Logger().error('Failed to handle SQL file \'' + os.path.basename(filepath) + '\'') Logger().error('reason: ' + str(exception)) @staticmethod def _sql_filename(filename): SQLStructure = namedtuple( 'SQLStructure', 'ticket_order ticket_id server database direction operation_order operation' ) try: groups = ReleaseInstructions._regex.search(filename).groups() return SQLStructure( ticket_order=(groups[0] if groups[0] != '' else 'any_order'), ticket_id=groups[1], server=groups[2], database=groups[3], direction=groups[4], operation_order=(groups[5] if groups[5] != '' else None), operation=groups[6] ) except AttributeError: raise InvalidFilenameError('The filename \'' + filename + '\' does not match the anticipated format')