celerichain-ember-cor
Version:
Provides all the core services, decorators and utilities for building a Fleetbase extension for the Console.
355 lines (315 loc) • 13 kB
JavaScript
import Service from '@ember/service';
import { inject as service } from '@ember/service';
import { action, get } from '@ember/object';
import { isArray } from '@ember/array';
import { dasherize } from '@ember/string';
import { later } from '@ember/runloop';
import { pluralize } from 'ember-inflector';
import { format as formatDate } from 'date-fns';
import getModelName from '../utils/get-model-name';
import getWithDefault from '../utils/get-with-default';
import humanize from '../utils/humanize';
import first from '../utils/first';
export default class CrudService extends Service {
/**
* Inject the `fetch` service
*
* @var {Service}
*/
fetch;
/**
* Inject the `modalsManager` service
*
* @var {Service}
*/
modalsManager;
/**
* Inject the `notifications` service
*
* @var {Service}
*/
notifications;
/**
* Inject the `store` service
*
* @var {Service}
*/
store;
/**
* @service currentUser
*/
currentUser;
/**
* Generic deletion modal with options
*
* @param {Model} model
* @param {Object} options
* @void
*/
delete(model, options = {}) {
const modelName = getModelName(model, get(options, 'modelName'), { humanize: true, capitalizeWords: true });
this.modalsManager.confirm({
title: `Are you sure to delete this ${modelName}?`,
args: ['model'],
model,
confirm: (modal) => {
if (typeof options.onConfirm === 'function') {
options.onConfirm(model);
}
modal.startLoading();
return model
.destroyRecord()
.then((model) => {
this.notifications.success(options.successNotification || `${model.name ? modelName + " '" + model.name + "'" : "'" + modelName + "'"} has been deleted.`);
if (typeof options.onSuccess === 'function') {
options.onSuccess(model);
}
})
.catch((error) => {
this.notifications.serverError(error);
if (typeof options.onError === 'function') {
options.onError(error, model);
}
})
.finally(() => {
if (typeof options.callback === 'function') {
options.callback(model);
}
});
},
...options,
});
}
/**
* Generic deletion modal with options
*
* @param {Array} selected an array of selected models for deletion
* @param {Object} options
* @void
*/
bulkDelete(selected, options = {}) {
if (!isArray(selected) || selected.length === 0) {
return;
}
const firstModel = first(selected);
const modelName = getModelName(firstModel, get(options, 'modelName'), { humanize: true, capitalizeWords: true });
// make sure all are the same type
selected = selected.filter((m) => getModelName(m) === getModelName(firstModel));
return this.bulkAction('delete', selected, {
acceptButtonScheme: 'danger',
acceptButtonIcon: 'trash',
actionPath: `${dasherize(pluralize(modelName))}/bulk-delete`,
actionMethod: `DELETE`,
modelName,
...options,
});
}
/**
* Generic bulk action on multiple models modal with options
*
* @param {Array} selected an array of selected models for deletion
* @param {Object} options
* @void
*/
bulkAction(verb, selected, options = {}) {
if (!isArray(selected) || selected.length === 0) {
return;
}
const firstModel = first(selected);
const modelName = getModelName(firstModel, get(options, 'modelName'), { humanize: true, capitalizeWords: true });
const count = selected.length;
const actionMethod = (typeof options.actionMethod === 'string' ? options.actionMethod : `POST`).toLowerCase();
const fetchParams = getWithDefault(options, 'fetchParams', {});
const fetchOptions = getWithDefault(options, 'fetchOptions', {});
this.modalsManager.show('modals/bulk-action-model', {
title: `Bulk ${verb} ${pluralize(modelName)}`,
acceptButtonText: humanize(verb),
args: ['selected'],
modelNamePath: 'name',
verb,
selected,
count,
modelName,
remove: (model) => {
selected.removeObject(model);
this.modalsManager.setOption('selected', selected);
},
confirm: (modal) => {
const selected = modal.getOption('selected');
if (typeof options.onConfirm === 'function') {
options.onConfirm(selected);
}
modal.startLoading();
return this.fetch[actionMethod](
options.actionPath,
{
ids: selected.map((model) => model.id),
...fetchParams,
},
fetchOptions
)
.then((response) => {
this.notifications.success(response.message ?? options.successNotification ?? `${count} ${pluralize(modelName, count)} were updated successfully.`);
if (typeof options.onSuccess === 'function') {
options.onSuccess(selected);
}
})
.catch((error) => {
this.notifications.serverError(error);
if (typeof options.onError === 'function') {
options.onError(error, selected);
}
})
.finally(() => {
if (typeof options.callback === 'function') {
options.callback(selected);
}
});
},
...options,
});
}
/**
* Toggles dialog to export resource data
*
* @void
*/
export(modelName, options = {}) {
// always lowercase modelname
modelName = modelName.toLowerCase();
// set the model uri endpoint
const modelEndpoint = dasherize(pluralize(modelName));
const exportParams = options.params ?? {};
this.modalsManager.show('modals/export-form', {
title: `Export ${pluralize(modelName)}`,
acceptButtonText: 'Download',
modalClass: 'modal-sm',
format: 'xlsx',
formatOptions: ['csv', 'xlsx', 'xls', 'html', 'pdf'],
setFormat: ({ target }) => {
this.modalsManager.setOption('format', target.value || null);
},
confirm: (modal, done) => {
const format = modal.getOption('format') ?? 'xlsx';
modal.startLoading();
return this.fetch
.download(
`${modelEndpoint}/export`,
{
format,
...exportParams,
},
{
method: 'POST',
fileName: `${modelEndpoint}-${formatDate(new Date(), 'yyyy-MM-dd-HH:mm')}.${format}`,
}
)
.then(() => {
later(
this,
() => {
return done();
},
600
);
})
.catch((error) => {
modal.stopLoading();
this.notifications.serverError(error, 'Unable to download API credentials export.');
});
},
...options,
});
}
/**
* Prompts a spreadsheet upload for an import process.
*
* @param {String} modelName
* @param {Object} [options={}]
* @memberof CrudService
*/
import(modelName, options = {}) {
// always lowercase modelname
modelName = modelName.toLowerCase();
// set the model uri endpoint
const modelEndpoint = dasherize(pluralize(modelName));
// function to check if queue is empty
const checkQueue = () => {
const uploadQueue = this.modalsManager.getOption('uploadQueue');
if (uploadQueue.length) {
this.modalsManager.setOption('acceptButtonDisabled', false);
} else {
this.modalsManager.setOption('acceptButtonDisabled', true);
}
};
this.modalsManager.show('modals/import-form', {
title: `Import ${pluralize(modelName)} with spreadsheets`,
acceptButtonText: 'Start Import',
acceptButtonScheme: 'magic',
acceptButtonIcon: 'upload',
acceptButtonDisabled: true,
isProcessing: false,
uploadQueue: [],
fileQueueColumns: [
{ name: 'Type', valuePath: 'extension', key: 'type' },
{ name: 'File Name', valuePath: 'name', key: 'fileName' },
{ name: 'File Size', valuePath: 'size', key: 'fileSize' },
{ name: 'Upload Date', valuePath: 'file.lastModifiedDate', key: 'uploadDate' },
{ name: '', valuePath: '', key: 'delete' },
],
acceptedFileTypes: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'text/csv'],
queueFile: (file) => {
const uploadQueue = this.modalsManager.getOption('uploadQueue');
uploadQueue.pushObject(file);
checkQueue();
},
removeFile: (file) => {
const { queue } = file;
const uploadQueue = this.modalsManager.getOption('uploadQueue');
uploadQueue.removeObject(file);
queue.remove(file);
checkQueue();
},
confirm: async (modal) => {
const uploadQueue = this.modalsManager.getOption('uploadQueue');
const uploadedFiles = [];
const uploadTask = (file) => {
return new Promise((resolve) => {
this.fetch.uploadFile.perform(
file,
{
path: `uploads/import-sources/${this.currentUser.companyId}/${modelEndpoint}`,
type: 'import-source',
},
(uploadedFile) => {
uploadedFiles.pushObject(uploadedFile);
resolve(uploadedFile);
}
);
});
};
if (!uploadQueue.length) {
return this.notifications.warning('No spreadsheets uploaded for import to process.');
}
modal.startLoading();
modal.setOption('acceptButtonText', 'Uploading...');
for (let i = 0; i < uploadQueue.length; i++) {
const file = uploadQueue.objectAt(i);
await uploadTask(file);
}
this.modalsManager.setOption('acceptButtonText', 'Processing...');
this.modalsManager.setOption('isProcessing', true);
const files = uploadedFiles.map((file) => file.id);
try {
const response = await this.fetch.post(`${modelEndpoint}/import`, { files });
if (typeof options.onImportCompleted === 'function') {
options.onImportCompleted(response, files);
}
} catch (error) {
return this.notifications.serverError(error);
}
},
...options,
});
}
}