""" Apple Music Streams Ingestion Workflow. Ingest data from the Apple Music Streams report. See https://help.apple.com/itc/contentsalesandtrends/#/itc1f31430ac. """ from garcon import param from garcon.param import StaticParam from garcon_contrib.dynamo_feed_status import garcon_feed_status from feed_ingestion.flows import base from feed_ingestion.flows.apple_music_streams import config from feed_ingestion.flows.apple_music_streams import generators from feed_ingestion.flows.apple_music_streams import tasks from feed_ingestion.tasks import feed_status_tasks from feed_ingestion.tasks import load_fact_tables_tasks_sf from feed_ingestion.tasks import load_raw_table_tasks_sf from feed_ingestion.tasks import notification_tasks from feed_ingestion.tasks import overall_status_tasks from feed_ingestion.tasks import reporter_tasks from feed_ingestion.tasks import validate_raw_data_tasks_sf from feed_ingestion.util.jenkins.tasks import build_jenkins_dbt class Flow(base.FlowLicensor, base.FlowConfigMixin): """Apple Music Streams Flow class. This flow download the Apple Music Streams report data for IODA & The ORCHARD accounts and loads to a staging_raw_apple_music_streams. """ def __init__(self): """Initialize an Apple Music Streams flow.""" super(Flow, self).__init__(config.feed_name, config.feed_version) self.timeout = 60 * 60 * 6 # 6 hours instead of default 20 hours def decider(self, schedule, context): """Activity decider. Args: schedule (callable): The scheduler method. context (dict): Initial context of workflow. """ check_concurrent_status = schedule( 'check_concurrent_status', self.check_concurrent_status) if check_concurrent_status.result.get( 'check_concurrent_status.stop') is True: return bootstrap = schedule( 'bootstrap', self.bootstrap, requires=[check_concurrent_status]) if bootstrap.result.get('bootstrap.stop') is True: return is_soft_reload = bootstrap.result.get('bootstrap.soft_reload') if is_soft_reload: last_activities = [bootstrap] else: if bootstrap.result.get('bootstrap.use_s3') == 'True': # move needed files from the drop location to archive location context_licensor = context.get('licensor') if context_licensor == 'sme': grab_drop_files = schedule( 'grab_drop_files', self.grab_drop_files, requires=[bootstrap]) last_activities = [grab_drop_files] elif context_licensor == 'awal': grab_drop_files_from_s3_awal = schedule( 'grab_drop_files_from_s3_awal', self.grab_drop_files_from_s3_awal, requires=[bootstrap]) last_activities = [grab_drop_files_from_s3_awal] else: raise ValueError( f'Licensor {context_licensor} is not supported') else: # Download report files and upload them to s3 reporter_to_s3 = schedule( 'reporter_to_s3', self.reporter_to_s3, requires=[bootstrap]) last_activities = [reporter_to_s3] # Check if all files were downloaded update_feed_file_status = schedule( 'update_feed_file_status', self.update_feed_file_status, requires=last_activities) last_activities = [update_feed_file_status] check_available_reports = schedule( 'check_available_reports', self.check_available_reports, requires=last_activities) if check_available_reports.result.get( 'check_available_reports.stop') is True: return if not is_soft_reload: create_temp_staging_raw_tables = schedule( 'create_temp_staging_raw_tables', self.create_temp_staging_raw_tables, requires=[check_available_reports]) load_temp_staging_raw_tables = schedule( 'load_temp_staging_raw_tables', self.load_temp_staging_raw_tables, requires=[create_temp_staging_raw_tables]) populate_staging_raw = schedule( 'populate_staging_raw', self.populate_staging_raw, requires=[load_temp_staging_raw_tables]) drop_temp_stage_tables = schedule( 'drop_temp_stage_tables', self.drop_temp_stage_tables, requires=[populate_staging_raw]) set_status_to_populated_raw_table = schedule( 'set_status_to_populated_raw_table', self.set_status_to_populated_raw_table, requires=[drop_temp_stage_tables]) set_status_ingested = schedule( 'set_status_ingested', self.set_status_ingested, requires=[set_status_to_populated_raw_table]) last_activities = [set_status_ingested] if check_available_reports.result.get( 'check_available_reports.load_fact_analytics') is True: if not is_soft_reload: # no need to update_dim_tables # because they are populated from staging_raw # and in case of soft_reload staging_raw was not updated update_dim_tables = schedule( 'update_dim_tables', self.update_dim_tables, requires=last_activities) last_activities = [update_dim_tables] update_apple_id_mapping = schedule( 'update_apple_id_mapping', self.update_apple_id_mapping, requires=last_activities) load_fact_analytics = schedule( 'load_fact_analytics', self.load_fact_analytics, requires=[update_apple_id_mapping]) set_status_ingested_to_fact_analytics_report = schedule( 'set_status_ingested_to_fact_analytics_report', self.set_status_ingested_to_fact_analytics_report, requires=[load_fact_analytics]) last_activities = [set_status_ingested_to_fact_analytics_report] if check_available_reports.result.get( 'check_available_reports.update_library_reports') is True: update_staging_raw_library_reports = schedule( 'update_staging_raw_library_reports', self.update_staging_raw_library_reports, requires=last_activities) last_activities = [update_staging_raw_library_reports] load_aggregated_skips_and_saves = schedule( 'load_aggregated_skips_and_saves', self.load_aggregated_skips_and_saves, requires=last_activities) set_overall_status_ingested = schedule( 'set_overall_status_ingested', self.set_overall_status_ingested, requires=[load_aggregated_skips_and_saves]) schedule( 'build_jenkins_dbt', self.build_jenkins_dbt, requires=[set_overall_status_ingested]) @property def check_concurrent_status(self): """Check and reset feed status if it is needed.""" return self.create( name='check_concurrent_status', tasks=base.SyncRunner( tasks.check_concurrent_status.fill( namespace='check_concurrent_status', date='context_date', reload='reload', domain=param.StaticParam(self.domain), licensor='licensor'))) @property def bootstrap(self): """Bootstrap initial configuration.""" return self.create( name='bootstrap', tasks=base.SyncRunner( tasks.bootstrap.fill( namespace='bootstrap', date='context_date', reload='reload', reports='reports', licensor='licensor', snowflake_error_limit='snowflake_error_limit', use_s3='use_s3'))) @property def reporter_to_s3(self): """Load Reporter files to Apple Music Streams' S3 archives bucket.""" return self.create( name='reporter_to_s3', generators=[generators.itunes_reports_generator], schedule_to_start=7200, tasks=base.SyncRunner( reporter_tasks.extract_reporter_file_to_s3.fill( namespace='reporter_to_s3', reporter_account='reporter_account', report_type='report_type', report_role=param.StaticParam('sales'), date='bootstrap.date', destination_s3_path='bootstrap.s3_archive_bucket', feed_name='feed_name', licensor='licensor', vendors='vendors_config'))) @property def grab_drop_files(self): """Archive needed files from the drop location to archive location.""" return self.create( name='grab_drop_files', generators=[generators.itunes_reports_generator], tasks=base.SyncRunner( tasks.grab_drop_files.fill( namespace='grab_drop_files', feed_name='feed_name', date='bootstrap.date', s3_archive_path='bootstrap.s3_archive_bucket', s3_download_path='bootstrap.s3_drop_bucket', filename='filename', reporter_account='reporter_account', report_type='report_type', contexts_config='bootstrap.contexts', licensor='licensor'))) @property def grab_drop_files_from_s3_awal(self): """Copy files from drop location and archive files on s3.""" return self.create( name='grab_drop_files_from_s3_awal', generators=[generators.itunes_reports_generator], schedule_to_start=48000, tasks=base.SyncRunner( tasks.grab_drop_files_awal.fill( namespace='grab_drop_files_from_s3_awal', feed_name='feed_name', date='bootstrap.date', report_name='report_type', filename='filename', s3_archive_path='bootstrap.s3_archive_bucket', licensor='licensor', reporter_account='reporter_account'))) @property def update_feed_file_status(self): """Update Feed Status whether files were downloaded. (Or not available). """ return self.create( name='update_feed_file_status', generators=[generators.update_feed_file_status_generator], schedule_to_start=48000, tasks=base.SyncRunner( feed_status_tasks.update_feed_file_status.fill( namespace='feed_file_status', feed_name='feed_name', date='bootstrap.date', s3_path='bootstrap.s3_archive_bucket', files='expected_download_files', report_type='report_type', contexts_config='bootstrap.contexts', licensor='licensor' ))) @property def check_available_reports(self): """Check reports which was uploaded on s3.""" return self.create( name='check_available_reports', schedule_to_start=48000, tasks=base.SyncRunner( tasks.check_available_reports.fill( namespace='check_available_reports', date='bootstrap.date', reports_status_names='bootstrap.reports_status_names', s3_path='bootstrap.s3_archive_bucket', licensor='licensor', is_soft_reload='bootstrap.soft_reload', ))) @property def create_temp_staging_raw_tables(self): """Create temp staging raw table.""" return self.create( name='create_temp_staging_raw_tables', generators=[generators.temp_staging_raw_generator], schedule_to_start=48000, tasks=base.SyncRunner( load_raw_table_tasks_sf.create_temp_staging_raw_table.fill( namespace='create_temp_staging_raw_tables', date='bootstrap.date', sfdb_params='sfdb_params', feed_name='feed_name', secrets_path=param.StaticParam(config.secrets_path), temp_staging_raw_table='temp_table_name', kwargs='kwargs'))) @property def load_temp_staging_raw_tables(self): """Load temp staging raw tables.""" return self.create( name='load_temp_staging_raw_tables', generators=[generators.temp_staging_raw_generator], schedule_to_start=48000, tasks=base.SyncRunner( validate_raw_data_tasks_sf.load_temp_staging_raw_table.fill( namespace='load_temp_staging_raw_tables', aws=param.StaticParam(self.conf_aws), date='bootstrap.date', sfdb_params='sfdb_params', feed_name='feed_name', secrets_path=param.StaticParam(config.secrets_path), kwargs='kwargs', key_dir='key_dir', temp_staging_raw_table='temp_table_name', error_limit='bootstrap.snowflake_error_limit'))) @property def populate_staging_raw(self): """Join temp tables and unload data in the staging_raw table.""" return self.create( name='load_staging_raw', generators=[generators.populate_staging_raw_generator], schedule_to_start=48000, tasks=base.SyncRunner( tasks.load_staging_raw_table.fill( namespace='load_staging_raw_table', date='bootstrap.date', processed_datetime='bootstrap.processed_datetime', vendors='vendors', sfdb_params='sfdb_params', staging_raw_table='staging_raw_table', report_name='report_name', feed_name='feed_name', secrets_path=param.StaticParam(config.secrets_path), licensor='licensor'))) @property def update_staging_raw_library_reports(self): """Update upc and isrc for amLibraryEvents, amTotalLibraryAdds.""" return self.create( name='update_staging_raw_library_reports', generators=[ generators.update_staging_raw_library_reports_generator], schedule_to_start=48000, tasks=base.SyncRunner( tasks.update_staging_raw_library_reports.fill( namespace='update_staging_raw_library_reports', date='bootstrap.date', sfdb_params='sfdb_params', report_name='report_name', feed_name='feed_name', secrets_path=param.StaticParam(config.secrets_path), licensor='licensor'))) @property def load_aggregated_skips_and_saves(self): """Activity to load load_aggregated_skips_and_saves.""" return self.create( name='load_aggregated_skips_and_saves', schedule_to_start=48000, tasks=base.SyncRunner( tasks.load_aggregated_skips_and_saves.fill( namespace='load_aggregated_skips_and_saves', date='bootstrap.date', sfdb_params='bootstrap.sfdb_params', secrets_path=param.StaticParam(config.secrets_path), licensor='licensor', available_reports='check_available_reports.' 'available_reports', is_soft_reload='bootstrap.soft_reload', ))) @property def drop_temp_stage_tables(self): """Drop the staging_raw temp tables.""" return self.create( name='drop_temp_stage_tables', generators=[generators.temp_staging_raw_generator], schedule_to_start=48000, tasks=base.SyncRunner( tasks.drop_temp_table.fill( namespace='drop_temp_table', temp_table_name='temp_table_name', sfdb_params='sfdb_params', secrets_path=param.StaticParam(config.secrets_path), report_name='report_name'))) @property def set_status_to_populated_raw_table(self): """Set overall feed status to POPULATED_RAW_TABLE.""" return self.create( name='set_status_to_populated_raw_table', generators=[generators.populate_staging_raw_generator], schedule_to_start=48000, tasks=base.SyncRunner( overall_status_tasks.set_overall_status.fill( namespace='set_status_to_populated_raw_table', feed_name='feed_name', date='bootstrap.date', status=param.StaticParam( garcon_feed_status.STATUS_POPULATED_RAW_TABLE)))) @property def set_status_ingested(self): """Set report status INGESTED if all tasks completed.""" return self.create( name='set_status_ingested', schedule_to_start=48000, tasks=base.SyncRunner( tasks.set_status_ingested.fill( namespace='set_status_ingested', date='bootstrap.date', reports_status_names='bootstrap.reports_status_names', licensor='licensor'))) @property def update_apple_id_mapping(self): """Populate staging_raw_apple_music table with ioda data.""" return self.create( name='update_apple_id_mapping', schedule_to_start=48000, tasks=base.SyncRunner( tasks.update_apple_id_mapping.fill( namespace='update_apple_id_mapping', date='bootstrap.date', sfdb_params='sfdb_params', skip_mapping='skip_mapping', secrets_path=param.StaticParam(config.secrets_path), licensor='licensor'))) @property def update_dim_tables(self): """Update dimension tables.""" return self.create( name='update_dim_tables', schedule_to_start=48000, tasks=base.SyncRunner( load_fact_tables_tasks_sf.update_dim_tables.fill( namespace='update_dim_tables', feed_name='bootstrap.feed_name_for_fact_analytics', date='bootstrap.date', sfdb_params='sfdb_params', secrets_path=param.StaticParam(config.secrets_path), kwargs=param.StaticParam(config.dimension_tables)), notification_tasks.sns_publish_message.fill( topic=param.StaticParam( config.dimension_tables['sns_topic']), feed_name='bootstrap.feed_name_for_fact_analytics', date='bootstrap.date', subject='update_dim_tables.sns_report_subject', message='update_dim_tables.sns_report_message'))) @property def load_fact_analytics(self): """Load fact_analytics tables.""" return self.create( name='load_fact_analytics', schedule_to_start=48000, tasks=base.SyncRunner( load_fact_tables_tasks_sf.load_fact_data.fill( namespace='load_fact_data', feed_name='bootstrap.feed_name_for_fact_analytics', date='bootstrap.date', sfdb_params='sfdb_params', secrets_path=param.StaticParam(config.secrets_path), kwargs='bootstrap.fact_analytics_kwargs'))) @property def set_status_ingested_to_fact_analytics_report(self): """Set status to INGESTED to fact_analytics report.""" return self.create( name='set_status_ingested_to_fact_analytics_report', schedule_to_start=48000, tasks=base.SyncRunner( tasks.set_status_ingested_to_fact_analytics_report.fill( namespace='set_status_ingested_to_fact_analytics_report', feed_name='bootstrap.feed_name_for_fact_analytics', date='bootstrap.date', status=param.StaticParam( garcon_feed_status.STATUS_INGESTED)))) @property def set_overall_status_ingested(self): """Set overall feed status INGESTED if all reports are completed.""" return self.create( name='set_overall_status_ingested', schedule_to_start=48000, tasks=base.SyncRunner( tasks.set_overall_status_ingested.fill( namespace='set_overall_status_ingested', date='bootstrap.date', licensor='licensor'))) @property def build_jenkins_dbt(self): """Build Jenkins DBT job.""" return self.create( name='build_jenkins_dbt', schedule_to_start=48000, tasks=base.SyncRunner( build_jenkins_dbt.fill( namespace='build_jenkins_dbt', date='bootstrap.date', feed_name=StaticParam(config.feed_name), licensor='licensor', build_dbt='build_dbt', # coming from context of the https://scheduler.theorchard.io/job/swf-am-streams-sf-exec/ # noqa config='bootstrap.jenkins_config')))