← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 16084: Re-implemented audit for updates and deletes to DataValues. Added tests for the DataValueAuditSer...

 

Merge authors:
  Halvdan Hoem Grelland (halvdanhg)
------------------------------------------------------------
revno: 16084 [merge]
committer: Halvdan Hoem Grelland <halvdanhg@xxxxxxxxx>
branch nick: dhis2
timestamp: Fri 2014-07-11 12:47:46 +0200
message:
  Re-implemented audit for updates and deletes to DataValues. Added tests for the DataValueAuditService. Implemented tabbed view in data value history dialog (data entry) for displaying data element history alongside the audit trail.
added:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/AuditType.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/common/AuditTypeUserType.java
  dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/datavalue/DataValueAuditServiceTest.java
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValue.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueAudit.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueAuditService.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueAuditStore.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DataValueAuditDeletionHandler.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueAuditService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueAuditStore.java
  dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml
  dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/datavalue/hibernate/DataValueAudit.hbm.xml
  dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java
  dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java
  dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/GetHistoryAction.java
  dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/org/hisp/dhis/de/i18n_module.properties
  dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/history.vm
  dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/style/dhis-web-dataentry.css


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== added file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/AuditType.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/AuditType.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/AuditType.java	2014-07-09 13:43:43 +0000
@@ -0,0 +1,49 @@
+package org.hisp.dhis.common;
+
+/*
+ * Copyright (c) 2004-2014, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * Neither the name of the HISP project nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @author Halvdan Hoem Grelland
+ */
+public enum AuditType
+{
+    UPDATE( "update" ), DELETE( "delete" );
+
+    private final String value;
+
+    private AuditType( String value )
+    {
+        this.value = value;
+    }
+
+    public String getValue()
+    {
+        return value;
+    }
+}

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValue.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValue.java	2014-07-07 10:38:51 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValue.java	2014-07-11 10:21:13 +0000
@@ -55,6 +55,10 @@
     public static final String TRUE = "true";
     public static final String FALSE = "false";
 
+    // -------------------------------------------------------------------------
+    // Persistent properties
+    // -------------------------------------------------------------------------
+
     private DataElement dataElement;
 
     private Period period;
@@ -78,6 +82,16 @@
     private Boolean followup;
 
     // -------------------------------------------------------------------------
+    // Transient properties
+    // -------------------------------------------------------------------------
+
+    private transient boolean auditValueIsSet = false;
+
+    private transient boolean valueIsSet = false;
+
+    private transient String auditValue;
+
+    // -------------------------------------------------------------------------
     // Constructors
     // -------------------------------------------------------------------------
 
@@ -296,6 +310,14 @@
 
     public void setValue( String value )
     {
+        if( !auditValueIsSet )
+        {
+            this.auditValue = valueIsSet ? this.auditValue : value;
+            auditValueIsSet = true;
+        }
+
+        valueIsSet = true;
+
         this.value = value;
     }
 
@@ -348,4 +370,9 @@
     {
         this.followup = followup;
     }
+
+    public String getAuditValue()
+    {
+        return auditValue;
+    }
 }
\ No newline at end of file

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueAudit.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueAudit.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueAudit.java	2014-07-10 15:06:47 +0000
@@ -28,42 +28,58 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import org.hisp.dhis.common.AuditType;
+import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
+import org.hisp.dhis.organisationunit.OrganisationUnit;
+import org.hisp.dhis.period.Period;
+
 import java.util.Date;
 
 /**
  * @author Quang Nguyen
- * @version Mar 30, 2010 9:13:36 PM
+ * @author Halvdan Hoem Grelland
  */
 
 public class DataValueAudit
 {
     private int id;
 
-    private DataValue dataValue;
-
     private String value;
 
-    private String storedBy;
+    private String modifiedBy;
 
     private Date timestamp;
 
-    private String comment;
+    private DataElement dataElement;
+
+    private Period period;
+
+    private OrganisationUnit organisationUnit;
+
+    private DataElementCategoryOptionCombo categoryOptionCombo;
+
+    private AuditType auditType;
 
     // -------------------------------------------------------------------------
     // Constructors
     // -------------------------------------------------------------------------
+
     public DataValueAudit()
     {
-
     }
 
-    public DataValueAudit( DataValue dataValue, String value, String storedBy, Date timestamp, String comment )
+    public DataValueAudit( DataValue dataValue, String value, String modifiedBy, Date timestamp, AuditType auditType )
     {
-        this.dataValue = dataValue;
+        this.dataElement = dataValue.getDataElement();
+        this.period = dataValue.getPeriod();
+        this.organisationUnit = dataValue.getSource();
+        this.categoryOptionCombo = dataValue.getCategoryOptionCombo();
+
         this.value = value;
-        this.storedBy = storedBy;
+        this.modifiedBy = modifiedBy;
         this.timestamp = timestamp;
-        this.comment = comment;
+        this.auditType = auditType;
     }
 
     // -------------------------------------------------------------------------
@@ -80,16 +96,6 @@
         this.id = id;
     }
 
-    public DataValue getDataValue()
-    {
-        return dataValue;
-    }
-
-    public void setDataValue( DataValue dataValue )
-    {
-        this.dataValue = dataValue;
-    }
-
     public String getValue()
     {
         return value;
@@ -100,14 +106,14 @@
         this.value = value;
     }
 
-    public String getStoredBy()
+    public String getModifiedBy()
     {
-        return storedBy;
+        return modifiedBy;
     }
 
-    public void setStoredBy( String storedBy )
+    public void setModifiedBy( String modifiedBy )
     {
-        this.storedBy = storedBy;
+        this.modifiedBy = modifiedBy;
     }
 
     public Date getTimestamp()
@@ -115,18 +121,58 @@
         return timestamp;
     }
 
-    public void setTimestamp( Date timeStamp )
-    {
-        this.timestamp = timeStamp;
-    }
-
-    public String getComment()
-    {
-        return comment;
-    }
-
-    public void setComment( String comment )
-    {
-        this.comment = comment;
+    public void setTimestamp( Date timestamp )
+    {
+        this.timestamp = timestamp;
+    }
+
+    public DataElement getDataElement()
+    {
+        return dataElement;
+    }
+
+    public void setDataElement( DataElement dataElement )
+    {
+        this.dataElement = dataElement;
+    }
+
+    public Period getPeriod()
+    {
+        return period;
+    }
+
+    public void setPeriod( Period period )
+    {
+        this.period = period;
+    }
+
+    public OrganisationUnit getOrganisationUnit()
+    {
+        return organisationUnit;
+    }
+
+    public void setOrganisationUnit( OrganisationUnit organisationUnit )
+    {
+        this.organisationUnit = organisationUnit;
+    }
+
+    public DataElementCategoryOptionCombo getCategoryOptionCombo()
+    {
+        return categoryOptionCombo;
+    }
+
+    public void setCategoryOptionCombo( DataElementCategoryOptionCombo categoryOptionCombo )
+    {
+        this.categoryOptionCombo = categoryOptionCombo;
+    }
+
+    public AuditType getAuditType()
+    {
+        return auditType;
+    }
+
+    public void setAuditType( AuditType auditType )
+    {
+        this.auditType = auditType;
     }
 }

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueAuditService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueAuditService.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueAuditService.java	2014-07-08 17:16:48 +0000
@@ -28,37 +28,89 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-import java.util.Collection;
-
 import org.hisp.dhis.dataelement.DataElement;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.period.Period;
 
+import java.util.Collection;
+
 /**
  * @author Quang Nguyen
- * @version Mar 30, 2010 9:39:38 PM
+ * @author Halvdan Hoem Grelland
  */
 
 public interface DataValueAuditService
 {
     String ID = DataValueAuditService.class.getName();
 
+    /**
+     * Adds a DataValueAudit.
+     *
+     * @param dataValueAudit the DataValueAudit to add.
+     */
     void addDataValueAudit( DataValueAudit dataValueAudit );
 
+    /**
+     * Returns all DataValueAudits for the given DataValue.
+     *
+     * @param dataValue the DataValue to get DataValueAudits for.
+     * @return a collection of DataValueAudits which match the given DataValue,
+     *         or an empty collection if there are no matches.
+     */
+    Collection<DataValueAudit> getDataValueAudits( DataValue dataValue );
+
+    /**
+     * Returns all DataValueAudits for the given DataElement, Period,
+     * OrganisationUnit and DataElementCategoryOptionCombo.
+     *
+     * @param dataElement the DataElement of the DataValueAudits.
+     * @param period the Period of the DataValueAudits.
+     * @param organisationUnit the OrganisationUnit of the DataValueAudits.
+     * @param categoryOptionCombo the DataElementCategoryOptionCombo of the DataValueAudits.
+     * @return a collection of DataValueAudits which matches the given DataElement, Period,
+     *         OrganisationUnit and DataElementCategoryOptionCombo, or an empty collection if
+     *         there are not matches.
+     */
+    Collection<DataValueAudit> getDataValueAudits( DataElement dataElement,
+        Period period, OrganisationUnit organisationUnit, DataElementCategoryOptionCombo categoryOptionCombo );
+
+    /**
+     * Deletes a DataValueAudit.
+     *
+     * @param dataValueAudit the DataValueAudit to be deleted.
+     */
     void deleteDataValueAudit( DataValueAudit dataValueAudit );
 
-    int deleteDataValueAuditByDataValue( DataValue dataValue );
-
-    void deleteDataValueAuditBySource( OrganisationUnit source );
-
-    void deleteDataValueAuditByDataElement( DataElement dataElement );
-
-    Collection<DataValueAudit> getDataValueAuditByDataValue( DataValue dataValue );
-
-    Collection<DataValueAudit> getAll();
-    
-    public void deleteByPeriod( Period period );
-    
-    public void deleteByDataElementCategoryOptionCombo( DataElementCategoryOptionCombo optionCombo );
+    /**
+     * Deletes all DataValueAudits for the given DataElement.
+     *
+     * @param dataElement the DataElement for which the DataValueAudits should be deleted.
+     * @return the number of deleted DataValueAudits.
+     */
+    int deleteDataValueAuditByDataElement( DataElement dataElement );
+
+    /**
+     * Deletes all DataValueAudits for the given Period.
+     *
+     * @param period the Period for which the DataValueAudits should be deleted.
+     * @return the number of deleted DataValueAudits.
+     */
+    int deleteDataValueAuditByPeriod( Period period );
+
+    /**
+     * Deletes all DataValues for the given OrganisationUnit.
+     *
+     * @param organisationUnit the OrganisationUnit for which the DataValueAudits should be deleted.
+     * @return the number of deleted DataValueAudits.
+     */
+    int deleteDataValueAuditByOrganisationUnit( OrganisationUnit organisationUnit );
+
+    /**
+     * Deletes all DataValues for the given DataElementCategoryOptionCombo.
+     *
+     * @param categoryOptionCombo the DataElementCategoryOptionCombo for which the DataValueAudits should be deleted.
+     * @return the number of deleted DataValueAudits.
+     */
+    int deleteDataValueAuditByCategoryOptionCombo( DataElementCategoryOptionCombo categoryOptionCombo );
 }

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueAuditStore.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueAuditStore.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueAuditStore.java	2014-07-09 13:43:43 +0000
@@ -28,36 +28,90 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-import java.util.Collection;
-
 import org.hisp.dhis.dataelement.DataElement;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.period.Period;
 
+import java.util.Collection;
+
 /**
  * @author Quang Nguyen
- * @version Mar 30, 2010 10:32:19 PM
+ * @author Halvdan Hoem Grelland
  */
 public interface DataValueAuditStore
 {
     String ID = DataValueAuditStore.class.getName();
 
+    /**
+     * Adds a DataValueAudit.
+     *
+     * @param dataValueAudit the DataValueAudit to add.
+     */
     void addDataValueAudit( DataValueAudit dataValueAudit );
 
+    /**
+     * Returns all DataValueAudits which match the DataElement, Period, OrganisationUnit
+     * and DataElementCategoryOptionCombo of the given DataValue.
+     *
+     * @param dataValue the DataValue to get DataValueAudits for.
+     * @return a collection of DataValueAudits which match the DataElement Period,
+     *         OrganisationUnit and DataElementCategoryOptionCombo of the given DataValue,
+     *         or an empty collection if no DataValueAudits match.
+     */
+    Collection<DataValueAudit> getDataValueAudits( DataValue dataValue );
+
+    /**
+     * Returns all DataValueAudits which match the given DataElement, Period,
+     * OrganisationUnit and DataElementCategoryOptionCombo.
+     *
+     * @param dataElement the DataElement of the DataValueAudits.
+     * @param period the Period of the DataValueAudits.
+     * @param organisationUnit the OrganisationUnit of the DataValueAudits.
+     * @param categoryOptionCombo the DataElementCategoryOptionCombo of the DataValueAudits.
+     * @return a collection of DataValueAudits which match the given DataElement, Period,
+     *         OrganisationUnit and DataElementCategoryOptionCombo, or an empty collection
+     *         if no DataValueAudits match.
+     */
+    Collection<DataValueAudit> getDataValueAudits( DataElement dataElement,
+        Period period, OrganisationUnit organisationUnit, DataElementCategoryOptionCombo categoryOptionCombo );
+
+    /**
+     * Deletes a DataValueAudit.
+     *
+     * @param dataValueAudit the DataValueAudit to delete.
+     */
     void deleteDataValueAudit( DataValueAudit dataValueAudit );
 
-    int deleteDataValueAuditByDataValue( DataValue dataValue );
-
-    void deleteDataValueAuditBySource( OrganisationUnit source );
-
-    void deleteDataValueAuditByDataElement( DataElement dataElement );
-
-    Collection<DataValueAudit> getDataValueAuditByDataValue( DataValue dataValue );
-
-    Collection<DataValueAudit> getAll();
-    
-    int deleteByPeriod( Period period );
-    
-    int deleteByDataElementCategoryOptionCombo( DataElementCategoryOptionCombo optionCombo );
+    /**
+     * Deletes all DataValueAudits for the given DataElement.
+     *
+     * @param dataElement the DataElement for which the DataValueAudits should be deleted.
+     * @return the number of deleted DataValueAudits.
+     */
+    int deleteDataValueAuditByDataElement( DataElement dataElement );
+
+    /**
+     * Deletes all DataValueAudits for the given Period.
+     *
+     * @param period the Period for which the DataValueAudits should be deleted.
+     * @return the number of deleted DataValueAudits.
+     */
+    int deleteDataValueAuditByPeriod( Period period );
+
+    /**
+     * Deletes all DataValues for the given OrganisationUnit.
+     *
+     * @param organisationUnit the OrganisationUnit for which the DataValueAudits should be deleted.
+     * @return the number of deleted DataValueAudits.
+     */
+    int deleteDataValueAuditByOrganisationUnit( OrganisationUnit organisationUnit );
+
+    /**
+     * Deletes all DataValues for the given DataElementCategoryOptionCombo.
+     *
+     * @param categoryOptionCombo the DataElementCategoryOptionCombo for which the DataValueAudits should be deleted.
+     * @return the number of deleted DataValueAudits.
+     */
+    int deleteDataValueAuditByCategoryOptionCombo( DataElementCategoryOptionCombo categoryOptionCombo );
 }

=== added file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/common/AuditTypeUserType.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/common/AuditTypeUserType.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/common/AuditTypeUserType.java	2014-07-09 13:43:43 +0000
@@ -0,0 +1,42 @@
+package org.hisp.dhis.common;
+
+/*
+ * Copyright (c) 2004-2014, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the HISP project nor the names of its contributors may
+ *   be used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import org.hisp.dhis.hibernate.EnumUserType;
+
+/**
+ * @author Halvdan Hoem Grelland
+ */
+public class AuditTypeUserType
+    extends EnumUserType<AuditType>
+{
+    public AuditTypeUserType()
+    {
+        super( AuditType.class );
+    }
+}

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DataValueAuditDeletionHandler.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DataValueAuditDeletionHandler.java	2014-07-02 16:51:32 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DataValueAuditDeletionHandler.java	2014-07-10 15:06:47 +0000
@@ -66,24 +66,24 @@
     @Override
     public void deleteOrganisationUnit( OrganisationUnit unit )
     {
-    	dataValueAuditService.deleteDataValueAuditBySource( unit );
+        dataValueAuditService.deleteDataValueAuditByOrganisationUnit( unit );
     }
 
     @Override
     public void deleteDataElement( DataElement dataElement )
     {
-    	dataValueAuditService.deleteDataValueAuditByDataElement( dataElement );
+        dataValueAuditService.deleteDataValueAuditByDataElement( dataElement );
     }
 
     @Override
     public void deletePeriod( Period period )
     {
-    	dataValueAuditService.deleteByPeriod( period );
+        dataValueAuditService.deleteDataValueAuditByPeriod( period );
     }
     
     @Override
     public void deleteDataElementCategoryOptionCombo( DataElementCategoryOptionCombo categoryOptionCombo)
     {
-    	dataValueAuditService.deleteByDataElementCategoryOptionCombo( categoryOptionCombo );
+        dataValueAuditService.deleteDataValueAuditByCategoryOptionCombo( categoryOptionCombo );
     }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueAuditService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueAuditService.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueAuditService.java	2014-07-09 13:43:43 +0000
@@ -34,9 +34,11 @@
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.period.Period;
+import org.springframework.transaction.annotation.Transactional;
 
 /**
  * @author Quang Nguyen
+ * @author Halvdan Hoem Grelland
  */
 public class DefaultDataValueAuditService
     implements DataValueAuditService
@@ -56,48 +58,57 @@
     // DataValueAuditService implementation
     // -------------------------------------------------------------------------
 
+    @Override
     public void addDataValueAudit( DataValueAudit dataValueAudit )
     {
         dataValueAuditStore.addDataValueAudit( dataValueAudit );
     }
 
+    @Override
+    @Transactional
     public void deleteDataValueAudit( DataValueAudit dataValueAudit )
     {
         dataValueAuditStore.deleteDataValueAudit( dataValueAudit );
     }
 
-    public int deleteDataValueAuditByDataValue( DataValue dataValue )
-    {
-        return dataValueAuditStore.deleteDataValueAuditByDataValue( dataValue );
-    }
-
-    public Collection<DataValueAudit> getDataValueAuditByDataValue( DataValue dataValue )
-    {
-        return dataValueAuditStore.getDataValueAuditByDataValue( dataValue );
-    }
-
-    public Collection<DataValueAudit> getAll()
-    {
-        return dataValueAuditStore.getAll();
-    }
-
-    public void deleteDataValueAuditBySource( OrganisationUnit unit )
-    {
-        dataValueAuditStore.deleteDataValueAuditBySource( unit );
-    }
-
-    public void deleteDataValueAuditByDataElement( DataElement dataElement )
-    {
-        dataValueAuditStore.deleteDataValueAuditByDataElement( dataElement );
-        
-    }
-    public void deleteByDataElementCategoryOptionCombo( DataElementCategoryOptionCombo optionCombo )
-    {
-    	dataValueAuditStore.deleteByDataElementCategoryOptionCombo( optionCombo );
-    }
-    
-    public void deleteByPeriod( Period period )
-    {
-    	dataValueAuditStore.deleteByPeriod( period );
+    @Override
+    public Collection<DataValueAudit> getDataValueAudits( DataValue dataValue )
+    {
+        return dataValueAuditStore.getDataValueAudits( dataValue );
+    }
+
+    @Override
+    public Collection<DataValueAudit> getDataValueAudits( DataElement dataElement, Period period,
+        OrganisationUnit organisationUnit, DataElementCategoryOptionCombo categoryOptionCombo )
+    {
+        return dataValueAuditStore.getDataValueAudits( dataElement, period, organisationUnit, categoryOptionCombo );
+    }
+
+    @Override
+    @Transactional
+    public int deleteDataValueAuditByDataElement( DataElement dataElement )
+    {
+        return dataValueAuditStore.deleteDataValueAuditByDataElement( dataElement );
+    }
+
+    @Override
+    @Transactional
+    public int deleteDataValueAuditByPeriod( Period period )
+    {
+        return dataValueAuditStore.deleteDataValueAuditByPeriod( period );
+    }
+
+    @Override
+    @Transactional
+    public int deleteDataValueAuditByOrganisationUnit( OrganisationUnit organisationUnit )
+    {
+        return dataValueAuditStore.deleteDataValueAuditByOrganisationUnit( organisationUnit );
+    }
+
+    @Override
+    @Transactional
+    public int deleteDataValueAuditByCategoryOptionCombo( DataElementCategoryOptionCombo categoryOptionCombo )
+    {
+        return dataValueAuditStore.deleteDataValueAuditByCategoryOptionCombo( categoryOptionCombo );
     }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java	2014-07-07 11:00:35 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java	2014-07-11 10:21:13 +0000
@@ -38,6 +38,7 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.hisp.dhis.common.AuditType;
 import org.hisp.dhis.common.MapMap;
 import org.hisp.dhis.dataelement.CategoryOptionGroup;
 import org.hisp.dhis.dataelement.DataElement;
@@ -48,10 +49,12 @@
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.period.Period;
 import org.hisp.dhis.period.PeriodType;
+import org.hisp.dhis.user.CurrentUserService;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
  * @author Kristian Nordal
+ * @author Halvdan Hoem Grelland
  */
 @Transactional
 public class DefaultDataValueService
@@ -70,6 +73,20 @@
         this.dataValueStore = dataValueStore;
     }
 
+    private DataValueAuditService dataValueAuditService;
+
+    public void setDataValueAuditService( DataValueAuditService dataValueAuditService )
+    {
+        this.dataValueAuditService = dataValueAuditService;
+    }
+
+    private CurrentUserService currentUserService;
+
+    public void setCurrentUserService( CurrentUserService currentUserService )
+    {
+        this.currentUserService = currentUserService;
+    }
+
     private DataElementCategoryService categoryService;
 
     public void setCategoryService( DataElementCategoryService categoryService )
@@ -130,6 +147,7 @@
         return true;
     }
 
+    @Transactional
     public void updateDataValue( DataValue dataValue )
     {
         if ( dataValue.isNullValue() || dataValueIsZeroAndInsignificant( dataValue.getValue(), dataValue.getDataElement() ) )
@@ -138,6 +156,10 @@
         }
         else if ( dataValueIsValid( dataValue.getValue(), dataValue.getDataElement() ) == null )
         {
+            DataValueAudit dataValueAudit = new DataValueAudit( dataValue, dataValue.getAuditValue(),
+                dataValue.getStoredBy(), new Date(), AuditType.UPDATE );
+
+            dataValueAuditService.addDataValueAudit( dataValueAudit );
             dataValueStore.updateDataValue( dataValue );
         }
     }
@@ -145,6 +167,11 @@
     @Transactional
     public void deleteDataValue( DataValue dataValue )
     {
+        DataValueAudit dataValueAudit = new DataValueAudit( dataValue, dataValue.getAuditValue(),
+            currentUserService.getCurrentUsername(), new Date(), AuditType.DELETE );
+
+        dataValueAuditService.addDataValueAudit( dataValueAudit );
+
         dataValueStore.deleteDataValue( dataValue );
     }
 

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueAuditStore.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueAuditStore.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueAuditStore.java	2014-07-10 15:06:47 +0000
@@ -28,12 +28,14 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import java.util.ArrayList;
 import java.util.Collection;
 
 import org.hibernate.Criteria;
 import org.hibernate.Query;
 import org.hibernate.Session;
 import org.hibernate.SessionFactory;
+import org.hibernate.criterion.Order;
 import org.hibernate.criterion.Restrictions;
 import org.hisp.dhis.dataelement.DataElement;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
@@ -42,10 +44,11 @@
 import org.hisp.dhis.datavalue.DataValueAuditStore;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.period.Period;
+import org.hisp.dhis.period.PeriodStore;
 
 /**
  * @author Quang Nguyen
- * @version Mar 30, 2010 10:42:16 PM
+ * @author Halvdan Hoem Grelland
  */
 public class HibernateDataValueAuditStore
     implements DataValueAuditStore
@@ -61,10 +64,18 @@
         this.sessionFactory = sessionFactory;
     }
 
+    private PeriodStore periodStore;
+
+    public void setPeriodStore( PeriodStore periodStore )
+    {
+        this.periodStore = periodStore;
+    }
+
     // -------------------------------------------------------------------------
     // DataValueAuditStore implementation
     // -------------------------------------------------------------------------
 
+    @Override
     public void addDataValueAudit( DataValueAudit dataValueAudit )
     {
         Session session = sessionFactory.getCurrentSession();
@@ -72,6 +83,38 @@
         session.save( dataValueAudit );
     }
 
+
+    @Override
+    public Collection<DataValueAudit> getDataValueAudits( DataValue dataValue )
+    {
+        return getDataValueAudits( dataValue.getDataElement(), dataValue.getPeriod(),
+            dataValue.getSource(), dataValue.getCategoryOptionCombo() );
+    }
+
+    @Override
+    public Collection<DataValueAudit> getDataValueAudits( DataElement dataElement, Period period,
+        OrganisationUnit organisationUnit, DataElementCategoryOptionCombo categoryOptionCombo )
+    {
+        Session session = sessionFactory.getCurrentSession();
+
+        Period storedPeriod = periodStore.reloadPeriod( period );
+
+        if( storedPeriod == null )
+        {
+            return new ArrayList<DataValueAudit>();
+        }
+
+        Criteria criteria = session.createCriteria( DataValueAudit.class )
+            .add( Restrictions.eq( "dataElement", dataElement ) )
+            .add( Restrictions.eq( "period", storedPeriod ) )
+            .add( Restrictions.eq( "organisationUnit", organisationUnit ) )
+            .add( Restrictions.eq( "categoryOptionCombo", categoryOptionCombo ) )
+            .addOrder( Order.desc( "timestamp" ) );
+
+        return criteria.list();
+    }
+
+    @Override
     public void deleteDataValueAudit( DataValueAudit dataValueAudit )
     {
         Session session = sessionFactory.getCurrentSession();
@@ -79,82 +122,45 @@
         session.delete( dataValueAudit );
     }
 
-    public int deleteDataValueAuditByDataValue( DataValue dataValue )
-    {
-        Session session = sessionFactory.getCurrentSession();
-
-        Query query = session.createQuery( "delete DataValueAudit where dataValue = :dataValue" );
-        query.setEntity( "dataValue", dataValue );
-
-        return query.executeUpdate();
-    }
-
-    public void deleteDataValueAuditBySource( OrganisationUnit source )
-    {
-        for ( DataValueAudit each : getAll() )
-        {
-            if ( each.getDataValue().getSource().equals( source ) )
-            {
-                deleteDataValueAudit( each );
-            }
-        }
-    }
-
-    @SuppressWarnings( "unchecked" )
-    public Collection<DataValueAudit> getDataValueAuditByDataValue( DataValue dataValue )
-    {
-        Session session = sessionFactory.getCurrentSession();
-
-        Criteria criteria = session.createCriteria( DataValueAudit.class );
-        criteria.add( Restrictions.eq( "dataValue", dataValue ) );
-
-        return criteria.list();
-    }
-
-    @SuppressWarnings( "unchecked" )
-    public Collection<DataValueAudit> getAll()
-    {
-        Session session = sessionFactory.getCurrentSession();
-
-        Criteria criteria = session.createCriteria( DataValueAudit.class );
-
-        return criteria.list();
-    }
-
-    public void deleteDataValueAuditByDataElement( DataElement dataElement )
-    {
-        for ( DataValueAudit each : getAll() )
-        {
-            if ( each.getDataValue().getDataElement().equals( dataElement ) )
-            {
-                deleteDataValueAudit( each );
-            }
-        }
-    }
-
-    public int deleteByPeriod( Period period )
-    {
-        /*
-        Session session = sessionFactory.getCurrentSession();
-
-        Query query = session.createQuery( "delete DataValueAudit where period = :period" );
-        query.setEntity( "period", period );
-
-        return query.executeUpdate();
-        */
-        return 0;
-    }
-
-    public int deleteByDataElementCategoryOptionCombo( DataElementCategoryOptionCombo optionCombo )
-    {
-        /*
-        Session session = sessionFactory.getCurrentSession();
-
-        Query query = session.createQuery( "delete DataValueAudit where optionCombo = :optionCombo" );
-        query.setEntity( "optionCombo", optionCombo );
-
-        return query.executeUpdate();
-        */
-        return 0;
+    @Override
+    public int deleteDataValueAuditByDataElement( DataElement dataElement )
+    {
+        Query query = sessionFactory.getCurrentSession()
+            .createQuery( "DELETE DataValueAudit WHERE dataElement = :dataElement" )
+            .setEntity( "dataElement", dataElement );
+
+        return query.executeUpdate();
+    }
+
+    @Override
+    public int deleteDataValueAuditByPeriod( Period period )
+    {
+        Period storedPeriod = periodStore.reloadPeriod( period );
+
+        Query query = sessionFactory.getCurrentSession()
+            .createQuery( "DELETE DataValueAudit WHERE period = :period" )
+            .setEntity( "period", storedPeriod );
+
+        return query.executeUpdate();
+    }
+
+    @Override
+    public int deleteDataValueAuditByOrganisationUnit( OrganisationUnit organisationUnit )
+    {
+        Query query = sessionFactory.getCurrentSession()
+            .createQuery( "DELETE DataValueAudit WHERE organisationUnit = :organisationUnit" )
+            .setEntity( "organisationUnit", organisationUnit );
+
+        return query.executeUpdate();
+    }
+
+    @Override
+    public int deleteDataValueAuditByCategoryOptionCombo( DataElementCategoryOptionCombo categoryOptionCombo )
+    {
+        Query query = sessionFactory.getCurrentSession()
+            .createQuery( "DELETE DataValueAudit WHERE categoryOptionCombo = :categoryOptionCombo" )
+            .setEntity( "categoryOptionCombo", categoryOptionCombo );
+
+        return query.executeUpdate();
     }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2014-07-06 17:12:17 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2014-07-11 10:21:13 +0000
@@ -251,6 +251,7 @@
 
   <bean id="org.hisp.dhis.datavalue.DataValueAuditStore" class="org.hisp.dhis.datavalue.hibernate.HibernateDataValueAuditStore">
     <property name="sessionFactory" ref="sessionFactory" />
+    <property name="periodStore" ref="org.hisp.dhis.period.PeriodStore" />
   </bean>
 
   <bean id="org.hisp.dhis.user.UserStore" class="org.hisp.dhis.user.hibernate.HibernateUserStore">
@@ -412,6 +413,8 @@
 
   <bean id="org.hisp.dhis.datavalue.DataValueService" class="org.hisp.dhis.datavalue.DefaultDataValueService">
     <property name="dataValueStore" ref="org.hisp.dhis.datavalue.DataValueStore" />
+    <property name="dataValueAuditService" ref="org.hisp.dhis.datavalue.DataValueAuditService" />
+    <property name="currentUserService" ref="org.hisp.dhis.user.CurrentUserService" />
     <property name="categoryService" ref="org.hisp.dhis.dataelement.DataElementCategoryService" />
   </bean>
 

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/datavalue/hibernate/DataValueAudit.hbm.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/datavalue/hibernate/DataValueAudit.hbm.xml	2013-12-19 18:12:57 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/datavalue/hibernate/DataValueAudit.hbm.xml	2014-07-09 13:43:43 +0000
@@ -4,27 +4,31 @@
   "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd";>
 
 <hibernate-mapping>
-  <class name="org.hisp.dhis.datavalue.DataValueAudit" table="datavalue_audit">
+  <class name="org.hisp.dhis.datavalue.DataValueAudit" table="datavalueaudit">
 
-    <id name="id">
-      <generator class="native" />
+    <id name="id" column="datavalueauditid">
+      <generator class="native"/>
     </id>
 
-    <many-to-one name="dataValue" class="org.hisp.dhis.datavalue.DataValue" foreign-key="fk_datavalueaudit_datavalue">
-      <column name="dataelementid" />
-      <column name="periodid" />
-      <column name="sourceid" />
-      <column name="categoryoptioncomboid" />
-      <column name="attributeoptioncomboid" />
-    </many-to-one>
-
-    <property name="value" />
-
-    <property name="storedBy" column="storedby" length="31" />
-
-    <property name="timestamp" column="lastupdated" type="timestamp" />
-
-    <property name="comment" length="360" />
+    <many-to-one name="dataElement" class="org.hisp.dhis.dataelement.DataElement" column="dataelementid"
+                     foreign-key="fk_datavalueaudit_dataelementid" not-null="true"/>
+
+    <many-to-one name="period" class="org.hisp.dhis.period.Period" column="periodid"
+                     foreign-key="fk_datavalueaudit_periodid" not-null="true"/>
+
+    <many-to-one name="organisationUnit" class="org.hisp.dhis.organisationunit.OrganisationUnit" column="organisationunitid"
+                     foreign-key="fk_datavalueaudit_organisationunitid" not-null="true"/>
+
+    <many-to-one name="categoryOptionCombo" class="org.hisp.dhis.dataelement.DataElementCategoryOptionCombo"
+                     column="categoryoptioncomboid" foreign-key="fk_datavalueaudit_categoryoptioncomboid" not-null="true"/>
+
+    <property name="value" length="255" />
+
+    <property name="timestamp" column="timestamp" type="timestamp" not-null="true" index="id_datavalueaudit_timestamp"/>
+
+    <property name="modifiedBy" column="modifiedby" length="100"/>
+
+    <property name="auditType" column="audittype" type="org.hisp.dhis.common.AuditTypeUserType" not-null="true" />
 
   </class>
 </hibernate-mapping>

=== added file 'dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/datavalue/DataValueAuditServiceTest.java'
--- dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/datavalue/DataValueAuditServiceTest.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/datavalue/DataValueAuditServiceTest.java	2014-07-10 15:06:47 +0000
@@ -0,0 +1,365 @@
+package org.hisp.dhis.datavalue;
+
+/*
+ * Copyright (c) 2004-2014, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * Neither the name of the HISP project nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import org.hisp.dhis.DhisTest;
+import org.hisp.dhis.common.AuditType;
+import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
+import org.hisp.dhis.dataelement.DataElementCategoryService;
+import org.hisp.dhis.dataelement.DataElementService;
+import org.hisp.dhis.organisationunit.OrganisationUnit;
+import org.hisp.dhis.organisationunit.OrganisationUnitService;
+import org.hisp.dhis.period.Period;
+import org.hisp.dhis.period.PeriodService;
+import org.junit.Test;
+
+import java.util.Collection;
+import java.util.Date;
+
+import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Created by Halvdan Hoem Grelland
+ */
+public class DataValueAuditServiceTest
+        extends DhisTest
+{
+    // -------------------------------------------------------------------------
+    // Supporting data
+    // -------------------------------------------------------------------------
+
+    private DataElement dataElementA;
+
+    private DataElement dataElementB;
+
+    private DataElement dataElementC;
+
+    private DataElement dataElementD;
+
+    private DataElementCategoryOptionCombo optionCombo;
+
+    private Period periodA;
+
+    private Period periodB;
+
+    private Period periodC;
+
+    private Period periodD;
+
+    private OrganisationUnit orgUnitA;
+
+    private OrganisationUnit orgUnitB;
+
+    private OrganisationUnit orgUnitC;
+
+    private OrganisationUnit orgUnitD;
+
+    private DataValue dataValueA;
+
+    private DataValue dataValueB;
+
+    private DataValue dataValueC;
+
+    private DataValue dataValueD;
+
+    // -------------------------------------------------------------------------
+    // Set up/tear down
+    // -------------------------------------------------------------------------
+
+    @Override
+    public void setUpTest()
+        throws Exception
+    {
+        dataValueAuditService = (DataValueAuditService) getBean( DataValueAuditService.ID );
+
+        dataValueService = (DataValueService) getBean( DataValueService.ID );
+
+        dataElementService = (DataElementService) getBean( DataElementService.ID );
+
+        categoryService = (DataElementCategoryService) getBean( DataElementCategoryService.ID );
+
+        periodService = (PeriodService) getBean( PeriodService.ID );
+
+        organisationUnitService = (OrganisationUnitService) getBean( OrganisationUnitService.ID );
+
+        // ---------------------------------------------------------------------
+        // Add supporting data
+        // ---------------------------------------------------------------------
+
+        dataElementA = createDataElement( 'A' );
+        dataElementB = createDataElement( 'B' );
+        dataElementC = createDataElement( 'C' );
+        dataElementD = createDataElement( 'D' );
+
+        dataElementService.addDataElement( dataElementA );
+        dataElementService.addDataElement( dataElementB );
+        dataElementService.addDataElement( dataElementC );
+        dataElementService.addDataElement( dataElementD );
+
+        periodA = createPeriod( getDay( 5 ), getDay( 6 ) );
+        periodB = createPeriod( getDay( 6 ), getDay( 7 ) );
+        periodC = createPeriod( getDay( 7 ), getDay( 8 ) );
+        periodD = createPeriod( getDay( 8 ), getDay( 9 ) );
+
+        periodService.addPeriod( periodA );
+        periodService.addPeriod( periodB );
+        periodService.addPeriod( periodC );
+        periodService.addPeriod( periodD );
+
+        orgUnitA = createOrganisationUnit( 'A' );
+        orgUnitB = createOrganisationUnit( 'B' );
+        orgUnitC = createOrganisationUnit( 'C' );
+        orgUnitD = createOrganisationUnit( 'D' );
+
+        organisationUnitService.addOrganisationUnit( orgUnitA );
+        organisationUnitService.addOrganisationUnit( orgUnitB );
+        organisationUnitService.addOrganisationUnit( orgUnitC );
+        organisationUnitService.addOrganisationUnit( orgUnitD );
+
+        optionCombo = categoryService.getDefaultDataElementCategoryOptionCombo();
+        categoryService.addDataElementCategoryOptionCombo( optionCombo );
+
+        dataValueA = createDataValue( dataElementA, periodA, orgUnitA,  "1", optionCombo );
+        dataValueB = createDataValue( dataElementB, periodB, orgUnitB,  "2", optionCombo );
+        dataValueC = createDataValue( dataElementC, periodC, orgUnitC,  "3", optionCombo );
+        dataValueD = createDataValue( dataElementD, periodD, orgUnitD,  "4", optionCombo );
+
+        dataValueService.addDataValue( dataValueA );
+        dataValueService.addDataValue( dataValueB );
+        dataValueService.addDataValue( dataValueC );
+        dataValueService.addDataValue( dataValueD );
+    }
+
+    @Override
+    public boolean emptyDatabaseAfterTest()
+    {
+        return true;
+    }
+
+    // -------------------------------------------------------------------------
+    // Basic DataValueAudit
+    // -------------------------------------------------------------------------
+
+    @Test
+    public void testAddDataValueAudit()
+    {
+        Date now = new Date();
+
+        DataValueAudit dataValueAuditA = new DataValueAudit( dataValueA, dataValueA.getValue(), dataValueA.getStoredBy(),
+            now, AuditType.UPDATE );
+        DataValueAudit dataValueAuditB = new DataValueAudit( dataValueB, dataValueB.getValue(), dataValueB.getStoredBy(),
+            now, AuditType.UPDATE );
+
+        dataValueAuditService.addDataValueAudit( dataValueAuditA );
+        dataValueAuditService.addDataValueAudit( dataValueAuditB );
+
+        Collection<DataValueAudit> audits = dataValueAuditService.getDataValueAudits( dataValueA );
+        assertNotNull( audits );
+        assertTrue( audits.contains( dataValueAuditA ) );
+    }
+
+    @Test
+    public void testDeleteDataValueAudit()
+    {
+        Date now = new Date();
+
+        DataValueAudit dataValueAuditA = new DataValueAudit( dataValueA, dataValueA.getValue(), dataValueA.getStoredBy(),
+            now, AuditType.UPDATE );
+        DataValueAudit dataValueAuditB = new DataValueAudit( dataValueA, dataValueA.getValue(), dataValueA.getStoredBy(),
+            now, AuditType.DELETE );
+
+        dataValueAuditService.addDataValueAudit( dataValueAuditA );
+        dataValueAuditService.addDataValueAudit( dataValueAuditB );
+
+        Collection<DataValueAudit> audits = dataValueAuditService.getDataValueAudits( dataValueA );
+
+        assertEquals( 2, audits.size() );
+
+        dataValueAuditService.deleteDataValueAudit( dataValueAuditA );
+
+        audits = dataValueAuditService.getDataValueAudits( dataValueA );
+
+        assertNotNull( audits );
+        assertEquals( 1, audits.size() );
+        assertTrue( audits.contains( dataValueAuditB ) );
+    }
+
+    // -------------------------------------------------------------------------
+    // DeletionHandler methods
+    // -------------------------------------------------------------------------
+
+    @Test
+    public void testDeleteDataValueAuditByDataElement()
+    {
+        Date now = new Date();
+
+        DataValueAudit dataValueAuditA = new DataValueAudit( dataValueA, dataValueA.getValue(), dataValueA.getStoredBy(),
+            now, AuditType.UPDATE );
+        DataValueAudit dataValueAuditB = new DataValueAudit( dataValueB, dataValueB.getValue(), dataValueB.getStoredBy(),
+            now, AuditType.UPDATE );
+
+        DataValueAudit dataValueAuditC1 = new DataValueAudit( dataValueC, dataValueC.getValue(), dataValueC.getStoredBy(),
+            now, AuditType.UPDATE );
+        DataValueAudit dataValueAuditC2 = new DataValueAudit( dataValueC, dataValueC.getValue(), dataValueC.getStoredBy(),
+            now, AuditType.UPDATE );
+
+        dataValueAuditService.addDataValueAudit( dataValueAuditA );
+        dataValueAuditService.addDataValueAudit( dataValueAuditB );
+
+        dataValueAuditService.addDataValueAudit( dataValueAuditC1 );
+        dataValueAuditService.addDataValueAudit( dataValueAuditC2 );
+
+        dataValueAuditService.deleteDataValueAuditByDataElement( dataValueAuditA.getDataElement() );
+
+        Collection<DataValueAudit> audits = dataValueAuditService.getDataValueAudits( dataValueA );
+
+        assertTrue( audits.isEmpty() );
+
+        audits = dataValueAuditService.getDataValueAudits( dataValueB );
+        assertTrue( audits.contains( dataValueAuditB ) );
+
+        dataValueAuditService.deleteDataValueAuditByDataElement( dataValueAuditC1.getDataElement() );
+
+        audits = dataValueAuditService.getDataValueAudits( dataValueD );
+
+        assertFalse( audits.contains( dataValueAuditC1 ) );
+        assertFalse( audits.contains( dataValueAuditC2 ) );
+    }
+
+    @Test
+    public void testDeleteDataValueAuditByPeriod()
+    {
+        Date now = new Date();
+
+        DataValueAudit dataValueAuditA = new DataValueAudit( dataValueA, dataValueA.getValue(), dataValueA.getStoredBy(),
+            now, AuditType.UPDATE );
+        DataValueAudit dataValueAuditB = new DataValueAudit( dataValueB, dataValueB.getValue(), dataValueB.getStoredBy(),
+            now, AuditType.UPDATE );
+
+        DataValueAudit dataValueAuditC1 = new DataValueAudit( dataValueC, dataValueC.getValue(), dataValueC.getStoredBy(),
+            now, AuditType.UPDATE );
+        DataValueAudit dataValueAuditC2 = new DataValueAudit( dataValueC, dataValueC.getValue(), dataValueC.getStoredBy(),
+            now, AuditType.UPDATE );
+
+        dataValueAuditService.addDataValueAudit( dataValueAuditA );
+        dataValueAuditService.addDataValueAudit( dataValueAuditB );
+
+        dataValueAuditService.addDataValueAudit( dataValueAuditC1 );
+        dataValueAuditService.addDataValueAudit( dataValueAuditC2 );
+
+        dataValueAuditService.deleteDataValueAuditByPeriod( dataValueAuditA.getPeriod() );
+
+        Collection<DataValueAudit> audits = dataValueAuditService.getDataValueAudits( dataValueA );
+
+        assertTrue( audits.isEmpty() );
+
+        dataValueAuditService.deleteDataValueAuditByPeriod( dataValueAuditC1.getPeriod() );
+
+        audits = dataValueAuditService.getDataValueAudits( dataValueB );
+
+        assertTrue( audits.contains( dataValueAuditB ) );
+
+        audits = dataValueAuditService.getDataValueAudits( dataValueC );
+
+        assertTrue( audits.isEmpty() );
+    }
+
+    @Test
+    public void testDeleteDataValueAuditByOrganisationUnit()
+    {
+        Date now = new Date();
+
+        DataValueAudit dataValueAuditA = new DataValueAudit( dataValueA, dataValueA.getValue(), dataValueA.getStoredBy(),
+            now, AuditType.UPDATE );
+        DataValueAudit dataValueAuditB = new DataValueAudit( dataValueB, dataValueB.getValue(), dataValueB.getStoredBy(),
+            now, AuditType.UPDATE );
+
+        DataValueAudit dataValueAuditC1 = new DataValueAudit( dataValueC, dataValueC.getValue(), dataValueC.getStoredBy(),
+            now, AuditType.UPDATE );
+        DataValueAudit dataValueAuditC2 = new DataValueAudit( dataValueC, dataValueC.getValue(), dataValueC.getStoredBy(),
+            now, AuditType.UPDATE );
+
+        dataValueAuditService.addDataValueAudit( dataValueAuditA );
+        dataValueAuditService.addDataValueAudit( dataValueAuditB );
+
+        dataValueAuditService.addDataValueAudit( dataValueAuditC1 );
+        dataValueAuditService.addDataValueAudit( dataValueAuditC2 );
+
+        dataValueAuditService.deleteDataValueAuditByOrganisationUnit( dataValueAuditA.getOrganisationUnit() );
+
+        Collection<DataValueAudit> audits = dataValueAuditService.getDataValueAudits( dataValueA );
+
+        assertTrue( audits.isEmpty() );
+
+        dataValueAuditService.deleteDataValueAuditByOrganisationUnit( dataValueAuditC1.getOrganisationUnit() );
+
+        audits = dataValueAuditService.getDataValueAudits( dataValueC );
+
+        assertTrue( audits.isEmpty() );
+
+        audits = dataValueAuditService.getDataValueAudits( dataValueB );
+
+        assertNotNull( dataValueAuditB );
+        assertNotNull( audits );
+
+        assertTrue( audits.contains( dataValueAuditB ) );
+    }
+
+    @Test
+    public void testDeleteDataValueAuditByCategoryOptionCombo()
+    {
+        Date now = new Date();
+
+        DataValueAudit dataValueAuditA = new DataValueAudit( dataValueA, dataValueA.getValue(), dataValueA.getStoredBy(),
+            now, AuditType.UPDATE );
+        DataValueAudit dataValueAuditB = new DataValueAudit( dataValueB, dataValueB.getValue(), dataValueB.getStoredBy(),
+            now, AuditType.UPDATE );
+        DataValueAudit dataValueAuditC = new DataValueAudit( dataValueC, dataValueC.getValue(), dataValueC.getStoredBy(),
+            now, AuditType.UPDATE );
+
+        dataValueAuditService.addDataValueAudit( dataValueAuditA );
+        dataValueAuditService.addDataValueAudit( dataValueAuditB );
+        dataValueAuditService.addDataValueAudit( dataValueAuditC );
+
+        dataValueAuditService.deleteDataValueAuditByCategoryOptionCombo( optionCombo );
+
+        Collection<DataValueAudit> audits = dataValueAuditService.getDataValueAudits( dataValueA );
+        assertTrue( audits.isEmpty() );
+
+        audits = dataValueAuditService.getDataValueAudits( dataValueB );
+        assertTrue( audits.isEmpty() );
+
+        audits = dataValueAuditService.getDataValueAudits( dataValueC );
+        assertTrue( audits.isEmpty() );
+    }
+}

=== modified file 'dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java'
--- dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java	2014-04-27 21:52:33 +0000
+++ dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java	2014-07-08 17:16:48 +0000
@@ -82,6 +82,7 @@
         emptyTable( "sqlview" );
 
         emptyTable( "datavalue_audit" );
+        emptyTable( "datavalueaudit" );
         emptyTable( "datavalue" );
         emptyTable( "completedatasetregistration" );
 

=== modified file 'dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java'
--- dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java	2014-06-30 18:27:25 +0000
+++ dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java	2014-07-08 17:16:48 +0000
@@ -79,6 +79,7 @@
 import org.hisp.dhis.dataset.DataSetService;
 import org.hisp.dhis.dataset.SectionService;
 import org.hisp.dhis.datavalue.DataValue;
+import org.hisp.dhis.datavalue.DataValueAuditService;
 import org.hisp.dhis.datavalue.DataValueService;
 import org.hisp.dhis.expression.Expression;
 import org.hisp.dhis.expression.ExpressionService;
@@ -198,6 +199,8 @@
 
     protected DataValueService dataValueService;
 
+    protected DataValueAuditService dataValueAuditService;
+
     protected ResourceTableService resourceTableService;
 
     protected MappingService mappingService;

=== modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/GetHistoryAction.java'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/GetHistoryAction.java	2014-05-22 12:40:24 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/GetHistoryAction.java	2014-07-10 15:06:47 +0000
@@ -55,6 +55,7 @@
 
 /**
  * @author Torgeir Lorange Ostby
+ * @author Halvdan Hoem Grelland
  */
 public class GetHistoryAction
     implements Action
@@ -257,7 +258,9 @@
         OrganisationUnit organisationUnit = organisationUnitService.getOrganisationUnit( organisationUnitId );
 
         DataElementCategoryOptionCombo attributeOptionCombo = inputUtils.getAttributeOptionCombo( ServletActionContext.getResponse(), cc, cp );
-        
+
+        dataValueAudits = dataValueAuditService.getDataValueAudits( dataElement, period, organisationUnit, categoryOptionCombo );
+
         dataValue = dataValueService.getDataValue( dataElement, period, organisationUnit, categoryOptionCombo, attributeOptionCombo );
 
         if ( dataValue != null )
@@ -272,8 +275,6 @@
 
         minMaxInvalid = !DataElement.VALUE_TYPE_INT.equals( dataElement.getType() );
 
-        dataValueAudits = dataValueAuditService.getDataValueAuditByDataValue( dataValue );
-
         commentOptionSet = dataElement.getCommentOptionSet();
         
         return SUCCESS;

=== modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/org/hisp/dhis/de/i18n_module.properties'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/org/hisp/dhis/de/i18n_module.properties	2014-06-27 16:27:09 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/org/hisp/dhis/de/i18n_module.properties	2014-07-10 15:06:47 +0000
@@ -8,6 +8,8 @@
 stored_by=Stored by
 stored_date=Stored date
 last_updated=Last updated
+update=Update
+delete=Delete
 no_value=No value
 maximum=Maximum
 organisation_unit=Organisation Unit
@@ -34,6 +36,7 @@
 min_limit=Min limit
 max_limit=Max limit
 dataelement_history=Data element history
+audit_trail=Audit trail
 nr=Nr
 min_max_limits=Min-max limits
 run_validation=Run validation
@@ -78,6 +81,8 @@
 select_period=Select period
 select_option=Select option
 change_from=change from
+modification=Modification
+modified_by=Modified by
 to=to
 on=On
 value=Value
@@ -113,4 +118,5 @@
 section=Section
 print_form=Print form
 print_blank_form=Print blank form
-instruction=Instruction
\ No newline at end of file
+instruction=Instruction
+there_is_no_audit_trail_for_this_value=There is no audit trail for this value

=== modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/history.vm'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/history.vm	2014-06-27 16:27:09 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/history.vm	2014-07-10 15:06:47 +0000
@@ -4,6 +4,10 @@
 
 $( document ).ready( function() {
     dhis2.de.insertCommentOptionSet( '$!{commentOptionSet.uid}' );
+
+    $( function() {
+        $("#historyTabs").tabs();
+    });
 } );
 </script>
 
@@ -75,32 +79,65 @@
                 </tr>
             </table>
         </td>
-	</tr>
-	<tr>
-		<!-- History chart -->
-		<td colspan="2">
-			#if( $historyInvalid )
-			<div class="historyHeader">$encoder.htmlEncode( $i18n.getString( "history_not_valid" ) )</div>	
-			#else
-			<div class="historyHeader">$encoder.htmlEncode( $i18n.getString( "dataelement_history" ) )</div>
-			<img id="historyChart" 
-			src="../api/charts/history/data.png?de=${dataElementHistory.dataElement.uid}&co=${dataElementHistory.optionCombo.uid}&ou=${dataElementHistory.organisationUnit.uid}&pe=${periodId}"/>
-			#end
-        </td>
-    </tr>
-    #if( $dataValueAuditSize > 0 )
-	<tr>
-		<!-- Data Value History -->
-		<td colspan="2">
-			<<div class="historyHeader">>$encoder.htmlEncode( $i18n.getString( "datavalue_history" ) )</div>
-			#foreach( $eachDataValueAudit in $dataValueAudits )
-				<p>
-					$encoder.htmlEncode( $i18n.getString( "on" ) ) $eachDataValueAudit.timeStamp,
-					$eachDataValueAudit.storedBy 
-					$encoder.htmlEncode( $i18n.getString( "change_from" ) ) $eachDataValueAudit.value
-				</p>
-			#end					
-        </td>
-    </tr>
-    #end
-</table>
+    </tr>
+    <tr>    <!-- History chart and audit -->
+        <td colspan="2">
+            <div id="historyTabs">
+                <ul>
+                    <li><a href="#historyTabs-chart">$i18n.getString( "dataelement_history" )</a></li>
+                    <li><a href="#historyTabs-audit">$i18n.getString( "audit_trail" )</a></li>
+                </ul>
+                <div id="historyTabs-chart">
+                    #if( $historyInvalid )
+                        $encoder.htmlEncode( $i18n.getString( "history_not_valid" ) )
+                    #else
+                        <img id="historyChart"
+                             src="../api/charts/history/data.png?de=${dataElementHistory.dataElement.uid}&co=${dataElementHistory.optionCombo.uid}&ou=${dataElementHistory.organisationUnit.uid}&pe=${periodId}"/>
+                    #end
+                </div>
+                <div id="historyTabs-audit">
+                    #if( $dataValueAuditSize > 0 )
+                        <table id="historyAuditTable">
+                            <thead>
+                                <tr>
+                                    <th>$encoder.htmlEncode( $i18n.getString( "on" ) )</th>
+                                    <th>$encoder.htmlEncode( $i18n.getString( "modified_by" ) )</th>
+                                    <th>$encoder.htmlEncode( $i18n.getString( "value" ) )</th>
+                                    <th>$encoder.htmlEncode( $i18n.getString( "modification" ) )</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                            #foreach( $dataValueAudit in $dataValueAudits )
+                                <tr>
+                                    <td>
+                                        $format.formatDateTime( $dataValueAudit.timestamp )
+                                    </td>
+                                    <td>
+                                        #if( $dataValueAudit.modifiedBy )
+                                            $dataValueAudit.modifiedBy
+                                        #else
+                                            <i>$encoder.htmlEncode( $i18n.getString( "no_value" ) )</i>
+                                        #end
+                                    </td>
+                                    <td>
+                                        #if( $dataValueAudit.value )
+                                            $dataValueAudit.value
+                                        #else
+                                            <i>$encoder.htmlEncode( $i18n.getString( "no_value" ) )</i>
+                                        #end
+                                    </td>
+                                    <td>
+                                        <i>$i18n.getString( $dataValueAudit.auditType.getValue() )</i>
+                                    </td>
+                                </tr>
+                            #end
+                            </tbody>
+                        </table>
+                    #else
+                        <i>$encoder.htmlEncode( $i18n.getString( "there_is_no_audit_trail_for_this_value" ) )</i>
+                    #end
+                </div>
+            </div>
+        </td>
+    </tr>
+</table>
\ No newline at end of file

=== modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/style/dhis-web-dataentry.css'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/style/dhis-web-dataentry.css	2014-07-08 14:52:55 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/style/dhis-web-dataentry.css	2014-07-10 15:06:47 +0000
@@ -57,7 +57,7 @@
   padding-right: 50px;
 }
 
-.hidden 
+.hidden
 {
   display: none;
 }
@@ -103,7 +103,7 @@
 .sectionFilter
 {
   padding: 5px;
-  border: solid 1px #f0f0f0; 
+  border: solid 1px #f0f0f0;
   outline: 0;
   color: #333;
   height: 14px;
@@ -135,3 +135,91 @@
   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.06);
 }
 
+/* jQuery UI tab style overrides for data value history view */
+
+#historyTabs
+{
+    padding: 0;
+    width:  545px;
+    height: 345px;
+}
+
+#historyTabs.ui-tabs.ui-widget.ui-widget-content
+{
+    border-width: 0;
+}
+
+#historyTabs .ui-tabs-nav
+{
+    border-width: 0px;
+    border-bottom-width: 1px;
+    border-bottom-color: inherit;
+    border-radius: 0;
+    background: transparent;
+    padding: 0;
+}
+
+#historyTabs.ui-widget-content
+{
+    background: none;
+}
+
+#historyTabs .ui-tabs-panel. ui-widget-content
+{
+    padding-top: 0.5em;
+    padding-left: 0.5em;
+    padding-right: 0.5em;
+    border-width: 0;
+}
+
+/* History audit table */
+
+#historyAuditTable
+{
+    width: 100%;
+    border-collapse: collapse;
+    text-align: left;
+    background-color: #F5F5F5;
+}
+
+#historyAuditTable tbody tr:nth-child( odd )
+{
+    background-color: #EDEDED;
+}
+
+#historyAuditTable td, #historyAuditTable th
+{
+    padding-top: 5px;
+    padding-bottom: 5px;
+    padding-left: 10px;
+}
+
+#historyAuditTable td
+{
+    border: 1px solid lightgrey;
+}
+
+#historyAuditTable th
+{
+    background-color: #DFEFFC;
+    border: 1px solid #C5DBEC;
+}
+
+#historyTabs-chart
+{
+    padding-bottom: 0;
+    padding-left: 0;
+}
+
+#historyTabs-audit
+{
+    height: 260px;
+    overflow: auto;
+    padding-top: 0;
+    margin-top: 20px;
+}
+
+#historyTabs table thead th .text
+{
+    position: absolute;
+}