#################### 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;