]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6834 apply feedback
authorStas Vilchik <vilchiks@gmail.com>
Mon, 5 Oct 2015 08:10:31 +0000 (10:10 +0200)
committerStas Vilchik <vilchiks@gmail.com>
Mon, 5 Oct 2015 09:23:29 +0000 (11:23 +0200)
16 files changed:
server/sonar-web/.eslintrc
server/sonar-web/Gruntfile.coffee
server/sonar-web/package.json
server/sonar-web/src/main/js/apps/background-tasks/app.js
server/sonar-web/src/main/js/apps/background-tasks/header.js
server/sonar-web/src/main/js/apps/background-tasks/helpers.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/main.js
server/sonar-web/src/main/js/apps/background-tasks/search.js
server/sonar-web/src/main/js/apps/background-tasks/stats.js
server/sonar-web/src/main/js/apps/background-tasks/tasks.js
server/sonar-web/src/main/js/apps/nav/component/component-nav-menu.jsx
server/sonar-web/src/main/js/apps/nav/settings/settings-nav.jsx
server/sonar-web/src/main/js/libs/third-party/shim/moment-shim.js [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb
server/sonar-web/tests/apps/background-tasks-test.js
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 6dd0753370bceb5743176acc7913058961b83258..a9b3b6d0eae4b2433887cddbd770d09ce3d9a220 100644 (file)
@@ -48,7 +48,7 @@
     "curly": 2,                 // specify curly brace conventions for all control statements
     "default-case": 1,          // require default case in switch statements (off by default)
     "dot-notation": 0,          // encourages use of dot notation whenever possible
-    "eqeqeq": 2,                // require the use of === and !==
+    "eqeqeq": [2, "allow-null"],// require the use of === and !==
     "guard-for-in": 2,          // make sure for-in loops have an if statement (off by default)
     "no-alert": 1,              // disallow the use of alert, confirm, and prompt
     "no-caller": 2,             // disallow use of arguments.caller or arguments.callee
index e2eecf52e601cea1ac4e89f5b976774b523d9e72..8a51cadc0fbd5fcab2a2b5cb5312a7fa258d845e 100644 (file)
@@ -117,6 +117,7 @@ module.exports = (grunt) ->
           'jquery': 'libs/third-party/shim/jquery-shim'
           'backbone': 'libs/third-party/shim/backbone-shim'
           'backbone.marionette': 'libs/third-party/shim/marionette-shim'
+          'moment': 'libs/third-party/shim/moment-shim'
 
       issuesContext: options:
         name: 'apps/issues/app-context'
index c6de70b2cfe9392ffea957e0fbea6c02d7993405..b555da9cf2c285207390b795f6889733c7fa2101 100644 (file)
@@ -28,6 +28,7 @@
     "jquery": "2.1.4",
     "jsdom": "^6.5.1",
     "mocha": "^2.3.3",
+    "moment": "^2.10.6",
     "react": "0.13.3",
     "sinon": "^1.15.4",
     "sinon-chai": "^2.8.0",
index 7a94708fcc2e456571ddf71cf550bc9b72eff020..d69c1b580919ee6589e0d7bd1c590b4409300689 100644 (file)
@@ -3,7 +3,9 @@ import Main from './main';
 
 export default {
   start (options) {
-    var el = document.querySelector(options.el);
-    React.render(<Main options={options}/>, el);
+    window.requestMessages().done(() => {
+      let el = document.querySelector(options.el);
+      React.render(<Main options={options}/>, el);
+    });
   }
 };
index 7f967d77d30356bd061d9c80ebd8e5c88af6d56d..9af73bd5963c00cf0aaba73a62ca826b7ef9317c 100644 (file)
@@ -4,12 +4,9 @@ export default React.createClass({
   render() {
     return (
         <header className="page-header">
-          <h1 className="page-title">Background Tasks</h1>
-          <p className="page-description">The server is in charge to process reports submitted by batch analyses. This
-            page allows to monitor the queue of pending reports to process, and gives access to the history of past
-            analyses.</p>
+          <h1 className="page-title">{window.t('background_tasks.page')}</h1>
+          <p className="page-description">{window.t('background_tasks.page.description')}</p>
         </header>
-
     );
   }
 });
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/helpers.js b/server/sonar-web/src/main/js/apps/background-tasks/helpers.js
new file mode 100644 (file)
index 0000000..9dd9ed8
--- /dev/null
@@ -0,0 +1,21 @@
+const ONE_SECOND = 1000;
+const ONE_MINUTE = 60 * ONE_SECOND;
+
+function format(int, suffix) {
+  return `${int}${suffix}`;
+}
+
+export function formatDuration(value) {
+  if (!value) {
+    return '';
+  }
+  if (value >= ONE_MINUTE) {
+    let minutes = Math.round(value / ONE_MINUTE);
+    return format(minutes, 'min');
+  } else if (value >= ONE_SECOND) {
+    let seconds = Math.round(value / ONE_SECOND);
+    return format(seconds, 's');
+  } else {
+    return format(value, 'ms');
+  }
+}
index 026f2d7e264b1eddcf5e95aa49c7cdd7dc52e0de..9545e8a76f3171d6f5682772e22a72bf4521756e 100644 (file)
@@ -171,10 +171,7 @@ export default React.createClass({
   },
 
   onTaskCanceled(task) {
-    cancelTask(task.id).done(data => {
-      _.extend(task, data.task);
-      this.forceUpdate();
-    });
+    cancelTask(task.id).then(this.requestData);
   },
 
   cancelPending() {
@@ -189,6 +186,7 @@ export default React.createClass({
           <Stats {...this.state} cancelPending={this.cancelPending} showFailures={this.showFailures}/>
 
           <Search {...this.props} {...this.state}
+              refresh={this.requestData}
               onStatusChange={this.onStatusChange}
               onCurrentsChange={this.onCurrentsChange}
               onDateChange={this.onDateChange}
index d70ab5e1337c5b06187131a009ca08fa67ac45e4..5c83f281d7b9e5c3fdb2c73387c84f08d5d25292 100644 (file)
@@ -19,25 +19,25 @@ export default React.createClass({
 
   getCurrentsOptions() {
     return [
-      { value: CURRENTS.ALL, label: 'All' },
-      { value: CURRENTS.ONLY_CURRENTS, label: 'Only Currents' }
+      { value: CURRENTS.ALL, label: window.t('background_tasks.currents_filter.ALL') },
+      { value: CURRENTS.ONLY_CURRENTS, label: window.t('background_tasks.currents_filter.ONLY_CURRENTS') }
     ];
   },
 
   getStatusOptions() {
     return [
-      { value: STATUSES.ALL, label: 'All' },
-      { value: STATUSES.SUCCESS, label: 'Success' },
-      { value: STATUSES.FAILED, label: 'Failed' },
-      { value: STATUSES.CANCELED, label: 'Canceled' }
+      { value: STATUSES.ALL, label: window.t('background_task.status.ALL') },
+      { value: STATUSES.SUCCESS, label: window.t('background_task.status.SUCCESS') },
+      { value: STATUSES.FAILED, label: window.t('background_task.status.FAILED') },
+      { value: STATUSES.CANCELED, label: window.t('background_task.status.CANCELED') }
     ];
   },
 
   getDateOptions() {
     return [
-      { value: DATE.ANY, label: 'Any Date' },
-      { value: DATE.TODAY, label: 'Today' },
-      { value: DATE.CUSTOM, label: 'Custom' }
+      { value: DATE.ANY, label: window.t('background_tasks.date_filter.ALL') },
+      { value: DATE.TODAY, label: window.t('background_tasks.date_filter.TODAY') },
+      { value: DATE.CUSTOM, label: window.t('background_tasks.date_filter.CUSTOM') }
     ];
   },
 
@@ -112,6 +112,14 @@ export default React.createClass({
     );
   },
 
+  refresh(e) {
+    e.preventDefault();
+    this.props.refresh();
+    let btn = e.target;
+    btn.disabled = true;
+    setTimeout(() => btn.disabled = false, 500);
+  },
+
   render() {
     return (
         <section className="big-spacer-top big-spacer-bottom">
@@ -130,6 +138,9 @@ export default React.createClass({
               {this.renderCustomDateInput()}
             </li>
             <li>{this.renderSearchBox()}</li>
+            <li className="pull-right">
+              <button onClick={this.refresh} ref="reloadButton">{window.t('reload')}</button>
+            </li>
           </ul>
         </section>
     );
index 719b789fa7325c41e245ae8dbfcfbd0c97526220..cd6057b89123243a26f4309661a61e0a12d95940 100644 (file)
@@ -1,4 +1,5 @@
 import React from 'react';
+import {formatDuration} from './helpers';
 
 export default React.createClass({
   onPendingCanceled(e) {
@@ -16,9 +17,12 @@ export default React.createClass({
       return null;
     }
     return (
-        <span className="huge-spacer-left">
+        <span className="huge-spacer-left" title={window.t('background_tasks.in_progress_duration')}
+              data-toggle="tooltip">
           <i className="spinner spacer-right" style={{ verticalAlign: 'text-top' }}/>
-          <span className="emphasised-measure">{this.props.inProgressDuration} ms</span>
+          <span ref="inProgressDuration" className="emphasised-measure">
+            {formatDuration(this.props.inProgressDuration)}
+          </span>
         </span>
     );
   },
@@ -30,13 +34,21 @@ export default React.createClass({
     if (this.props.pendingCount > 0) {
       return (
           <span>
-            <span className="emphasised-measure">{this.props.pendingCount}</span> pending
-            <a onClick={this.onPendingCanceled} className="icon-delete spacer-left" title="Cancel Pending Tasks"
-               data-toggle="tooltip" href="#"></a>
+            <span ref="pendingCount" className="emphasised-measure">{this.props.pendingCount}</span>
+            &nbsp;
+            {window.t('background_tasks.pending')}
+            <a ref="cancelPending" onClick={this.onPendingCanceled} className="icon-delete spacer-left"
+               title={window.t('background_tasks.cancel_all_tasks')} data-toggle="tooltip" href="#"></a>
           </span>
       );
     } else {
-      return <span><span className="emphasised-measure">{this.props.pendingCount}</span> pending</span>;
+      return (
+          <span>
+            <span ref="pendingCount" className="emphasised-measure">{this.props.pendingCount}</span>
+            &nbsp;
+            {window.t('background_tasks.pending')}
+          </span>
+      );
     }
   },
 
@@ -45,9 +57,22 @@ export default React.createClass({
       return null;
     }
     if (this.props.failuresCount > 0) {
-      return <span><a onClick={this.onFailuresClick} className="emphasised-measure" href="#">{this.props.failuresCount}</a> failures</span>;
+      return (
+          <span>
+            <a ref="failureCount" onClick={this.onFailuresClick} className="emphasised-measure"
+               href="#">{this.props.failuresCount}</a>
+            &nbsp;
+            {window.t('background_tasks.failures')}
+          </span>
+      );
     } else {
-      return <span><span className="emphasised-measure">{this.props.failuresCount}</span> failures</span>;
+      return (
+          <span>
+            <span ref="failureCount" className="emphasised-measure">{this.props.failuresCount}</span>
+            &nbsp;
+            {window.t('background_tasks.failures')}
+          </span>
+      );
     }
   },
 
index 9e0e6b33e9b5e1cc198f89d515cc9e8b34e18d62..8d3753e7d136d887f468df38c1a9cc4bbab6059e 100644 (file)
@@ -1,8 +1,10 @@
+import moment from 'moment';
 import React from 'react';
 import {getProjectUrl} from '../../helpers/Url';
 import QualifierIcon from '../../components/shared/qualifier-icon';
 import PendingIcon from '../../components/shared/pending-icon';
 import {STATUSES} from './constants';
+import {formatDuration} from './helpers';
 
 export default React.createClass({
   propTypes: {
@@ -35,7 +37,8 @@ export default React.createClass({
       default:
         inner = '';
     }
-    return <td className="thin spacer-right">{inner}</td>;
+    return <td className="thin spacer-right" title={window.t('background_task.status', task.status)}
+               data-toggle="tooltip">{inner}</td>;
   },
 
   renderTaskComponent(task) {
@@ -43,11 +46,10 @@ export default React.createClass({
       return <td><span className="note">{task.id}</span></td>;
     }
 
-    let qualifier = task.type === 'REPORT' ? 'TRK' : null;
     return (
         <td>
           <span className="little-spacer-right">
-            <QualifierIcon qualifier={qualifier}/>
+            <QualifierIcon qualifier={task.componentQualifier}/>
           </span>
           <a href={getProjectUrl(task.componentKey)}>{task.componentName}</a>
         </td>
@@ -73,8 +75,7 @@ export default React.createClass({
   },
 
   renderTaskExecutionTime(task) {
-    let inner = task.executionTimeMs ? `${task.executionTimeMs} ms` : '';
-    return <td className="thin nowrap text-right">{inner}</td>;
+    return <td className="thin nowrap text-right">{formatDuration(task.executionTimeMs)}</td>;
   },
 
   isAnotherDay(a, b) {
@@ -84,8 +85,8 @@ export default React.createClass({
   renderCancelButton(task) {
     if (task.status === STATUSES.PENDING) {
       return (
-          <a onClick={this.onTaskCanceled.bind(this, task)} className="icon-delete" title="Cancel Task"
-             data-toggle="tooltip" href="#"></a>
+          <a onClick={this.onTaskCanceled.bind(this, task)} className="icon-delete"
+             title={window.t('background_tasks.cancel_task')} data-toggle="tooltip" href="#"></a>
       );
     } else {
       return null;
@@ -95,7 +96,7 @@ export default React.createClass({
   renderLogsLink(task) {
     if (task.logs) {
       let url = `${window.baseUrl}/api/ce/logs?taskId=${task.id}`;
-      return <a target="_blank" href={url}>Logs</a>;
+      return <a target="_blank" href={url}>{window.t('background_tasks.logs')}</a>;
     } else {
       return null;
     }
@@ -132,10 +133,10 @@ export default React.createClass({
             <th>&nbsp;</th>
             <th>&nbsp;</th>
             <th>&nbsp;</th>
-            <th>Submitted</th>
-            <th>Started</th>
-            <th>Finished</th>
-            <th>Duration</th>
+            <th>{window.t('background_tasks.table.submitted')}</th>
+            <th>{window.t('background_tasks.table.started')}</th>
+            <th>{window.t('background_tasks.table.finished')}</th>
+            <th>{window.t('background_tasks.table.duration')}</th>
             <th>&nbsp;</th>
           </tr>
           </thead>
index ed54a7cf63d7781d5ad90d9e432d2694e48718f7..cd6b3adae7070fa39d794d29310e7859be304712 100644 (file)
@@ -152,7 +152,7 @@ export default React.createClass({
   renderBackgroundTasksLink() {
     // TODO check permissions
     const url = `/project/background_tasks?id=${encodeURIComponent(this.props.component.key)}`;
-    return this.renderLink(url, 'Background Tasks', '/project/background_tasks');
+    return this.renderLink(url, window.t('background_tasks.page'), '/project/background_tasks');
   },
 
   renderUpdateKeyLink() {
index e6e316a9ed492975eb712a8ad153c050644dde97..87de9371fc38e39607c005c85ea11b090a6f8a34 100644 (file)
@@ -56,7 +56,7 @@ export default React.createClass({
               </a>
               <ul className="dropdown-menu">
                 {this.renderLink('/projects', 'Management')}
-                {this.renderLink('/background_tasks', 'Background Tasks')}
+                {this.renderLink('/background_tasks', window.t('background_tasks.page'))}
               </ul>
             </li>
 
diff --git a/server/sonar-web/src/main/js/libs/third-party/shim/moment-shim.js b/server/sonar-web/src/main/js/libs/third-party/shim/moment-shim.js
new file mode 100644 (file)
index 0000000..32de3fe
--- /dev/null
@@ -0,0 +1,3 @@
+define(function () {
+  return window.moment;
+});
index 16110d5399be4889ab6ae1ce88dc7f644f82c8a3..8e16db96445960fdb4a0815a5171c4f02b3f8236 100644 (file)
@@ -51,7 +51,8 @@
         'underscore': 'libs/third-party/shim/underscore-shim',
         'jquery': 'libs/third-party/shim/jquery-shim',
         'backbone': 'libs/third-party/shim/backbone-shim',
-        'backbone.marionette': 'libs/third-party/shim/marionette-shim'
+        'backbone.marionette': 'libs/third-party/shim/marionette-shim',
+        'moment': 'libs/third-party/shim/moment-shim'
       }
     });
   </script>
index a5501a5ec0ad36a0da9ae3bdaf78a63468635f75..22f2531a36e59de6354a50d2908100fb5305fdc8 100644 (file)
@@ -1,8 +1,12 @@
+/* eslint no-unused-expressions: 0 */
 import React from 'react/addons';
 import App from '../../src/main/js/apps/background-tasks/app';
 import Header from '../../src/main/js/apps/background-tasks/header';
+import Stats from '../../src/main/js/apps/background-tasks/stats';
 import Search from '../../src/main/js/apps/background-tasks/search';
+import Tasks from '../../src/main/js/apps/background-tasks/tasks';
 import {STATUSES, CURRENTS, DEBOUNCE_DELAY} from '../../src/main/js/apps/background-tasks/constants';
+import {formatDuration} from '../../src/main/js/apps/background-tasks/helpers';
 
 let TestUtils = React.addons.TestUtils;
 let chai = require('chai');
@@ -74,5 +78,167 @@ describe('Background Tasks', function () {
         done();
       }, DEBOUNCE_DELAY);
     });
+
+    it('should reload', () => {
+      let spy = sinon.spy(),
+          reloadSpy = sinon.spy();
+      let component = TestUtils.renderIntoDocument(<Search options={{}}
+                                                           onStatusChange={spy}
+                                                           onCurrentsChange={spy}
+                                                           onDateChange={spy}
+                                                           refresh={reloadSpy}/>);
+      let reloadButton = React.findDOMNode(component.refs.reloadButton);
+      expect(reloadSpy).to.not.have.been.called;
+      TestUtils.Simulate.click(reloadButton);
+      expect(reloadSpy).to.have.been.called;
+    });
+  });
+
+  describe('Stats', () => {
+    describe('Pending', () => {
+      it('should show zero pending', () => {
+        let result = TestUtils.renderIntoDocument(<Stats pendingCount="0"/>),
+            pendingCounter = React.findDOMNode(result.refs.pendingCount);
+        expect(pendingCounter.textContent).to.contain('0');
+      });
+
+      it('should show 5 pending', () => {
+        let result = TestUtils.renderIntoDocument(<Stats pendingCount="5"/>),
+            pendingCounter = React.findDOMNode(result.refs.pendingCount);
+        expect(pendingCounter.textContent).to.contain('5');
+      });
+
+      it('should not show cancel pending button', () => {
+        let result = TestUtils.renderIntoDocument(<Stats pendingCount="0"/>),
+            cancelPending = React.findDOMNode(result.refs.cancelPending);
+        expect(cancelPending).to.not.be.ok;
+      });
+
+      it('should show cancel pending button', () => {
+        let result = TestUtils.renderIntoDocument(<Stats pendingCount="5"/>),
+            cancelPending = React.findDOMNode(result.refs.cancelPending);
+        expect(cancelPending).to.be.ok;
+      });
+
+      it('should trigger cancelling pending', () => {
+        let spy = sinon.spy();
+        let result = TestUtils.renderIntoDocument(<Stats pendingCount="5" cancelPending={spy}/>),
+            cancelPending = React.findDOMNode(result.refs.cancelPending);
+        expect(spy).to.not.have.been.called;
+        TestUtils.Simulate.click(cancelPending);
+        expect(spy).to.have.been.called;
+      });
+    });
+
+    describe('Failures', () => {
+      it('should show zero failures', () => {
+        let result = TestUtils.renderIntoDocument(<Stats failuresCount="0"/>),
+            failureCounter = React.findDOMNode(result.refs.failureCount);
+        expect(failureCounter.textContent).to.contain('0');
+      });
+
+      it('should show 5 failures', () => {
+        let result = TestUtils.renderIntoDocument(<Stats failuresCount="5"/>),
+            failureCounter = React.findDOMNode(result.refs.failureCount);
+        expect(failureCounter.textContent).to.contain('5');
+      });
+
+      it('should not show link to failures', () => {
+        let result = TestUtils.renderIntoDocument(<Stats failuresCount="0"/>),
+            failureCounter = React.findDOMNode(result.refs.failureCount);
+        expect(failureCounter.tagName.toLowerCase()).to.not.equal('a');
+      });
+
+      it('should show link to failures', () => {
+        let result = TestUtils.renderIntoDocument(<Stats failuresCount="5"/>),
+            failureCounter = React.findDOMNode(result.refs.failureCount);
+        expect(failureCounter.tagName.toLowerCase()).to.equal('a');
+      });
+
+      it('should trigger filtering failures', () => {
+        let spy = sinon.spy();
+        let result = TestUtils.renderIntoDocument(<Stats failuresCount="5" showFailures={spy}/>),
+            failureCounter = React.findDOMNode(result.refs.failureCount);
+        expect(spy).to.not.have.been.called;
+        TestUtils.Simulate.click(failureCounter);
+        expect(spy).to.have.been.called;
+      });
+    });
+
+    describe('In Progress Duration', () => {
+      it('should show duration', () => {
+        let result = TestUtils.renderIntoDocument(<Stats inProgressDuration="173"/>),
+            inProgressDuration = React.findDOMNode(result.refs.inProgressDuration);
+        expect(inProgressDuration.textContent).to.include('173ms');
+      });
+
+      it('should format duration', () => {
+        let result = TestUtils.renderIntoDocument(<Stats inProgressDuration="1073"/>),
+            inProgressDuration = React.findDOMNode(result.refs.inProgressDuration);
+        expect(inProgressDuration.textContent).to.include('1s');
+      });
+
+      it('should not show duration', () => {
+        let result = TestUtils.renderIntoDocument(<Stats/>),
+            inProgressDuration = React.findDOMNode(result.refs.inProgressDuration);
+        expect(inProgressDuration).to.not.be.ok;
+      });
+    });
+  });
+
+  describe('Tasks', () => {
+    it('should show list', () => {
+      let tasks = [
+        { id: 'a' },
+        { id: 'b' },
+        { id: 'c' }
+      ];
+      let result = TestUtils.renderIntoDocument(<Tasks tasks={tasks}/>);
+      expect(TestUtils.scryRenderedDOMComponentsWithTag(result, 'tr')).to.have.length(3 + /* table header */ 1);
+    });
+  });
+
+  describe('Helpers', () => {
+    describe('#formatDuration()', () => {
+      it('should format 173ms', () => {
+        expect(formatDuration(173)).to.equal('173ms');
+      });
+
+      it('should format 999ms', () => {
+        expect(formatDuration(999)).to.equal('999ms');
+      });
+
+      it('should format 1s', () => {
+        expect(formatDuration(1000)).to.equal('1s');
+      });
+
+      it('should format 1s', () => {
+        expect(formatDuration(1001)).to.equal('1s');
+      });
+
+      it('should format 2s', () => {
+        expect(formatDuration(1501)).to.equal('2s');
+      });
+
+      it('should format 59s', () => {
+        expect(formatDuration(59000)).to.equal('59s');
+      });
+
+      it('should format 1min', () => {
+        expect(formatDuration(60000)).to.equal('1min');
+      });
+
+      it('should format 1min', () => {
+        expect(formatDuration(62757)).to.equal('1min');
+      });
+
+      it('should format 4min', () => {
+        expect(formatDuration(224567)).to.equal('4min');
+      });
+
+      it('should format 80min', () => {
+        expect(formatDuration(80 * 60 * 1000)).to.equal('80min');
+      });
+    });
   });
 });
index cfc4626d9c95e76d70f2968df2706d45b3da6a92..e739976c016de0a6c50c2ae7d6047e189715093a 100644 (file)
@@ -3051,10 +3051,31 @@ component_navigation.status.pending.admin=There is a pending analysis.<br>More d
 component_navigation.status.in_progress=The analysis is in progress.
 component_navigation.status.in_progress.admin=The analysis is in progress.<br>More details available on the <a href="{0}">Background Tasks</a> page.
 
+background_task.status.ALL=All
 background_task.status.PENDING=Pending
 background_task.status.IN_PROGRESS=In Progress
 background_task.status.SUCCESS=Success
 background_task.status.FAILED=Failed
 background_task.status.CANCELED=Canceled
 
+background_tasks.page=Background Tasks
+background_tasks.page.description=The server is in charge to process reports submitted by batch analyses. This page allows to monitor the queue of pending reports to process, and gives access to the history of past analyses.
 
+background_tasks.currents_filter.ALL=All
+background_tasks.currents_filter.ONLY_CURRENTS=Only Latest Analysis
+
+background_tasks.date_filter.ALL=Any Date
+background_tasks.date_filter.TODAY=Today
+background_tasks.date_filter.CUSTOM=Custom
+
+background_tasks.table.submitted=Submitted
+background_tasks.table.started=Started
+background_tasks.table.finished=Finished
+background_tasks.table.duration=Duration
+
+background_tasks.logs=Logs
+background_tasks.cancel_task=Cancel Task
+background_tasks.cancel_all_tasks=Cancel All Pending Tasks
+background_tasks.pending=pending
+background_tasks.failures=failures
+background_tasks.in_progress_duration=Duration of the current task in progress.