MDL-64779 tool_analytics: Export selector
authorDavid Monllaó <davidm@moodle.com>
Tue, 26 Mar 2019 12:57:09 +0000 (13:57 +0100)
committerDavid Monllaó <davidm@moodle.com>
Mon, 1 Apr 2019 11:56:37 +0000 (13:56 +0200)
admin/tool/analytics/amd/build/model.min.js
admin/tool/analytics/amd/src/model.js
admin/tool/analytics/classes/output/models_list.php
admin/tool/analytics/lang/en/tool_analytics.php
admin/tool/analytics/model.php
admin/tool/analytics/templates/export_options.mustache [new file with mode: 0644]
analytics/classes/model.php
analytics/classes/model_config.php
analytics/tests/prediction_test.php

index 180805d..9e958db 100644 (file)
Binary files a/admin/tool/analytics/amd/build/model.min.js and b/admin/tool/analytics/amd/build/model.min.js differ
index f39c577..8d35e86 100644 (file)
@@ -149,6 +149,71 @@ define(['jquery', 'core/str', 'core/log', 'core/notification', 'core/modal_facto
                         return;
                     });
 
+                    modal.show();
+                    return modal;
+                }).fail(Notification.exception);
+            });
+        },
+
+        /**
+         * Displays export options.
+         *
+         * We have two main options: export training data and export configuration.
+         * The 2nd option has an extra option: include the trained algorithm weights.
+         *
+         * @param  {String}  actionId
+         * @param  {Boolean} isTrained
+         */
+        selectExportOptions: function(actionId, isTrained) {
+            $('[data-action-id="' + actionId + '"]').on('click', function(ev) {
+                ev.preventDefault();
+
+                var a = $(ev.currentTarget);
+
+                if (!isTrained) {
+                    // Export the model configuration if the model is not trained. We can't export anything else.
+                    a.attr('href', a.attr('href') + '&action=exportmodel&includeweights=0');
+                    window.location.href = a.attr('href');
+                    return;
+                }
+
+                var stringsPromise = Str.get_strings([
+                    {
+                        key: 'export',
+                        component: 'tool_analytics'
+                    }
+                ]);
+                var modalPromise = ModalFactory.create({type: ModalFactory.types.SAVE_CANCEL});
+                var bodyPromise = Templates.render('tool_analytics/export_options', {});
+
+                $.when(stringsPromise, modalPromise).then(function(strings, modal) {
+
+                    modal.getRoot().on(ModalEvents.hidden, modal.destroy.bind(modal));
+
+                    modal.setTitle(strings[0]);
+                    modal.setSaveButtonText(strings[0]);
+                    modal.setBody(bodyPromise);
+
+                    modal.getRoot().on(ModalEvents.save, function() {
+
+                        var exportOption = $("input[name='exportoption']:checked").val();
+
+                        if (exportOption == 'exportdata') {
+                            a.attr('href', a.attr('href') + '&action=exportdata');
+
+                        } else {
+                            a.attr('href', a.attr('href') + '&action=exportmodel');
+                            if ($("#id-includeweights").is(':checked')) {
+                                a.attr('href', a.attr('href') + '&includeweights=1');
+                            } else {
+                                a.attr('href', a.attr('href') + '&includeweights=0');
+                            }
+                        }
+
+                        window.location.href = a.attr('href');
+                        return;
+                    });
+
                     modal.show();
                     return modal;
                 }).fail(Notification.exception);
index 351dae4..5152b6d 100644 (file)
@@ -235,22 +235,27 @@ class models_list implements \renderable, \templatable {
                 $actionsmenu->add($icon);
             }
 
-            // Export training data.
-            if (!$model->is_static() && $model->is_trained()) {
-                $urlparams['action'] = 'exportdata';
-                $url = new \moodle_url('model.php', $urlparams);
-                $icon = new \action_menu_link_secondary($url, new \pix_icon('i/export',
-                    get_string('exporttrainingdata', 'tool_analytics')), get_string('exporttrainingdata', 'tool_analytics'));
-                $actionsmenu->add($icon);
-            }
+            // Export.
+            if (!$model->is_static()) {
 
-            // Export model.
-            if (!$model->is_static() && $model->get_indicators() && !empty($modeldata->timesplitting)) {
-                $urlparams['action'] = 'exportmodel';
-                $url = new \moodle_url('model.php', $urlparams);
-                $icon = new \action_menu_link_secondary($url, new \pix_icon('i/backup',
-                    get_string('exportmodel', 'tool_analytics')), get_string('exportmodel', 'tool_analytics'));
-                $actionsmenu->add($icon);
+                $fullysetup = $model->get_indicators() && !empty($modeldata->timesplitting);
+                $istrained = $model->is_trained();
+
+                if ($fullysetup || $istrained) {
+
+                    $url = new \moodle_url('model.php', $urlparams);
+                    // Clear the previous action param from the URL, we will set it in JS.
+                    $url->remove_params('action');
+
+                    $actionid = 'export-' . $model->get_id();
+                    $PAGE->requires->js_call_amd('tool_analytics/model', 'selectExportOptions',
+                        [$actionid, $istrained]);
+
+                    $icon = new \action_menu_link_secondary($url, new \pix_icon('i/export',
+                        get_string('export', 'tool_analytics')), get_string('export', 'tool_analytics'),
+                        ['data-action-id' => $actionid]);
+                    $actionsmenu->add($icon);
+                }
             }
 
             // Invalid analysables.
index 354dc96..dc0c5c9 100644 (file)
@@ -66,6 +66,8 @@ $string['evaluationmodecoltrainedmodel'] = 'Trained model';
 $string['evaluationmodecolconfiguration'] = 'Configuration';
 $string['evaluationmodeconfiguration'] = 'Evaluate the model configuration';
 $string['evaluationinbatches'] = 'The site contents are calculated and stored in batches. The evaluation process may be stopped at any time. The next time it is run, it will continue from the point when it was stopped.';
+$string['export'] = 'Export';
+$string['exportincludeweights'] = 'Include the weights of the trained model';
 $string['exportmodel'] = 'Export configuration';
 $string['exporttrainingdata'] = 'Export training data';
 $string['getpredictionsresultscli'] = 'Results using {$a->name} (id: {$a->id}) time-splitting method';
index a58b5d7..3cca64e 100644 (file)
@@ -235,8 +235,11 @@ switch ($action) {
         break;
 
     case 'exportmodel':
+
+        $includeweights = optional_param('includeweights', 1, PARAM_INT);
+
         $zipfilename = 'model-' . $model->get_unique_id() . '-' . microtime(false) . '.zip';
-        $zipfilepath = $model->export_model($zipfilename);
+        $zipfilepath = $model->export_model($zipfilename, $includeweights);
         send_temp_file($zipfilepath, $zipfilename);
         break;
 
diff --git a/admin/tool/analytics/templates/export_options.mustache b/admin/tool/analytics/templates/export_options.mustache
new file mode 100644 (file)
index 0000000..b7180ab
--- /dev/null
@@ -0,0 +1,57 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Moodle is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template tool_analytics/export_options
+
+    Export options.
+
+    The purpose of this template is to render the exporting options.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Example context (json):
+    {
+    }
+}}
+<div class="form-check">
+    <input class="form-check-input" type="radio" name="exportoption" id="id-mode-exportdata" value="exportdata">
+    <label class="form-check-label" for="id-mode-exportdata">{{#str}} exporttrainingdata, tool_analytics {{/str}}</label>
+</div>
+<div class="form-check">
+    <input class="form-check-input" type="radio" name="exportoption" id="id-mode-exportmodel" value="exportmodel" checked>
+    <label class="form-check-label" for="id-mode-exportmodel">{{#str}} exportmodel, tool_analytics {{/str}}</label>
+</div>
+<div class="form-check m-l-2" id="id-includeweights-container">
+  <input class="form-check-input" type="checkbox" id="id-includeweights" value="1" checked>
+  <label class="form-check-label" for="id-includeweights">{{#str}} exportincludeweights, tool_analytics {{/str}}</label>
+</div>
+
+{{#js}}
+    require(['jquery'], function($) {
+        $("input[name='exportoption']:radio").change(function() {
+            if ($(this).val() == 'exportdata') {
+                $('#id-includeweights-container').hide();
+            } else {
+                $('#id-includeweights-container').show();
+            }
+        });
+    });
+{{/js}}
index 6230b75..e7fae37 100644 (file)
@@ -1408,14 +1408,15 @@ class model {
      * Exports the model data to a zip file.
      *
      * @param string $zipfilename
+     * @param bool $includeweights Include the model weights if available
      * @return string Zip file path
      */
-    public function export_model(string $zipfilename) : string {
+    public function export_model(string $zipfilename, bool $includeweights = true) : string {
 
         \core_analytics\manager::check_can_manage_models();
 
         $modelconfig = new model_config($this);
-        return $modelconfig->export($zipfilename);
+        return $modelconfig->export($zipfilename, $includeweights);
     }
 
     /**
index 2cb32cc..75b31f1 100644 (file)
@@ -58,9 +58,10 @@ class model_config {
      * Exports a model to a zip using the provided file name.
      *
      * @param string $zipfilename
+     * @param bool $includeweights Include the model weights if available
      * @return string
      */
-    public function export(string $zipfilename) : string {
+    public function export(string $zipfilename, bool $includeweights = true) : string {
 
         if (!$this->model) {
             throw new \coding_exception('No model object provided.');
@@ -84,7 +85,7 @@ class model_config {
         $zipfiles[self::CONFIG_FILE_NAME] = $jsonfilepath;
 
         // ML backend.
-        if ($this->model->is_trained()) {
+        if ($includeweights && $this->model->is_trained()) {
             $processor = $this->model->get_predictions_processor(true);
             $outputdir = $this->model->get_output_dir(array('execution'));
             $mlbackenddir = $processor->export($this->model->get_unique_id(), $outputdir);
index 7728648..3dea376 100644 (file)
@@ -305,6 +305,10 @@ class core_analytics_prediction_testcase extends advanced_testcase {
         $zipfilename = 'model-zip-' . microtime() . '.zip';
         $zipfilepath = $model->export_model($zipfilename);
 
+        $modelconfig = new \core_analytics\model_config();
+        list($modelconfig, $mlbackend) = $modelconfig->extract_import_contents($zipfilepath);
+        $this->assertNotFalse($mlbackend);
+
         $importmodel = \core_analytics\model::import_model($zipfilepath);
         $importmodel->enable();
 
@@ -317,6 +321,13 @@ class core_analytics_prediction_testcase extends advanced_testcase {
 
         $this->assertFalse($importmodel->trained_locally());
 
+        $zipfilename = 'model-zip-' . microtime() . '.zip';
+        $zipfilepath = $model->export_model($zipfilename, false);
+
+        $modelconfig = new \core_analytics\model_config();
+        list($modelconfig, $mlbackend) = $modelconfig->extract_import_contents($zipfilepath);
+        $this->assertFalse($mlbackend);
+
         set_config('enabled_stores', '', 'tool_log');
         get_log_manager(true);
     }