Some preparations for activity log.

Fixed issue #524
Table now displays horizontal scrollbar when the viewport is too narrow (typically on mobile)
This commit is contained in:
Tomas Bures 2019-02-07 14:38:32 +00:00
parent 4f408a26d5
commit e0bee9ed42
28 changed files with 353 additions and 97 deletions

View file

@ -0,0 +1,55 @@
'use strict';
async function _logActivity(typeId, data) {
// TODO
}
/*
Extra data:
campaign:
- status : CampaignStatus
list:
- subscriptionId
- subscriptionStatus : SubscriptionStatus
- fieldId
- segmentId
- importId
- importStatus : ImportStatus
*/
async function logEntityActivity(entityTypeId, activityType, entityId, extraData = {}) {
const data = {
...extraData,
type: activityType,
entity: entityId
};
await _logActivity(entityTypeId, data);
}
async function logCampaignTrackerActivity(activityType, campaignId, listId, subscriptionId, extraData = {}) {
const data = {
...extraData,
type: activityType,
campaign: campaignId,
list: listId,
subscription: subscriptionId
};
await _logActivity('campaign_tracker', data);
}
async function logBlacklistActivity(activityType, email) {
const data = {
...extraData,
type: activityType,
email
};
await _logActivity('blacklist', data);
}
module.exports.logEntityActivity = logEntityActivity;
module.exports.logBlacklistActivity = logBlacklistActivity;
module.exports.logCampaignTrackerActivity = logCampaignTrackerActivity;

View file

@ -5,6 +5,8 @@ const fork = require('child_process').fork;
const log = require('./log');
const path = require('path');
const {ImportStatus, RunStatus} = require('../../shared/imports');
const {ListActivityType} = require('../../shared/activity-log');
const activityLog = require('./activity-log');
let messageTid = 0;
let importerProcess;
@ -18,11 +20,17 @@ function spawn(callback) {
log.verbose('Importer', 'Spawning importer process');
knex.transaction(async tx => {
await tx('imports').where('status', ImportStatus.PREP_RUNNING).update({status: ImportStatus.PREP_SCHEDULED});
await tx('imports').where('status', ImportStatus.PREP_STOPPING).update({status: ImportStatus.PREP_FAILED});
const updateStatus = async (fromStatus, toStatus) => {
for (const impt of await tx('imports').where('status', fromStatus).select(['id', 'list'])) {
await tx('imports').where('id', impt.id).update({status: toStatus});
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: toStatus});
}
}
await tx('imports').where('status', ImportStatus.RUN_RUNNING).update({status: ImportStatus.RUN_SCHEDULED});
await tx('imports').where('status', ImportStatus.RUN_STOPPING).update({status: ImportStatus.RUN_FAILED});
await updateStatus(ImportStatus.PREP_RUNNING, ImportStatus.PREP_SCHEDULED);
await updateStatus(ImportStatus.PREP_STOPPING, ImportStatus.PREP_FAILED);
await updateStatus(ImportStatus.RUN_RUNNING, ImportStatus.RUN_SCHEDULED);
await updateStatus(ImportStatus.RUN_STOPPING, ImportStatus.RUN_FAILED);
await tx('import_runs').where('status', RunStatus.RUNNING).update({status: RunStatus.SCHEDULED});
await tx('import_runs').where('status', RunStatus.STOPPING).update({status: RunStatus.FAILED});

View file

@ -6,6 +6,10 @@ const shares = require('./shares');
const tools = require('../lib/tools');
const { enforce } = require('../lib/helpers');
const {BlacklistActivityType} = require('../../shared/activity-log');
const activityLog = require('../lib/activity-log');
async function listDTAjax(context, params) {
shares.enforceGlobalPermission(context, 'manageBlacklist');
@ -44,14 +48,21 @@ async function add(context, email) {
if (!existing) {
await tx('blacklist').insert({email});
}
await activityLog.logBlacklistActivity(BlacklistActivityType.ADD, email);
});
}
async function remove(context, email) {
enforce(email, 'Email has to be set');
shares.enforceGlobalPermission(context, 'manageBlacklist');
await knex('blacklist').where('email', email).del();
return await knex.transaction(async tx => {
shares.enforceGlobalPermission(context, 'manageBlacklist');
await tx('blacklist').where('email', email).del();
await activityLog.logBlacklistActivity(BlacklistActivityType.REMOVE, email);
});
}
async function isBlacklisted(email) {

View file

@ -21,6 +21,9 @@ const {LinkId} = require('./links');
const feedcheck = require('../lib/feedcheck');
const contextHelpers = require('../lib/context-helpers');
const {EntityActivityType, CampaignActivityType} = require('../../shared/activity-log');
const activityLog = require('../lib/activity-log');
const allowedKeysCommon = ['name', 'description', 'segment', 'namespace',
'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject_override', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url'];
@ -533,6 +536,8 @@ async function _createTx(tx, context, entity, content) {
}).where('id', id);
}
await activityLog.logEntityActivity('campaign', EntityActivityType.CREATE, id, {status: filteredEntity.status});
return id;
});
}
@ -591,6 +596,8 @@ async function updateWithConsistencyCheck(context, entity, content) {
await tx('campaigns').where('id', entity.id).update(filteredEntity);
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'campaign', entityId: entity.id });
await activityLog.logEntityActivity('campaign', EntityActivityType.UPDATE, entity.id, {status: filteredEntity.status});
});
}
@ -628,6 +635,8 @@ async function _removeTx(tx, context, id, existing = null) {
.del();
await tx('campaigns').where('id', id).del();
await activityLog.logEntityActivity('campaign', EntityActivityType.REMOVE, id);
}
@ -863,6 +872,8 @@ async function _changeStatus(context, campaignId, permittedCurrentStates, newSta
status: newState,
scheduled
});
await activityLog.logEntityActivity('campaign', CampaignActivityType.STATUS_CHANGE, campaignId, {status: newState});
});
senders.scheduleCheck();

View file

@ -16,6 +16,8 @@ const { cleanupFromPost } = require('../lib/helpers');
const Handlebars = require('handlebars');
const { getTrustedUrl, getSandboxUrl, getPublicUrl } = require('../lib/urls');
const { getMergeTagsForBases } = require('../../shared/templates');
const {ListActivityType} = require('../../shared/activity-log');
const activityLog = require('../lib/activity-log');
const allowedKeysCreate = new Set(['name', 'key', 'default_value', 'type', 'group', 'settings']);
@ -565,6 +567,8 @@ async function createTx(tx, context, listId, entity) {
await knex.schema.raw('ALTER TABLE `subscription__' + listId + '` ADD `source_' + columnName +'` int(11) DEFAULT NULL');
}
await activityLog.logEntityActivity('list', ListActivityType.CREATE_FIELD, listId, {fieldId: id});
return id;
}
@ -594,6 +598,8 @@ async function updateWithConsistencyCheck(context, listId, entity) {
await tx('custom_fields').where({list: listId, id: entity.id}).update(filterObject(entity, allowedKeysUpdate));
await _sortIn(tx, listId, entity.id, entity.orderListBefore, entity.orderSubscribeBefore, entity.orderManageBefore);
await activityLog.logEntityActivity('list', ListActivityType.UPDATE_FIELD, listId, {fieldId: entity.id});
});
}
@ -620,6 +626,8 @@ async function removeTx(tx, context, listId, id) {
await segments.removeRulesByColumnTx(tx, context, listId, existing.column);
}
await activityLog.logEntityActivity('list', ListActivityType.REMOVE_FIELD, listId, {fieldId: id});
}
async function remove(context, listId, id) {

View file

@ -10,6 +10,8 @@ const {ImportSource, MappingType, ImportStatus, RunStatus, prepFinished, prepFin
const fs = require('fs-extra-promise');
const path = require('path');
const importer = require('../lib/importer');
const {ListActivityType} = require('../../shared/activity-log');
const activityLog = require('../lib/activity-log');
const files = require('./files');
const filesDir = path.join(files.filesDir, 'imports');
@ -117,6 +119,8 @@ async function create(context, listId, entity, files) {
const ids = await tx('imports').insert(filteredEntity);
const id = ids[0];
await activityLog.logEntityActivity('list', ListActivityType.CREATE_IMPORT, listId, {importId: id, importStatus: entity.status});
return id;
});
@ -148,6 +152,8 @@ async function updateWithConsistencyCheck(context, listId, entity) {
filteredEntity.mapping = JSON.stringify(filteredEntity.mapping);
await tx('imports').where({list: listId, id: entity.id}).update(filteredEntity);
await activityLog.logEntityActivity('list', ListActivityType.UPDATE_IMPORT, listId, {importId: entity.id, importStatus: entity.status});
});
}
@ -170,6 +176,8 @@ async function removeTx(tx, context, listId, id) {
await tx('import_failed').whereIn('run', function() {this.from('import_runs').select('id').where('import', id)}).del();
await tx('import_runs').where('import', id).del();
await tx('imports').where({list: listId, id}).del();
await activityLog.logEntityActivity('list', ListActivityType.REMOVE_IMPORT, listId, {importId: id});
}
async function remove(context, listId, id) {
@ -208,6 +216,8 @@ async function start(context, listId, id) {
status: RunStatus.SCHEDULED,
mapping: entity.mapping
});
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, listId, {importId: id, importStatus: ImportStatus.RUN_SCHEDULED});
});
importer.scheduleCheck();
@ -234,6 +244,8 @@ async function stop(context, listId, id) {
await tx('import_runs').where('import', id).whereIn('status', [RunStatus.SCHEDULED, RunStatus.RUNNING]).update({
status: RunStatus.STOPPING
});
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, listId, {importId: id, importStatus: ImportStatus.RUN_STOPPING});
});
importer.scheduleCheck();

View file

@ -14,6 +14,9 @@ const imports = require('./imports');
const entitySettings = require('../lib/entity-settings');
const dependencyHelpers = require('../lib/dependency-helpers');
const {EntityActivityType} = require('../../shared/activity-log');
const activityLog = require('../lib/activity-log');
const {UnsubscriptionMode, FieldWizard} = require('../../shared/lists');
const allowedKeys = new Set(['name', 'description', 'default_form', 'public_subscribe', 'unsubscription_mode', 'contact_email', 'homepage', 'namespace', 'to_name', 'listunsubscribe_disabled', 'send_configuration']);
@ -196,6 +199,8 @@ async function create(context, entity) {
await fields.createTx(tx, context, id, fld);
}
await activityLog.logEntityActivity('list', EntityActivityType.CREATE, id);
return id;
});
}
@ -221,6 +226,8 @@ async function updateWithConsistencyCheck(context, entity) {
await tx('lists').where('id', entity.id).update(filterObject(entity, allowedKeys));
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'list', entityId: entity.id });
await activityLog.logEntityActivity('list', EntityActivityType.UPDATE, entity.id);
});
}
@ -244,6 +251,8 @@ async function remove(context, id) {
await tx('lists').where('id', id).del();
await knex.schema.dropTableIfExists('subscription__' + id);
await activityLog.logEntityActivity('list', EntityActivityType.REMOVE, id);
});
}

View file

@ -10,6 +10,8 @@ const moment = require('moment');
const fields = require('./fields');
const subscriptions = require('./subscriptions');
const dependencyHelpers = require('../lib/dependency-helpers');
const {ListActivityType} = require('../../shared/activity-log');
const activityLog = require('../lib/activity-log');
const allowedKeys = new Set(['name', 'settings']);
@ -304,6 +306,8 @@ async function create(context, listId, entity) {
const ids = await tx('segments').insert(filteredEntity);
const id = ids[0];
await activityLog.logEntityActivity('list', ListActivityType.CREATE_SEGMENT, listId, {segmentId: id});
return id;
});
}
@ -327,6 +331,8 @@ async function updateWithConsistencyCheck(context, listId, entity) {
await _validateAndPreprocess(tx, listId, entity, false);
await tx('segments').where({list: listId, id: entity.id}).update(filterObject(entity, allowedKeys));
await activityLog.logEntityActivity('list', ListActivityType.UPDATE_SEGMENT, listId, {segmentId: entity.id});
});
}
@ -346,6 +352,8 @@ async function removeTx(tx, context, listId, id) {
// The listId "where" is here to prevent deleting segment of a list for which a user does not have permission
await tx('segments').where({list: listId, id}).del();
await activityLog.logEntityActivity('list', ListActivityType.REMOVE_SEGMENT, listId, {segmentId: id});
}
async function remove(context, listId, id) {

View file

@ -15,6 +15,8 @@ const contextHelpers = require('../lib/context-helpers');
const tools = require('../lib/tools');
const shares = require('../models/shares');
const { tLog } = require('../lib/translate');
const {ListActivityType} = require('../../shared/activity-log');
const activityLog = require('../lib/activity-log');
const csvparse = require('csv-parse');
@ -41,6 +43,8 @@ function prepareCsv(impt) {
error: msg + '\n' + err.message
});
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.PREP_FAILED});
await fsExtra.removeAsync(filePath);
};
@ -56,6 +60,8 @@ function prepareCsv(impt) {
error: null
});
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.PREP_FINISHED});
await fsExtra.removeAsync(filePath);
};
@ -263,12 +269,16 @@ async function _execImportRun(impt, handlers) {
status: ImportStatus.RUN_FINISHED
});
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.RUN_FINISHED});
} catch (err) {
await knex('imports').where('id', impt.id).update({
last_run: new Date(),
error: err.message,
status: ImportStatus.RUN_FAILED
});
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.PREP_FAILED});
}
}
@ -361,14 +371,20 @@ async function getTask() {
if (impt.source === ImportSource.CSV_FILE && impt.status === ImportStatus.PREP_SCHEDULED) {
await tx('imports').where('id', impt.id).update('status', ImportStatus.PREP_RUNNING);
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.PREP_RUNNING});
return () => prepareCsv(impt);
} else if (impt.status === ImportStatus.RUN_SCHEDULED && impt.mapping_type === MappingType.BASIC_SUBSCRIBE) {
await tx('imports').where('id', impt.id).update('status', ImportStatus.RUN_RUNNING);
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.RUN_RUNNING});
return () => basicSubscribe(impt);
} else if (impt.status === ImportStatus.RUN_SCHEDULED && impt.mapping_type === MappingType.BASIC_UNSUBSCRIBE) {
await tx('imports').where('id', impt.id).update('status', ImportStatus.RUN_RUNNING);
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.RUN_RUNNING});
return () => basicUnsubscribe(impt);
}

View file

@ -9,6 +9,9 @@ const {CampaignStatus, CampaignType} = require('../../shared/campaigns');
const { enforce } = require('../lib/helpers');
const campaigns = require('../models/campaigns');
const builtinZoneMta = require('../lib/builtin-zone-mta');
const {CampaignActivityType} = require('../../shared/activity-log');
const activityLog = require('../lib/activity-log');
let messageTid = 0;
const workerProcesses = new Map();
@ -127,6 +130,8 @@ async function processCampaign(campaignId) {
}
await knex('campaigns').where('id', campaignId).update({status: CampaignStatus.FINISHED});
await activityLog.logEntityActivity('campaign', CampaignActivityType.STATUS_CHANGE, campaignId, {status: CampaignStatus.FINISHED});
messageQueue.delete(campaignId);
}
@ -214,6 +219,7 @@ async function scheduleCampaigns() {
if (scheduledCampaign) {
await tx('campaigns').where('id', scheduledCampaign.id).update({status: CampaignStatus.SENDING});
await activityLog.logEntityActivity('campaign', CampaignActivityType.STATUS_CHANGE, scheduledCampaign.id, {status: CampaignStatus.SENDING});
campaignId = scheduledCampaign.id;
}
});