Эх сурвалжийг харах

HUE-9109 [editor] Switch to index-based tracking of executables in editor v2

With this change the result is kept after editing.
Johan Ahlen 6 жил өмнө
parent
commit
9496dee329

+ 2 - 48
desktop/core/src/desktop/js/apps/notebook2/execution/executor.js

@@ -14,8 +14,8 @@
 // See the License for the specific language governing permissions and
 // See the License for the specific language governing permissions and
 // limitations under the License.
 // limitations under the License.
 
 
-import SqlExecutable from 'apps/notebook2/execution/sqlExecutable';
 import sessionManager from 'apps/notebook2/execution/sessionManager';
 import sessionManager from 'apps/notebook2/execution/sessionManager';
+import { syncExecutables } from 'apps/notebook2/execution/utils';
 
 
 // TODO: Remove, debug var
 // TODO: Remove, debug var
 window.sessionManager = sessionManager;
 window.sessionManager = sessionManager;
@@ -44,52 +44,6 @@ class Executor {
     this.snippet = options.snippet;
     this.snippet = options.snippet;
   }
   }
 
 
-  getExecutables(statementDetails) {
-    const allExecutablesIndex = {};
-    this.executables.forEach(executable => {
-      allExecutablesIndex[executable.getKey()] = executable;
-    });
-
-    const selectedExecutables = [];
-    let activeDatabase = this.database();
-    let currentSelectedIndex = 0;
-    const newExecutables = statementDetails.precedingStatements
-      .concat(statementDetails.activeStatement, statementDetails.followingStatements)
-      .map(parsedStatement => {
-        if (/USE/i.test(parsedStatement.firstToken)) {
-          const dbMatch = parsedStatement.statement.match(/use\s+([^;]+)/i);
-          if (dbMatch) {
-            activeDatabase = dbMatch[1];
-          }
-        }
-        let executable = new SqlExecutable({
-          parsedStatement: parsedStatement,
-          database: activeDatabase,
-          executor: this
-        });
-        if (allExecutablesIndex[executable.getKey()]) {
-          executable = allExecutablesIndex[executable.getKey()];
-          executable.parsedStatement = parsedStatement;
-          delete allExecutablesIndex[executable.getKey()];
-        }
-        if (
-          currentSelectedIndex < statementDetails.selectedStatements.length &&
-          parsedStatement === statementDetails.selectedStatements[currentSelectedIndex]
-        ) {
-          selectedExecutables.push(executable);
-          currentSelectedIndex++;
-        }
-        return executable;
-      });
-
-    const lostExecutables = Object.keys(allExecutablesIndex).map(key => allExecutablesIndex[key]);
-    return {
-      all: newExecutables,
-      lost: lostExecutables,
-      selected: selectedExecutables
-    };
-  }
-
   toJs() {
   toJs() {
     return {
     return {
       executables: this.executables.map(executable => executable.toJs())
       executables: this.executables.map(executable => executable.toJs())
@@ -97,7 +51,7 @@ class Executor {
   }
   }
 
 
   update(statementDetails, beforeExecute) {
   update(statementDetails, beforeExecute) {
-    const executables = this.getExecutables(statementDetails);
+    const executables = syncExecutables(this, statementDetails);
 
 
     // Cancel any "lost" executables and any batch chain it's part of
     // Cancel any "lost" executables and any batch chain it's part of
     executables.lost.forEach(lostExecutable => {
     executables.lost.forEach(lostExecutable => {

+ 76 - 0
desktop/core/src/desktop/js/apps/notebook2/execution/utils.js

@@ -0,0 +1,76 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SqlExecutable from 'apps/notebook2/execution/sqlExecutable';
+
+export const syncExecutables = (executor, statementDetails) => {
+  const allNewStatements = statementDetails.precedingStatements.concat(
+    statementDetails.activeStatement,
+    statementDetails.followingStatements
+  );
+
+  const existingExecutables = executor.executables.concat();
+
+  const result = {
+    all: [],
+    edited: [],
+    lost: [],
+    selected: []
+  };
+
+  let activeDatabase = executor.database();
+  let currentSelectedIndex = 0;
+  allNewStatements.forEach((parsedStatement, index) => {
+    if (/USE/i.test(parsedStatement.firstToken)) {
+      const dbMatch = parsedStatement.statement.match(/use\s+([^;]+)/i);
+      if (dbMatch) {
+        activeDatabase = dbMatch[1];
+      }
+    }
+
+    let executable = existingExecutables[index];
+    if (executable) {
+      const edited =
+        executable.database !== activeDatabase ||
+        parsedStatement.statement !== executable.parsedStatement.statement;
+      existingExecutables[index] = undefined; // undefined = not lost below
+      executable.database = activeDatabase;
+      executable.parsedStatement = parsedStatement;
+      if (edited) {
+        result.edited.push(executable);
+      }
+    } else {
+      executable = new SqlExecutable({
+        parsedStatement: parsedStatement,
+        database: activeDatabase,
+        executor: executor
+      });
+    }
+    result.all.push(executable);
+
+    if (
+      currentSelectedIndex < statementDetails.selectedStatements.length &&
+      parsedStatement === statementDetails.selectedStatements[currentSelectedIndex]
+    ) {
+      result.selected.push(executable);
+      currentSelectedIndex++;
+    }
+  });
+
+  result.lost = existingExecutables.filter(executable => typeof executable !== 'undefined');
+
+  return result;
+};

+ 198 - 0
desktop/core/src/desktop/js/apps/notebook2/execution/utils.test.js

@@ -0,0 +1,198 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { syncExecutables } from 'apps/notebook2/execution/utils';
+import sqlStatementsParser from 'parse/sqlStatementsParser';
+
+describe('utils.js', () => {
+  it('create new when no executables present', () => {
+    const executor = {
+      database: () => 'someDb',
+      executables: []
+    };
+    const statements = sqlStatementsParser.parse('SELECT 1;');
+    const result = syncExecutables(executor, {
+      precedingStatements: [],
+      activeStatement: statements[0],
+      followingStatements: [],
+      selectedStatements: [statements[0]]
+    });
+
+    expect(result.edited.length).toEqual(0);
+    expect(result.lost.length).toEqual(0);
+    expect(result.all.length).toEqual(1);
+    expect(result.selected.length).toEqual(1);
+    expect(result.all[0].database).toEqual('someDb');
+    expect(result.all[0].parsedStatement).toEqual(statements[0]);
+  });
+
+  it('should reuse existing executables when nothing has changed', () => {
+    const statements = sqlStatementsParser.parse('SELECT 1;');
+    const executor = {
+      database: () => 'someDb',
+      executables: [
+        {
+          database: 'someDb',
+          parsedStatement: JSON.parse(JSON.stringify(statements[0]))
+        }
+      ]
+    };
+    const result = syncExecutables(executor, {
+      precedingStatements: [],
+      activeStatement: statements[0],
+      followingStatements: [],
+      selectedStatements: [statements[0]]
+    });
+
+    expect(result.edited.length).toEqual(0);
+    expect(result.lost.length).toEqual(0);
+    expect(result.all.length).toEqual(1);
+    expect(result.selected.length).toEqual(1);
+    expect(result.all[0]).toEqual(executor.executables[0]);
+    expect(result.all[0].parsedStatement).toEqual(statements[0]);
+  });
+
+  it('should mark existing executables as edited when the database has changed', () => {
+    const executor = {
+      database: () => 'someOtherDb',
+      executables: [
+        {
+          database: 'someDb',
+          parsedStatement: sqlStatementsParser.parse('SELECT 1;')[0]
+        }
+      ]
+    };
+    const newStatements = sqlStatementsParser.parse('SELECT 2;');
+    const result = syncExecutables(executor, {
+      precedingStatements: [],
+      activeStatement: newStatements[0],
+      followingStatements: [],
+      selectedStatements: [newStatements[0]]
+    });
+
+    expect(executor.executables[0].database).toEqual('someOtherDb');
+    expect(result.edited.length).toEqual(1);
+    expect(result.lost.length).toEqual(0);
+    expect(result.all.length).toEqual(1);
+    expect(result.selected.length).toEqual(1);
+    expect(result.edited[0]).toEqual(executor.executables[0]);
+    expect(result.edited[0].parsedStatement).toEqual(newStatements[0]);
+  });
+
+  it('should mark existing executables as edited when same index and the statement has changed', () => {
+    const executor = {
+      database: () => 'someDb',
+      executables: [
+        {
+          database: 'someDb',
+          parsedStatement: sqlStatementsParser.parse('SELECT 1;')[0]
+        }
+      ]
+    };
+    const newStatements = sqlStatementsParser.parse('SELECT 2;');
+    const result = syncExecutables(executor, {
+      precedingStatements: [],
+      activeStatement: newStatements[0],
+      followingStatements: [],
+      selectedStatements: [newStatements[0]]
+    });
+
+    expect(result.edited.length).toEqual(1);
+    expect(result.lost.length).toEqual(0);
+    expect(result.all.length).toEqual(1);
+    expect(result.selected.length).toEqual(1);
+    expect(result.edited[0]).toEqual(executor.executables[0]);
+    expect(result.edited[0].parsedStatement).toEqual(newStatements[0]);
+  });
+
+  it('should not mark existing executables as edited when nothing has changed', () => {
+    const executor = {
+      database: () => 'someDb',
+      executables: [
+        {
+          database: 'someDb',
+          parsedStatement: sqlStatementsParser.parse('SELECT 1;')[0]
+        }
+      ]
+    };
+    const newStatements = sqlStatementsParser.parse('SELECT 1;');
+    const result = syncExecutables(executor, {
+      precedingStatements: [],
+      activeStatement: newStatements[0],
+      followingStatements: [],
+      selectedStatements: [newStatements[0]]
+    });
+
+    expect(result.edited.length).toEqual(0);
+    expect(result.lost.length).toEqual(0);
+    expect(result.all.length).toEqual(1);
+    expect(result.selected.length).toEqual(1);
+  });
+
+  it('should add executables when a statement is added', () => {
+    const executor = {
+      database: () => 'someDb',
+      executables: [
+        {
+          database: 'someDb',
+          parsedStatement: sqlStatementsParser.parse('SELECT 1;')[0]
+        }
+      ]
+    };
+    const newStatements = sqlStatementsParser.parse('SELECT 1;SELECT 2');
+    const result = syncExecutables(executor, {
+      precedingStatements: [newStatements[0]],
+      activeStatement: newStatements[1],
+      followingStatements: [],
+      selectedStatements: [newStatements[1]]
+    });
+
+    expect(result.edited.length).toEqual(0);
+    expect(result.lost.length).toEqual(0);
+    expect(result.all.length).toEqual(2);
+    expect(result.selected.length).toEqual(1);
+  });
+
+  it('should mark removed statements as lost', () => {
+    const initialStatements = sqlStatementsParser.parse('SELECT 1;SELECT 2;');
+    const executor = {
+      database: () => 'someDb',
+      executables: [
+        {
+          database: 'someDb',
+          parsedStatement: initialStatements[0]
+        },
+        {
+          database: 'someDb',
+          parsedStatement: initialStatements[1]
+        }
+      ]
+    };
+    const newStatements = sqlStatementsParser.parse('SELECT 1;');
+    const result = syncExecutables(executor, {
+      precedingStatements: [],
+      activeStatement: newStatements[0],
+      followingStatements: [],
+      selectedStatements: [newStatements[0]]
+    });
+
+    expect(result.edited.length).toEqual(0);
+    expect(result.lost.length).toEqual(1);
+    expect(result.all.length).toEqual(1);
+    expect(result.selected.length).toEqual(1);
+    expect(result.lost[0].parsedStatement.statement).toEqual('SELECT 2;');
+  });
+});