diff --git a/node-packages/commons/src/util.ts b/node-packages/commons/src/util.ts
index bbc7f8ed28..704e6fcb2a 100644
--- a/node-packages/commons/src/util.ts
+++ b/node-packages/commons/src/util.ts
@@ -5,6 +5,10 @@ export const generateBuildId = function() {
return `lagoon-build-${Math.random().toString(36).substring(7)}`;
};
+export const generateTaskName = function() {
+ return `lagoon-task-${Math.random().toString(36).substring(7)}`;
+};
+
export const jsonMerge = function(a, b, prop) {
var reduced = a.filter(function(aitem) {
return !b.find(function(bitem) {
diff --git a/services/api-db/docker-entrypoint-initdb.d/00-tables.sql b/services/api-db/docker-entrypoint-initdb.d/00-tables.sql
index 3fdec7f4de..352087fa48 100644
--- a/services/api-db/docker-entrypoint-initdb.d/00-tables.sql
+++ b/services/api-db/docker-entrypoint-initdb.d/00-tables.sql
@@ -183,10 +183,11 @@ CREATE TABLE IF NOT EXISTS environment_service (
CREATE TABLE IF NOT EXISTS task (
id int NOT NULL auto_increment PRIMARY KEY,
name varchar(100) NOT NULL,
+ task_name varchar(100) NULL,
environment int NOT NULL REFERENCES environment (id),
service varchar(100) NOT NULL,
command varchar(300) NOT NULL,
- status ENUM('active', 'succeeded', 'failed') NOT NULL,
+ status ENUM('new', 'pending', 'running', 'cancelled', 'error', 'failed', 'complete', 'active', 'succeeded') NOT NULL,
created datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
started datetime NULL,
completed datetime NULL,
diff --git a/services/api-db/docker-entrypoint-initdb.d/01-migrations.sql b/services/api-db/docker-entrypoint-initdb.d/01-migrations.sql
index 38df747f68..6fd00ae9d6 100644
--- a/services/api-db/docker-entrypoint-initdb.d/01-migrations.sql
+++ b/services/api-db/docker-entrypoint-initdb.d/01-migrations.sql
@@ -1631,6 +1631,94 @@ CREATE OR REPLACE PROCEDURE
END;
$$
+CREATE OR REPLACE PROCEDURE
+ add_task_name_to_tasks()
+
+ BEGIN
+ IF NOT EXISTS (
+ SELECT NULL
+ FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE
+ table_name = 'task'
+ AND table_schema = 'infrastructure'
+ AND column_name = 'task_name'
+ ) THEN
+ ALTER TABLE `task`
+ ADD `task_name` varchar(100) NULL;
+ END IF;
+ END;
+$$
+
+CREATE OR REPLACE PROCEDURE
+ add_new_task_status_types()
+
+ BEGIN
+ IF NOT EXISTS (
+ SELECT NULL
+ FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE
+ table_name = 'task'
+ AND table_schema = 'infrastructure'
+ AND column_name = 'status'
+ AND column_type like '%''cancelled%'
+ ) THEN
+ ALTER TABLE `task`
+ MODIFY status ENUM('new', 'pending', 'running', 'cancelled', 'error', 'failed', 'complete', 'active', 'succeeded') NOT NULL;
+ END IF;
+ END;
+$$
+
+-- update any active or succeeded statuses to be running or complete
+CREATE OR REPLACE PROCEDURE
+ update_active_succeeded_tasks()
+
+ BEGIN
+ UPDATE task t
+ SET
+ t.status = 'running'
+ WHERE
+ t.task_name = 'active';
+ UPDATE task t
+ SET
+ t.status = 'complete'
+ WHERE
+ t.task_name = 'succeeded';
+ END;
+$$
+
+-- generate a taskname for tasks missing one
+CREATE OR REPLACE PROCEDURE
+ update_missing_tasknames()
+
+ BEGIN
+ UPDATE task t
+ SET
+ t.task_name = CONCAT('lagoon-task-', (SELECT LEFT(UUID(), 6)))
+ WHERE
+ t.task_name IS NULL;
+ END;
+$$
+
+-- TODO: Eventually the `active/succeeded` values should go away once `remote-controller` is updated to send the correct values
+-- CREATE OR REPLACE PROCEDURE
+-- remove_active_succeeded_task_types()
+
+-- BEGIN
+-- IF NOT EXISTS (
+-- SELECT NULL
+-- FROM INFORMATION_SCHEMA.COLUMNS
+-- WHERE
+-- table_name = 'task'
+-- AND table_schema = 'infrastructure'
+-- AND column_name = 'status'
+-- AND column_type like '%''cancelled%'
+-- ) THEN
+-- ALTER TABLE `task`
+-- MODIFY status ENUM('new', 'pending', 'running', 'cancelled', 'error', 'failed', 'complete') NOT NULL;
+-- END IF;
+-- END;
+-- $$
+
DELIMITER ;
-- If adding new procedures, add them to the bottom of this list
@@ -1715,6 +1803,9 @@ CALL change_name_index_for_advanced_task_argument();
CALL add_confirmation_text_to_advanced_task_def();
CALL add_display_name_to_advanced_task_argument();
CALL add_ecdsa_ssh_key_types();
+CALL add_new_task_status_types();
+CALL update_active_succeeded_tasks();
+CALL update_missing_tasknames();
-- Drop legacy SSH key procedures
DROP PROCEDURE IF EXISTS CreateProjectSshKey;
diff --git a/services/api/src/resolvers.js b/services/api/src/resolvers.js
index 7f27b33ebc..14ed9033f1 100644
--- a/services/api/src/resolvers.js
+++ b/services/api/src/resolvers.js
@@ -54,6 +54,7 @@ const {
const {
getTasksByEnvironmentId,
+ getTaskByTaskName,
getTaskByRemoteId,
getTaskById,
addTask,
@@ -306,6 +307,12 @@ const resolvers = {
PROBLEM: 'problem',
},
TaskStatusType: {
+ NEW: 'new',
+ PENDING: 'pending',
+ RUNNING: 'running',
+ CANCELLED: 'cancelled',
+ ERROR: 'error',
+ COMPLETE: 'complete',
ACTIVE: 'active',
SUCCEEDED: 'succeeded',
FAILED: 'failed',
@@ -432,6 +439,7 @@ const resolvers = {
userCanSshToEnvironment,
deploymentByRemoteId: getDeploymentByRemoteId,
deploymentsByBulkId: getDeploymentsByBulkId,
+ taskByTaskName: getTaskByTaskName,
taskByRemoteId: getTaskByRemoteId,
taskById: getTaskById,
advancedTaskDefinitionById,
diff --git a/services/api/src/resources/task/helpers.ts b/services/api/src/resources/task/helpers.ts
index 919e92ba4a..5cea9ac332 100644
--- a/services/api/src/resources/task/helpers.ts
+++ b/services/api/src/resources/task/helpers.ts
@@ -14,6 +14,7 @@ export const Helpers = (sqlClientPool: Pool) => ({
addTask: async ({
id,
name,
+ taskName,
status,
created,
started,
@@ -26,6 +27,7 @@ export const Helpers = (sqlClientPool: Pool) => ({
}: {
id?: number;
name: string;
+ taskName: string;
status?: string;
created?: string;
started?: string;
@@ -41,6 +43,7 @@ export const Helpers = (sqlClientPool: Pool) => ({
Sql.insertTask({
id,
name,
+ taskName,
status,
created,
started,
@@ -99,6 +102,7 @@ export const Helpers = (sqlClientPool: Pool) => ({
{
id,
name,
+ taskName,
status,
created,
started,
@@ -112,6 +116,7 @@ export const Helpers = (sqlClientPool: Pool) => ({
}: {
id?: number,
name: string,
+ taskName: string,
status?: string,
created?: string,
started?: string,
@@ -142,6 +147,7 @@ export const Helpers = (sqlClientPool: Pool) => ({
Sql.insertTask({
id,
name,
+ taskName,
status,
created,
started,
diff --git a/services/api/src/resources/task/resolvers.ts b/services/api/src/resources/task/resolvers.ts
index 7ebdf870c2..4d60eb466c 100644
--- a/services/api/src/resources/task/resolvers.ts
+++ b/services/api/src/resources/task/resolvers.ts
@@ -13,6 +13,7 @@ import { Helpers as projectHelpers } from '../project/helpers';
import { Validators as envValidators } from '../environment/validators';
import S3 from 'aws-sdk/clients/s3';
import sha1 from 'sha1';
+import { generateTaskName } from '@lagoon/commons/dist/util';
const accessKeyId = process.env.S3_FILES_ACCESS_KEY_ID || 'minio'
const secretAccessKey = process.env.S3_FILES_SECRET_ACCESS_KEY || 'minio123'
@@ -97,7 +98,7 @@ export const getTaskLog: ResolverFn = async (
export const getTasksByEnvironmentId: ResolverFn = async (
{ id: eid },
- { id: filterId, limit },
+ { id: filterId, taskName: taskName, limit },
{ sqlClientPool, hasPermission }
) => {
const environment = await environmentHelpers(
@@ -116,6 +117,10 @@ export const getTasksByEnvironmentId: ResolverFn = async (
queryBuilder = queryBuilder.andWhere('id', filterId);
}
+ if (taskName) {
+ queryBuilder = queryBuilder.andWhere('task_name', taskName);
+ }
+
if (limit) {
queryBuilder = queryBuilder.limit(limit);
}
@@ -123,6 +128,30 @@ export const getTasksByEnvironmentId: ResolverFn = async (
return query(sqlClientPool, queryBuilder.toString());
};
+export const getTaskByTaskName: ResolverFn = async (
+ root,
+ { taskName },
+ { sqlClientPool, hasPermission }
+) => {
+ const queryString = knex('task')
+ .where('task_name', '=', taskName)
+ .toString();
+
+ const rows = await query(sqlClientPool, queryString);
+ const task = R.prop(0, rows);
+
+ if (!task) {
+ return null;
+ }
+
+ const rowsPerms = await query(sqlClientPool, Sql.selectPermsForTask(task.id));
+ await hasPermission('task', 'view', {
+ project: R.path(['0', 'pid'], rowsPerms)
+ });
+
+ return task;
+};
+
export const getTaskByRemoteId: ResolverFn = async (
root,
{ id },
@@ -208,6 +237,8 @@ export const addTask: ResolverFn = async (
execute = true;
}
+ let taskName = generateTaskName()
+
userActivityLogger(`User added task '${name}'`, {
project: '',
event: 'api:addTask',
@@ -215,6 +246,7 @@ export const addTask: ResolverFn = async (
input: {
id,
name,
+ taskName,
status,
created,
started,
@@ -231,6 +263,7 @@ export const addTask: ResolverFn = async (
const taskData = await Helpers(sqlClientPool).addTask({
id,
name,
+ taskName,
status,
created,
started,
@@ -393,6 +426,7 @@ TOKEN="$(ssh -p $TASK_SSH_PORT -t lagoon@$TASK_SSH_HOST token)" && curl -sS "$TA
const taskData = await Helpers(sqlClientPool).addTask({
name: 'Drush archive-dump',
+ taskName: generateTaskName(),
environment: environmentId,
service: 'cli',
command,
@@ -441,6 +475,7 @@ TOKEN="$(ssh -p $TASK_SSH_PORT -t lagoon@$TASK_SSH_HOST token)" && curl -sS "$TA
const taskData = await Helpers(sqlClientPool).addTask({
name: 'Drush sql-dump',
+ taskName: generateTaskName(),
environment: environmentId,
service: 'cli',
command,
@@ -491,6 +526,7 @@ export const taskDrushCacheClear: ResolverFn = async (
const taskData = await Helpers(sqlClientPool).addTask({
name: 'Drush cache-clear',
+ taskName: generateTaskName(),
environment: environmentId,
service: 'cli',
command,
@@ -530,6 +566,7 @@ export const taskDrushCron: ResolverFn = async (
const taskData = await Helpers(sqlClientPool).addTask({
name: 'Drush cron',
+ taskName: generateTaskName(),
environment: environmentId,
service: 'cli',
command: `drush cron`,
@@ -601,6 +638,7 @@ export const taskDrushSqlSync: ResolverFn = async (
const taskData = await Helpers(sqlClientPool).addTask({
name: `Sync DB ${sourceEnvironment.name} -> ${destinationEnvironment.name}`,
+ taskName: generateTaskName(),
environment: destinationEnvironmentId,
service: 'cli',
command: command,
@@ -672,6 +710,7 @@ export const taskDrushRsyncFiles: ResolverFn = async (
const taskData = await Helpers(sqlClientPool).addTask({
name: `Sync files ${sourceEnvironment.name} -> ${destinationEnvironment.name}`,
+ taskName: generateTaskName(),
environment: destinationEnvironmentId,
service: 'cli',
command: command,
@@ -711,6 +750,7 @@ export const taskDrushUserLogin: ResolverFn = async (
const taskData = await Helpers(sqlClientPool).addTask({
name: 'Drush uli',
+ taskName: generateTaskName(),
environment: environmentId,
service: 'cli',
command: `drush uli`,
diff --git a/services/api/src/resources/task/sql.ts b/services/api/src/resources/task/sql.ts
index fd5358cc76..ee1c3d2a03 100644
--- a/services/api/src/resources/task/sql.ts
+++ b/services/api/src/resources/task/sql.ts
@@ -8,6 +8,7 @@ export const Sql = {
insertTask: ({
id,
name,
+ taskName,
status,
created,
started,
@@ -22,6 +23,7 @@ export const Sql = {
}: {
id: number;
name: string;
+ taskName: string,
status: string;
created: string;
started: string;
@@ -38,6 +40,7 @@ export const Sql = {
.insert({
id,
name,
+ taskName,
status,
created,
started,
diff --git a/services/api/src/resources/task/task_definition_resolvers.ts b/services/api/src/resources/task/task_definition_resolvers.ts
index 471dec54dd..1d67c17a4b 100644
--- a/services/api/src/resources/task/task_definition_resolvers.ts
+++ b/services/api/src/resources/task/task_definition_resolvers.ts
@@ -19,6 +19,7 @@ import convertDateToMYSQLDateTimeFormat from '../../util/convertDateToMYSQLDateT
import * as advancedTaskToolbox from './advancedtasktoolbox';
import { IKeycloakAuthAttributes, KeycloakUnauthorizedError } from '../../util/auth';
import { Environment } from '../../resolvers';
+import { generateTaskName } from '@lagoon/commons/dist/util';
enum AdvancedTaskDefinitionTarget {
Group,
@@ -532,6 +533,7 @@ export const invokeRegisteredTask = async (
const taskData = await Helpers(sqlClientPool).addTask({
name: task.name,
+ taskName: generateTaskName(),
environment: environment,
service: task.service,
command: taskCommand,
@@ -554,6 +556,7 @@ export const invokeRegisteredTask = async (
const advancedTaskData = await Helpers(sqlClientPool).addAdvancedTask({
name: task.name,
+ taskName: generateTaskName(),
created: undefined,
started: undefined,
completed: undefined,
diff --git a/services/api/src/typeDefs.js b/services/api/src/typeDefs.js
index 56cb9f35a9..f881c2585e 100644
--- a/services/api/src/typeDefs.js
+++ b/services/api/src/typeDefs.js
@@ -70,6 +70,12 @@ const typeDefs = gql`
ACTIVE
SUCCEEDED
FAILED
+ NEW
+ PENDING
+ RUNNING
+ CANCELLED
+ ERROR
+ COMPLETE
}
enum RestoreStatusType {
@@ -829,7 +835,7 @@ const typeDefs = gql`
monitoringUrls: String
deployments(name: String, limit: Int): [Deployment]
backups(includeDeleted: Boolean, limit: Int): [Backup]
- tasks(id: Int, limit: Int): [Task]
+ tasks(id: Int, taskName: String, limit: Int): [Task]
advancedTasks: [AdvancedTaskDefinition]
services: [EnvironmentService]
problems(severity: [ProblemSeverityRating], source: [String]): [Problem]
@@ -915,6 +921,7 @@ const typeDefs = gql`
type Task {
id: Int
name: String
+ taskName: String
status: String
created: String
started: String
@@ -930,6 +937,7 @@ const typeDefs = gql`
type AdvancedTask {
id: Int
name: String
+ taskName: String
status: String
created: String
started: String
@@ -1067,6 +1075,7 @@ const typeDefs = gql`
): Environment
deploymentByRemoteId(id: String): Deployment
deploymentsByBulkId(bulkId: String): [Deployment]
+ taskByTaskName(taskName: String): Task
taskByRemoteId(id: String): Task
taskById(id: Int): Task
"""
@@ -1401,6 +1410,7 @@ const typeDefs = gql`
input UpdateTaskPatchInput {
name: String
+ taskName: String
status: TaskStatusType
created: String
started: String
diff --git a/services/controllerhandler/src/index.ts b/services/controllerhandler/src/index.ts
index 81ee15325e..2430d28595 100644
--- a/services/controllerhandler/src/index.ts
+++ b/services/controllerhandler/src/index.ts
@@ -38,30 +38,14 @@ const updateLagoonTask = async (meta) => {
const dateOrNull = R.unless(R.isNil, convertDateFormat) as any;
let completedDate = dateOrNull(meta.endTime) as any;
- if (meta.jobStatus === 'failed') {
+ if (meta.jobStatus.toUpperCase() === 'FAILED' || meta.jobStatus.toUpperCase() === 'CANCELLED') {
completedDate = dateOrNull(meta.endTime);
}
- // transform the jobstatus into one the API knows about
- let jobStatus = 'active';
- switch (meta.jobStatus) {
- case 'pending':
- jobStatus = 'active'
- break;
- case 'running':
- jobStatus = 'active'
- break;
- case 'complete':
- jobStatus = 'succeeded'
- break;
- default:
- jobStatus = meta.jobStatus
- break;
- }
// update the actual task now
await updateTask(Number(meta.task.id), {
remoteId: meta.remoteId,
- status: jobStatus.toUpperCase(),
+ status: meta.jobStatus.toUpperCase(),
started: dateOrNull(meta.startTime),
completed: completedDate
});
diff --git a/services/ui/server.js b/services/ui/server.js
index ead7c0e707..a7a468f922 100644
--- a/services/ui/server.js
+++ b/services/ui/server.js
@@ -87,7 +87,7 @@ app
(req, res) => {
app.render(req, res, '/task', {
openshiftProjectName: req.params.environmentSlug,
- taskId: req.params.taskSlug
+ taskName: req.params.taskSlug
});
}
);
diff --git a/services/ui/src/components/Breadcrumbs/Task.js b/services/ui/src/components/Breadcrumbs/Task.js
new file mode 100644
index 0000000000..0b4311c1eb
--- /dev/null
+++ b/services/ui/src/components/Breadcrumbs/Task.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import { getLinkData } from 'components/link/Task';
+import Breadcrumb from 'components/Breadcrumbs/Breadcrumb';
+
+const TaskBreadcrumb = ({ taskName, taskSlug, environmentSlug, projectSlug }) => {
+ const linkData = getLinkData(taskSlug, environmentSlug, projectSlug);
+
+ return (
+