#################### main pod documentation begin ################### =head1 NAME Zymonic::Action::AddSupersetSource - Zymonic Workflow System Action module =head1 SYNOPSIS Adds a Superset Source to the Superset Configuration and runs the initial denormalisation of the data. =head1 DESCRIPTION Adds a Superset Source to the Superset Configuration and runs the initial denormalisation of the data. =head1 USAGE It is not expected that this class would be used directly - only called from the ZymonicSuperset.xml integration. The following ClassOption Schema should be used: =head1 BUGS NONE =head1 SUPPORT As in the license, Zymonic is provided without warranty or support unless purchased separately, however... If you email zymonic-support@zednax.com your issue will be noted and may receive a response. For security issues, please contact zymonic-security@zednax.com and someone will respond within 8 working hours. =head1 AUTHOR Alex Masidlover et al. CPAN ID: MODAUTHOR Zednax Limited alex.masidlover@zednax.com http://www.zednax.com =head1 COPYRIGHT This program is free software licensed under the... Zymonic Public License 1.0 The full text of the license can be found in the LICENSE file included with this module. Other licenses may be acceptable if including parts of Zymonic in larger projects, please contact Zednax for details. =head1 SEE ALSO Zymonic perl(1). =cut #################### main pod documentation end ################### package Zymonic::Action::AddSupersetSource; use strict; BEGIN { use Exporter (); use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); $VERSION = '0.01'; @ISA = qw(Exporter); #Give a hoot don't pollute, do not export more than needed by default @EXPORT = qw(); @EXPORT_OK = qw(); %EXPORT_TAGS = (); } use base "Zymonic::Action"; use Zymonic; use Zymonic::Utils qw(get_array debug xml_escape debug_function_start debug_function_stop); use Zymonic; use Exception::Class ( 'Zymonic::Exception::Action::AddSupersetSource' => { isa => 'Zymonic::Exception::Action', fields => [], description => 'Problem with AddSupersetSource Action.' }, ); #################### subroutine header begin #################### =head2 init Usage : N/A Purpose : The init method is called by the object constructor to initialise the object fields. Returns : nothing Argument : nothing Throws : Zymonic::Exception::Action Comment : See Also : =cut #################### subroutine header end #################### sub init { my $self = shift; $self->SUPER::init(); } #################### subroutine header begin #################### =head2 filter Usage : $action->filter Purpose : Returns the filter Returns : See purpose Argument : nothing Throws : Comment : nothing =cut #################### subroutine header end #################### sub filter { my $self = shift; unless ( $self->{filter} ) { # Load the filter $self->{filter} = Zymonic::Filter->new( parent => $self, zname => $self->{filter_field_zname}, config => $self->{config}, DB => $self->{DB}, auth => $self->{auth}, ); } return $self->{filter}; } #################### subroutine header begin #################### =head2 do Usage : $action->do Purpose : Performs the action, i.e. sends a HTTP request Returns : nothing Argument : nothing Throws : Comment : nothing =cut #################### subroutine header end #################### sub do { my $self = shift; my $fake = shift || ''; my $no_display_attributes = shift || 0; my $result = $self->SUPER::do( $fake, $no_display_attributes ); $self->{filter_field_zname} = $self->static_or_field_value('Filter'); $self->{date_time_field} = $self->static_or_field_value( 'DateTimeField', 'optional' ); if ( $self->{success} && $self->{success} eq 'true' ) { $result->{success} = $self->{success} = 'false'; # call toolkit to update the source now my $toolkit_result = $self->run_toolkit( 'Superset', 'update_sources', [ $self->static_or_field_value('SourceID') ], # no locking, as calling transition will handle locks { no_locking => 'true', } ) || ''; $self->add_message_to_result( $result, $toolkit_result ); my $table_id = $self->create_superset_table( $self->{config}->sys_opt('superset_db_id'), $self->{date_time_field} ); # Update the source table with the correct id $self->{DB}->run_statement( { string => "UPDATE zz_superset_source SET table_id = ? WHERE source_filter = ?", params => [ $table_id, $self->{filter_field_zname} ] } ); $self->{success} = 'true'; my $src = "https://" . $self->{config}->sys_opt('superset_host') . ":" . $self->{config}->sys_opt('superset_port') . "/login/" . $self->{config}->{system_name} . "?next="; # Do this manually to not encode the entities. unless ( $result->{MessageWithLink} ) { $result->{MessageWithLink} = []; } push( @{ $result->{MessageWithLink} }, { content => $self->ll("Superset Source added - Visit to open Superset."), path => $src, } ); } $result->{success} = $self->{success}; return $result; } #################### subroutine header begin #################### =head2 create_superset_column Usage : $action->create_superset_column Purpose : Creates a superset column Returns : nothing Argument : superset table id, field zname Throws : Comment : nothing =cut #################### subroutine header end #################### sub create_superset_column { my $self = shift; my $table_id = shift; my $field_ref = shift; my $field_column_name = shift; # table_columns my %values = ( # created_on DATETIME NOT NULL, created_on => $self->superset_db->timestamp, # changed_on DATETIME NOT NULL, changed_on => $self->superset_db->timestamp, # table_id INTEGER, table_id => $table_id, # column_name VARCHAR(255) NOT NULL, column_name => $field_ref->{zname}, # is_dttm BOOLEAN, TODO - add a mechanism to DateTime to add a flag to its ref is_dttm => ( $field_ref->{class} eq 'DateTime' ? 1 : 0 ), # is_active BOOLEAN, is_active => 1, # type VARCHAR(32), type => uc( $field_ref->{field_type} ) . ( ( $field_ref->{field_type} =~ /timestamp|datetime|text|double|decimal|float/i ) ? '' : '(' . ( ( $field_ref->{max_length} ) ? $field_ref->{max_length} : $self->{DB}->default_max_length( $field_ref->{field_type} ) ) . ')' ), # groupby BOOLEAN, groupby => 1, # filterable BOOLEAN, filterable => 1, # description TEXT, # created_by_fk INTEGER, # changed_by_fk INTEGER, # expression TEXT, # verbose_name VARCHAR(1024), verbose_name => $field_column_name, # python_date_format VARCHAR(255), ); my $column_rec = $self->superset_db->run_query( { string => 'SELECT id FROM table_columns WHERE column_name = ? AND table_id = ?', params => [ $field_ref->{zname}, $table_id ] } ); if ( scalar( @{$column_rec} ) ) { # Update the table record $self->superset_db->run_statement( { string => "UPDATE table_columns SET " . join( ", ", map { $_ . " = ? " } keys(%values) ) . " WHERE id = ?", params => [ values(%values), $column_rec->[0]->{id} ] } ); } else { $self->superset_db->run_statement( { string => "INSERT INTO table_columns (" . join( ",", keys(%values) ) . ") VALUES (" . join( ", ", map { "?" } keys(%values) ) . ")", params => [ values(%values) ] } ); } $column_rec = $self->superset_db->run_query( { string => 'SELECT id FROM table_columns WHERE column_name = ? AND table_id = ?', params => [ $field_ref->{zname}, $table_id ] } ); return $column_rec->[0]->{id}; } #################### subroutine header begin #################### =head2 create_superset_table Usage : $action->create_superset_table Purpose : Creates a superset table Returns : nothing Argument : database id and datetime column name Throws : Comment : nothing =cut #################### subroutine header end #################### sub create_superset_table { my $self = shift; my $database_id = shift; my $datetime_col = shift; #tables my %values = ( # created_on DATETIME NOT NULL, created_on => $self->superset_db->timestamp, # changed_on DATETIME NOT NULL, changed_on => $self->superset_db->timestamp, # table_name VARCHAR(250) NOT NULL, table_name => $self->{filter_field_zname}, # main_dttm_col VARCHAR(250), main_dttm_col => $datetime_col, # default_endpoint TEXT, # database_id INTEGER NOT NULL, database_id => $database_id, # created_by_fk INTEGER, # changed_by_fk INTEGER, # "offset" INTEGER, # description TEXT, # is_featured BOOLEAN, # cache_timeout INTEGER, # schema VARCHAR(255), # sql TEXT, # params TEXT, # perm VARCHAR(1000), - TODO this is populated with [db name].[table name](id:[id]) - not sure why or what it relates to. # filter_select_enabled BOOLEAN, # fetch_values_predicate VARCHAR(1000), # is_sqllab_view BOOLEAN DEFAULT 0, # template_params TEXT, schema_perm VARCHAR(1000), ); my $table_rec = $self->superset_db->run_query( { string => 'SELECT id FROM tables WHERE table_name = ?', params => [ $self->{filter_field_zname} ] } ); my $table_id; if ( scalar( @{$table_rec} ) ) { $table_id = $table_rec->[0]->{id}; # Update the table record $self->superset_db->run_statement( { string => "UPDATE tables SET " . join( ", ", map { $_ . " = ? " } keys(%values) ) . " WHERE table_name = ?", params => [ values(%values), $self->{filter_field_zname} ] } ); } else { # do the insert $self->superset_db->run_statement( { string => "INSERT INTO tables (" . join( ",", keys(%values) ) . ") VALUES (" . join( ", ", map { "?" } keys(%values) ) . ")", params => [ values(%values) ] } ); # get the table id NOTE last_insert_id should take a table object # but currently nothing actually uses it. $table_id = $self->superset_db->last_insert_id(); my $source_perm = "[" . $self->{config}->denormalised_db_details->{dbname} . "].[" . $self->{filter_field_zname} . "].(id:" . $table_id . ")"; # update the source with the perm $self->superset_db->run_statement( { string => "UPDATE tables SET perm = ? WHERE id = ?", params => [ $source_perm, $table_id ] } ); # add the permissions to permission_view and the role # [perm] - needs datasource_access my $datasource_access_rec = $self->superset_db->run_query( { string => "SELECT id FROM ab_permission WHERE name = ?", params => ["datasource_access"] } ); $self->superset_db->run_statement( { string => "INSERT INTO ab_view_menu (name) VALUES (?)", params => [$source_perm] } ); my $abvm_id = $self->superset_db->last_insert_id(); $self->superset_db->run_statement( { string => "INSERT INTO ab_permission_view (permission_id, view_menu_id) VALUES (?,?)", params => [ $datasource_access_rec->[0]->{id}, $abvm_id ] } ); my $abpv_id = $self->superset_db->last_insert_id(); $self->superset_db->run_statement( { string => "INSERT INTO ab_permission_view_role (role_id, permission_view_id) " . "SELECT id, ? FROM ab_role WHERE name LIKE ?", params => [ $abpv_id, $self->{config}->{system_name} . "_%" ], } ); } my @report_fields = $self->filter->get_report_fields(''); my $qb = $self->filter->build_query_builder( "", \@report_fields, "denormalise" ); my @column_ids = (); foreach my $denorm_field ( grep { !$_->{skip_in_sql} } values %{ $qb->{fields} } ) { # skip hidden fields next if ( $denorm_field->{hidden} || '' ) eq 'true'; my $display_name = $qb->denormalisedAS($denorm_field); my $column_id = $self->create_superset_column( $table_id, $denorm_field, $display_name ); push( @column_ids, $column_id ); } # Clear unused columns $self->superset_db->run_statement( { string => 'DELETE FROM table_columns WHERE table_id = ? AND id NOT IN(' . join( ", ", map { "?" } @column_ids ) . ')', params => [ $table_id, @column_ids ] } ); return $table_id; } #################### subroutine header begin #################### =head2 superset_db Usage : $self->superset_db->run_query(...) Purpose : returns a Zymonic DB object connected to the superset sqlite DB Returns : see purpose Argument : nothing Throws : nothing Comment : See Also : =cut #################### subroutine header end #################### sub superset_db { my $self = shift; # get option # TODO permissions on SQLite DB file need to allow for apache access my $sqlitefile = $self->{config}->sys_opt('superset_sqlite'); $self->{superset_db} = Zymonic::DB->new( dbdriver => 'SQLite', driver_string => 'dbi:SQLite:dbname=' . $sqlitefile, ) unless $self->{superset_db}; return $self->{superset_db}; } 1;