Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions src/prerna/collaboration/WorkRoutineRoom.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@
import prerna.reactor.automation.run.AutomationRunStore;
import prerna.reactor.automation.utils.AutomationRuntimeUtils;

/** Opens a private discussion of one completed routine output without exposing its automation history. */
/**
* The owner's routine runs: their history, the latest runs for the sidebar, deleting one, and a private discussion of
* one completed output, without exposing the automation history behind them.
*/
public final class WorkRoutineRoom {
private static final int MAX_RUNS = 30;
// the sidebar shows the routines that ran last, each with its latest runs
Expand Down Expand Up @@ -206,14 +209,37 @@ private static String roomOf(String runId) {
return null;
}

/**
* Deletes one finished run of the owner's routine from its history. When it was the routine's latest run, Routines
* stops showing its result too. Chats opened from the run stay; they are the owner's own conversations.
*/
public static void deleteRun(Insight insight, String routineId, String runId) {
WorkRoutine.Slot slot = target(insight, routineId).slot();
String id = runId == null ? "" : runId.trim();
Map<String, Object> run = id.isEmpty() ? null : ownRun(slot, insight.getUser(), id);
if (run == null) {
throw new IllegalArgumentException("Unknown run");
}
if ("running".equals(status(run))) {
throw new IllegalArgumentException("This run is still going. Delete it once it has finished.");
}
AutomationRunStore.deleteRun(AutomationProjectService.inlineRunScope(insight.getUser()), slot.automationId(), id);
// the row keeps its start, which orders the sidebar, and the older runs stay in the history
CollaborationDbUtils.update("UPDATE " + slot.kind().table() + " SET RUN_STATUS = NULL, FINISHED_AT = NULL, "
+ "ROOM_ID = NULL, RUN_ID = NULL, SUMMARY = NULL, ERROR = NULL, ERROR_CAUSE = NULL" + slot.where()
+ " AND RUN_ID = ?", slot.args(new Object[0], id));
}

/** The run, only when it is this owner's run of this routine; the client id is never trusted alone. */
private static Map<String, Object> ownRun(WorkRoutine.Slot slot, User user, String runId) {
Map<String, Object> run = AutomationRunStore.getRunDetail(runId);
return run != null && AutomationProjectService.inlineRunScope(user).equals(run.get(AutomationConstants.PROJECT_ID))
&& slot.automationId().equals(run.get(AutomationConstants.AUTOMATION_ID)) ? run : null;
}

/** One run's result text, only when the run is this owner's run of this routine; null when it has none. */
private static String runOutput(WorkRoutine.Slot slot, User user, String runId) {
Map<String, Object> run = AutomationRunStore.getRunDetail(runId);
if (run == null || !AutomationProjectService.inlineRunScope(user).equals(run.get(AutomationConstants.PROJECT_ID))
|| !slot.automationId().equals(run.get(AutomationConstants.AUTOMATION_ID))) {
return null;
}
return output(slot, runId);
return ownRun(slot, user, runId) == null ? null : output(slot, runId);
}

private static String output(WorkRoutine.Slot slot, String runId) {
Expand Down
47 changes: 37 additions & 10 deletions src/prerna/reactor/automation/run/AutomationRunStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -272,16 +272,22 @@ private AutomationRunStore() {
PARENT_NODE_ID, ITERATION_INDEX, SOURCE_NODE_ID) \
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""";

// Child rows first, then the runs, all selected by run scope and automation.
// Child rows first, then the runs, all selected by run scope and automation, and for one run its id too.
private static final String RUNS_IN_SCOPE = "SELECT RUN_ID FROM AUTOMATION_RUNS WHERE PROJECT_ID = ? AND AUTOMATION_ID = ?";
private static final List<String> DELETE_RUNS = List.of(
"DELETE FROM AUTOMATION_RESULT_CHUNKS WHERE RESULT_ID IN (SELECT RESULT_ID FROM AUTOMATION_SAVED_RESULTS "
+ "WHERE RUN_ID IN (" + RUNS_IN_SCOPE + "))",
"DELETE FROM AUTOMATION_SAVED_RESULTS WHERE RUN_ID IN (" + RUNS_IN_SCOPE + ")",
"DELETE FROM AUTOMATION_RUN_NODE_SOURCES WHERE RUN_ID IN (" + RUNS_IN_SCOPE + ")",
"DELETE FROM AUTOMATION_NODE_OUTPUTS WHERE RUN_ID IN (" + RUNS_IN_SCOPE + ")",
"DELETE FROM AUTOMATION_RUN_WAITS WHERE RUN_ID IN (" + RUNS_IN_SCOPE + ")",
"DELETE FROM AUTOMATION_RUNS WHERE PROJECT_ID = ? AND AUTOMATION_ID = ?");
private static final List<String> DELETE_RUNS = deleteStatements("");
private static final List<String> DELETE_RUN = deleteStatements(" AND RUN_ID = ?");

private static List<String> deleteStatements(String andRun) {
String runs = RUNS_IN_SCOPE + andRun;
return List.of(
"DELETE FROM AUTOMATION_RESULT_CHUNKS WHERE RESULT_ID IN (SELECT RESULT_ID FROM AUTOMATION_SAVED_RESULTS "
+ "WHERE RUN_ID IN (" + runs + "))",
"DELETE FROM AUTOMATION_SAVED_RESULTS WHERE RUN_ID IN (" + runs + ")",
"DELETE FROM AUTOMATION_RUN_NODE_SOURCES WHERE RUN_ID IN (" + runs + ")",
"DELETE FROM AUTOMATION_NODE_OUTPUTS WHERE RUN_ID IN (" + runs + ")",
"DELETE FROM AUTOMATION_RUN_WAITS WHERE RUN_ID IN (" + runs + ")",
"DELETE FROM AUTOMATION_RUNS WHERE PROJECT_ID = ? AND AUTOMATION_ID = ?" + andRun);
}

private static final String INCREMENT_RUN_TOTAL_NODES =
"UPDATE AUTOMATION_RUNS SET TOTAL_NODES = TOTAL_NODES + ? WHERE RUN_ID = ?";
Expand Down Expand Up @@ -1135,17 +1141,38 @@ public static List<Map<String, Object>> getRunsForAutomation(String runScope, St
* @throws IllegalStateException when the rows cannot be deleted
*/
public static int deleteRuns(String runScope, String automationId) {
return delete(DELETE_RUNS, runScope, automationId, null);
}

/**
* Deletes one run of one automation in one run scope, with its source snapshot, node outputs, and waits, in one
* transaction. A run id from another scope or automation deletes nothing.
*
* @param runScope Automation project ID or inline run scope
* @param automationId logical automation stored in {@code AUTOMATION_ID}
* @param runId the run
* @return 1 when the run was deleted, else 0; 0 when the scheduler DB is unavailable
* @throws IllegalStateException when the rows cannot be deleted
*/
public static int deleteRun(String runScope, String automationId, String runId) {
return delete(DELETE_RUN, runScope, automationId, runId);
}

private static int delete(List<String> statements, String runScope, String automationId, String runId) {
IRDBMSEngine schedulerDb = getSchedulerDb();
if (schedulerDb == null) {
return 0;
}
try {
return QueryExecutionUtility.write(schedulerDb, conn -> {
int deleted = 0;
for (String sql : DELETE_RUNS) {
for (String sql : statements) {
deleted = QueryExecutionUtility.executeUpdate(conn, sql, ps -> {
ps.setString(1, runScope);
ps.setString(2, automationId);
if (runId != null) {
ps.setString(3, runId);
}
});
}
// The last statement deletes the runs themselves.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*******************************************************************************
* Copyright 2015 Defense Health Agency (DHA)
*
* If your use of this software does not include any GPLv2 components:
* Licensed 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.
* ----------------------------------------------------------------------------
* If your use of this software includes any GPLv2 components:
* This program 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 2
* of the License, or (at your option) any later version.
*
* This program 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.
*******************************************************************************/
package prerna.reactor.collaboration;

import java.util.Map;

import prerna.collaboration.WorkRoutineRoom;
import prerna.sablecc2.om.nounmeta.NounMetadata;

public class WorkDeleteRoutineRunReactor extends AbstractCollaborationReactor {
private static final String ROUTINE_ID = "routineId";
private static final String RUN_ID = "runId";

public WorkDeleteRoutineRunReactor() {
this.keysToGet = new String[] { ROUTINE_ID, RUN_ID };
this.keyRequired = new int[] { 1, 1 };
}

@Override
public NounMetadata execute() {
getUser();
WorkRoutineRoom.deleteRun(this.insight, getString(ROUTINE_ID), getString(RUN_ID));
return mapResult(Map.of("deleted", true));
}

@Override
public String getReactorDescription() {
return "Deletes one finished run of one of the signed-in user's routines from its run history; when it was the "
+ "routine's latest run, Routines stops showing its result. Chats opened from the run are kept";
}

@Override
protected String getDescriptionForKey(String key) {
if (ROUTINE_ID.equals(key)) {
return "Routine id: inbox-daily-brief, weekly-meeting-agendas, event-planning, expense-review, or the id of "
+ "one of the owner's own routines";
}
if (RUN_ID.equals(key)) {
return "The run to delete, from the routine's run history";
}
return super.getDescriptionForKey(key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,41 @@ void inlineScopeRunsCompleteAndDeleteOnlyTheirOwnRows() throws Exception {
}
}

@Test
void deletingOneRunLeavesTheAutomationsOtherRuns() throws Exception {
try (var db = new JdbcTestDatabase()) {
schema(db);
savedResultSchema(db);
db.execute(
"CREATE TABLE AUTOMATION_RUN_WAITS (WAIT_ID VARCHAR, RUN_ID VARCHAR, NODE_ID VARCHAR, WAIT_TYPE VARCHAR, AGENT_RUN_ID VARCHAR, ROOM_ID VARCHAR, RESUME_NODE_ID VARCHAR, STATUS VARCHAR, CREATED_BY VARCHAR, STARTED_AT TIMESTAMP, EXPIRES_AT TIMESTAMP, RESOLVED_AT TIMESTAMP, RESOLVED_BY VARCHAR)");
String scope = AutomationConstants.INLINE_RUN_SCOPE_PREFIX + "owner";
List<Map<String, Object>> nodes = List.of(Map.of("id", "node", "label", "Node"));
AutomationRunStore.initializeRun("old", scope, "brief", 1, "hash", "{}", Map.of(), "SCHEDULED", "user",
nodes, Map.of(), Map.of("node", "source"));
AutomationRunStore.initializeRun("new", scope, "brief", 1, "hash", "{}", Map.of(), "SCHEDULED", "user",
nodes, Map.of(), Map.of("node", "source"));
db.execute("INSERT INTO AUTOMATION_RUN_WAITS (WAIT_ID, RUN_ID, NODE_ID, STATUS) "
+ "VALUES ('wait', 'new', 'node', 'RESOLVED')",
"INSERT INTO AUTOMATION_SAVED_RESULTS (RESULT_ID, RUN_ID) VALUES ('result', 'new')",
"INSERT INTO AUTOMATION_RESULT_CHUNKS (RESULT_ID, CHUNK_INDEX) VALUES ('result', 0)");

// the run id alone is not enough: another scope or automation deletes nothing
assertEquals(0, AutomationRunStore.deleteRun(AutomationConstants.INLINE_RUN_SCOPE_PREFIX + "other",
"brief", "new"));
assertEquals(0, AutomationRunStore.deleteRun(scope, "default", "new"));
assertEquals(1, AutomationRunStore.deleteRun(scope, "brief", "new"));

assertEquals(1, db.count("AUTOMATION_RUNS"));
assertEquals("old", db.value("SELECT RUN_ID FROM AUTOMATION_RUNS"));
assertEquals(1, db.count("AUTOMATION_RUN_NODE_SOURCES"));
assertEquals(1, db.count("AUTOMATION_NODE_OUTPUTS"));
assertEquals(0, db.count("AUTOMATION_RUN_WAITS"));
assertEquals(0, db.count("AUTOMATION_SAVED_RESULTS"));
assertEquals(0, db.count("AUTOMATION_RESULT_CHUNKS"));
assertEquals(0, AutomationRunStore.deleteRun(scope, "brief", "new"));
}
}

@Test
void lookAlikeScopeGetsNoRunsFromADatabaseThatIgnoresCase() throws Exception {
String scope = AutomationConstants.INLINE_RUN_SCOPE_PREFIX + "owner";
Expand Down
Loading