Skip to content

Commit

Permalink
Merge #4143 #4149
Browse files Browse the repository at this point in the history
4143: feat(reports): add file type detection r=jniles a=jniles

Adds file type detection to the reports archive page so that we are not
proposing to the user to print reports that are not printable (xlsx for
example).  It also adds features to provide the correct font awesome
icon depending on the extension to help a user determine which kind of
file they will be downloading.

**Example**
![RZoqUH5TAk](https://user-images.githubusercontent.com/896472/73257918-e899a400-41c4-11ea-9898-76bb6bd0a333.gif)


4149: fix(inventory): allow deletion from inventory r=jniles a=jniles

This commit removes the foreign key constraints from the inventory_log
to allow deletion from the inventory.  It also fixes the http headers
sent twice error in the error handler.

Closes #4147.

Co-authored-by: Jonathan Niles <jonathanwniles@gmail.com>
  • Loading branch information
bors[bot] and jniles committed Feb 3, 2020
3 parents ef4c3e4 + 7a99978 + 789e1b6 commit fc2db60
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 18 deletions.
55 changes: 55 additions & 0 deletions client/src/modules/reports/baseReports.service.js
Expand Up @@ -98,4 +98,59 @@ function BaseReportService($http, Modal, util, Languages) {

return instance.result;
}

function parseFileUrlToExtension(url) {
if (!url) { return ''; }
const parts = url.split('.');
const extension = parts[parts.length - 1];
return extension;
}

/**
* @function parseFileUrlToIcon
*
* @description
* Takes in a URL string with an given extension (.pdf, .doc, etc) and
* returns the font awesome class name associated with that icon.
*/
function parseFileUrlToIcon(url) {
const extension = parseFileUrlToExtension(url);

let icon;

switch (extension) {
case 'doc':
case 'docx':
icon = 'fa-file-word-o';
break;
case 'pdf':
icon = 'fa-file-pdf-o';
break;
case 'xlsx':
case 'xls':
icon = 'fa-file-excel-o';
break;
case 'zip':
case 'gz':
icon = 'fa-file-archive-o';
break;
case 'png':
case 'jpeg':
case 'jpg':
case 'svg':
icon = 'fa-file-image-o';
break;
case 'csv':
icon = 'fa-file-csv-o';
break;
default:
icon = 'fa-file-o';
break;
}

return icon;
}

service.parseFileUrlToIcon = parseFileUrlToIcon;
service.parseFileUrlToExtension = parseFileUrlToExtension;
}
24 changes: 19 additions & 5 deletions client/src/modules/reports/reportsArchive.js
Expand Up @@ -8,14 +8,22 @@ ReportsArchiveController.$inject = [
function ReportsArchiveController($state, SavedReports, Notify, reportData) {
const vm = this;

const typeTemplate = '<div class="ui-grid-cell-contents"><i class="fa fa-file-pdf-o"></i></div>';
const dateTemplate = `<div class="ui-grid-cell-contents">
const typeTemplate = `
<div class="ui-grid-cell-contents">
<i class="fa" ng-class="row.entity.icon" ng-attr-title="{{row.entity.extension}}"></i>
</div>`.trim();

const dateTemplate = `
<div class="ui-grid-cell-contents">
{{ row.entity.timestamp | date }} (<span am-time-ago="row.entity.timestamp"></span>)
</div>`;
</div>`.trim();

const printTemplate = `<div class="ui-grid-cell-contents">
const printTemplate = `
<div class="ui-grid-cell-contents">
<span ng-if="row.entity.isPrintable">
<bh-pdf-link pdf-url="/reports/archive/{{row.entity.uuid}}"></bh-pdf-link>
</div>`;
</span>
</div>`.trim();

const reportId = reportData.id;
vm.key = $state.params.key;
Expand Down Expand Up @@ -80,6 +88,12 @@ function ReportsArchiveController($state, SavedReports, Notify, reportData) {
// Load archived reports
SavedReports.listSavedReports(reportId)
.then((results) => {
results.forEach(row => {
row.icon = SavedReports.parseFileUrlToIcon(row.link);
row.extension = SavedReports.parseFileUrlToExtension(row.link);
row.isPrintable = row.extension === 'pdf';
});

vm.gridOptions.data = results;
})
.catch(() => {
Expand Down
19 changes: 11 additions & 8 deletions server/controllers/inventory/index.js
Expand Up @@ -208,15 +208,18 @@ function getInventoryItemsById(req, res, next) {


/**
* DELETE /inventory/:uuid
* delete an inventory group
* DELETE /inventory/metadata/:uuid
*
* @description
* Delete an inventory item from the database
*/
function deleteInventory(req, res, next) {

core.remove(req.params.uuid)
.then(res.sendStatus(204))
.catch(error => core.errorHandler(error, req, res, next))
.done();
async function deleteInventory(req, res, next) {
try {
await core.remove(req.params.uuid);
res.sendStatus(204);
} catch (err) {
core.errorHandler(err, req, res, next);
}
}

// ======================= inventory group =============================
Expand Down
7 changes: 3 additions & 4 deletions server/controllers/inventory/inventory/core.js
Expand Up @@ -227,11 +227,10 @@ function getItemsMetadata(params) {
}


// This function helps to delete an invetory

// This function helps to delete an inventory
function remove(_uuid) {
const sql = `DELETE FROM inventory WHERE uuid = HUID(?)`;
return db.exec(sql, _uuid);
const sql = `DELETE FROM inventory WHERE uuid = ?`;
return db.exec(sql, db.bid(_uuid));
}

/**
Expand Down
3 changes: 3 additions & 0 deletions server/models/migrations/next/migrate.sql
Expand Up @@ -2,3 +2,6 @@ DROP TABLE department;

DELETE FROM role_unit WHERE unit_id = 215;
DELETE FROM unit WHERE id = 215;

ALTER TABLE inventory_log DROP FOREIGN KEY `inventory_log_ibfk_1`;

1 change: 0 additions & 1 deletion server/models/schema.sql
Expand Up @@ -2345,7 +2345,6 @@ CREATE TABLE `inventory_log` (
PRIMARY KEY (`uuid`),
KEY `inventory_uuid` (`inventory_uuid`),
KEY `user_id` (`user_id`),
FOREIGN KEY (`inventory_uuid`) REFERENCES `inventory` (`uuid`),
FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET = utf8mb4 DEFAULT COLLATE = utf8mb4_unicode_ci;

Expand Down
66 changes: 66 additions & 0 deletions test/client-unit/services/BaseReportService.spec.js
@@ -0,0 +1,66 @@
/* global inject, expect */
/* eslint no-unused-expressions:off */
describe('BaseReportService', () => {
// shared services
let SavedReports;

// load bhima.services
beforeEach(module(
'pascalprecht.translate',
'tmh.dynamicLocale',
'ngStorage',
'angularMoment',
'ui.bootstrap',
'bhima.services',
));


let parseExtension;
let parseIcon;
beforeEach(inject((_BaseReportService_) => {
SavedReports = _BaseReportService_;
parseExtension = SavedReports.parseFileUrlToExtension;
parseIcon = SavedReports.parseFileUrlToIcon;
}));

describe('#parseUrlToExtension()', () => {
it('correctly parses simple paths', () => {
expect(parseExtension('image.jpg')).to.equal('jpg');
expect(parseExtension('image.svg')).to.equal('svg');
expect(parseExtension('doc.docx')).to.equal('docx');

expect(parseExtension('/this/is/some/path.pdf')).to.equal('pdf');
expect(parseExtension('https://github.com/cool.gif')).to.equal('gif');

});

it('handles error cases', () => {
expect(parseExtension('')).to.equal('');
expect(parseExtension()).to.equal('');
});

it('correctly parses complex paths', () => {
expect(parseExtension('a.file.with.decimals.pdf')).to.equal('pdf');
expect(parseExtension('/more/complex/file.with/decimals.gz')).to.equal('gz');
});
});

describe('#parseUrlToIcon()', () => {
it('correctly parses paths to guess their icons', () => {
expect(parseIcon('icon.png')).to.equal('fa-file-image-o');
expect(parseIcon('icon.svg')).to.equal('fa-file-image-o');

expect(parseIcon('document.doc')).to.equal('fa-file-word-o');
expect(parseIcon('document.docx')).to.equal('fa-file-word-o');

expect(parseIcon('msexcel.xls')).to.equal('fa-file-excel-o');
expect(parseIcon('msexcel.xlsx')).to.equal('fa-file-excel-o');

expect(parseIcon('unknown.what')).to.equal('fa-file-o');


expect(parseIcon('report.pdf')).to.equal('fa-file-pdf-o');
});
});

});

0 comments on commit fc2db60

Please sign in to comment.