diff --git a/api/src/org/labkey/api/reports/report/ReportDescriptor.java b/api/src/org/labkey/api/reports/report/ReportDescriptor.java index d2fa4d62722..b0e5050c611 100644 --- a/api/src/org/labkey/api/reports/report/ReportDescriptor.java +++ b/api/src/org/labkey/api/reports/report/ReportDescriptor.java @@ -288,6 +288,11 @@ public void setWasShared() _wasShared = true; } + public boolean isInheritable() + { + return (getFlags() & FLAG_INHERITABLE) != 0; + } + @Nullable public Integer getAuthor() { @@ -780,7 +785,7 @@ public boolean isInherited(Container c) // if the report has been configured to be shared to child folders or is in the shared folder then // flag it as inherited. // - if (((getFlags() & ReportDescriptor.FLAG_INHERITABLE) != 0) || (ContainerManager.getSharedContainer().equals(srcContainer))) + if (isInheritable() || (ContainerManager.getSharedContainer().equals(srcContainer))) { return !c.equals(srcContainer); } diff --git a/api/src/org/labkey/api/reports/report/view/ReportDesignBean.java b/api/src/org/labkey/api/reports/report/view/ReportDesignBean.java index 29ac5c50876..79e3f283336 100644 --- a/api/src/org/labkey/api/reports/report/view/ReportDesignBean.java +++ b/api/src/org/labkey/api/reports/report/view/ReportDesignBean.java @@ -292,8 +292,8 @@ void populateFromDescriptor(ReportDescriptor descriptor) setCached(BooleanUtils.toBoolean(descriptor.getProperty(ReportDescriptor.Prop.cached))); setReportAccess(descriptor.getAccess()); - setShareReport((descriptor.isShared())); - setInheritable((descriptor.getFlags() & ReportDescriptor.FLAG_INHERITABLE) != 0); + setShareReport(descriptor.isShared()); + setInheritable(descriptor.isInheritable()); setRedirectUrl(getViewContext().getActionURL().getParameter(ReportDescriptor.Prop.redirectUrl.name())); } } diff --git a/api/src/org/labkey/api/reports/report/view/ReportUtil.java b/api/src/org/labkey/api/reports/report/view/ReportUtil.java index f99c19b2de9..1328ff75ce1 100644 --- a/api/src/org/labkey/api/reports/report/view/ReportUtil.java +++ b/api/src/org/labkey/api/reports/report/view/ReportUtil.java @@ -325,7 +325,7 @@ public static boolean isReportInherited(Container c, Report report) return !ContainerManager.getSharedContainer().equals(c); } - if ((report.getDescriptor().getFlags() & ReportDescriptor.FLAG_INHERITABLE) != 0) + if (report.getDescriptor().isInheritable()) { Container reportContainer = ContainerManager.getForId(report.getDescriptor().getContainerId()); if (!c.equals(reportContainer)) diff --git a/assay/package-lock.json b/assay/package-lock.json index 8b2ea3e4b34..95267b7e561 100644 --- a/assay/package-lock.json +++ b/assay/package-lock.json @@ -8,7 +8,7 @@ "name": "assay", "version": "0.0.0", "dependencies": { - "@labkey/components": "5.7.0" + "@labkey/components": "5.11.0" }, "devDependencies": { "@labkey/build": "7.7.1", @@ -2528,9 +2528,9 @@ } }, "node_modules/@labkey/api": { - "version": "1.35.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.35.1.tgz", - "integrity": "sha512-oVThx06H9Hnz2TJ92Xg8RRJ6nAARA4IsjgTXFyJNDNWOYcy7Zpl9Th2iujiboyg4mppZ6WaWJl9cNVaGi7DFYg==" + "version": "1.35.2", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.35.2.tgz", + "integrity": "sha512-ksOQ9GpkWyuL04p3h0lr2JvKwRfYR9xw5AH6KP5ZsCiX3ScqaJA30hHM6Qy+RD9vm+HRQvWsa41U3van+qJtaA==" }, "node_modules/@labkey/build": { "version": "7.7.1", @@ -2569,12 +2569,12 @@ } }, "node_modules/@labkey/components": { - "version": "5.7.0", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-5.7.0.tgz", - "integrity": "sha512-pEIrN0ackB9R4b2jcxu8ozVPV1UGrOEkRZ1Q80VIXCUc/QmPcGlXCbpnW3dt+C7Y6SeUfGy+6JCoLihlo3V1xA==", + "version": "5.11.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-5.11.0.tgz", + "integrity": "sha512-t+J0owiGHlFxfVL6ACy49x3Qu5KAtpJ0x22KmV5tycguSIZlyYsqZzv4vsyMnnE6vzDQ9Nvukh7IPsd/jhtZEQ==", "dependencies": { "@hello-pangea/dnd": "16.6.0", - "@labkey/api": "1.35.1", + "@labkey/api": "1.35.2", "@testing-library/dom": "~10.4.0", "@testing-library/jest-dom": "~6.5.0", "@testing-library/react": "~16.0.1", diff --git a/assay/package.json b/assay/package.json index 67ce9b2ffe0..ea51c3a864c 100644 --- a/assay/package.json +++ b/assay/package.json @@ -12,7 +12,7 @@ "clean": "rimraf resources/web/assay/gen && rimraf resources/views/gen && rimraf resources/web/gen" }, "dependencies": { - "@labkey/components": "5.7.0" + "@labkey/components": "5.11.0" }, "devDependencies": { "@labkey/build": "7.7.1", diff --git a/core/package-lock.json b/core/package-lock.json index a69a4df76ae..303398cdc74 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -8,7 +8,7 @@ "name": "labkey-core", "version": "0.0.0", "dependencies": { - "@labkey/components": "5.10.0", + "@labkey/components": "5.11.0", "@labkey/themes": "1.3.3" }, "devDependencies": { @@ -3447,9 +3447,9 @@ } }, "node_modules/@labkey/api": { - "version": "1.35.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.35.1.tgz", - "integrity": "sha512-oVThx06H9Hnz2TJ92Xg8RRJ6nAARA4IsjgTXFyJNDNWOYcy7Zpl9Th2iujiboyg4mppZ6WaWJl9cNVaGi7DFYg==" + "version": "1.35.2", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.35.2.tgz", + "integrity": "sha512-ksOQ9GpkWyuL04p3h0lr2JvKwRfYR9xw5AH6KP5ZsCiX3ScqaJA30hHM6Qy+RD9vm+HRQvWsa41U3van+qJtaA==" }, "node_modules/@labkey/build": { "version": "7.7.1", @@ -3488,12 +3488,12 @@ } }, "node_modules/@labkey/components": { - "version": "5.10.0", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-5.10.0.tgz", - "integrity": "sha512-PYbBzAKLOJtn9PlaUxRjBwTnE/irLd6w8SHj2IM8YdJv8dH5dIibceR7rtejMVMEp5MmfNQf0LRfq3DIFpvyPA==", + "version": "5.11.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-5.11.0.tgz", + "integrity": "sha512-t+J0owiGHlFxfVL6ACy49x3Qu5KAtpJ0x22KmV5tycguSIZlyYsqZzv4vsyMnnE6vzDQ9Nvukh7IPsd/jhtZEQ==", "dependencies": { "@hello-pangea/dnd": "16.6.0", - "@labkey/api": "1.35.1", + "@labkey/api": "1.35.2", "@testing-library/dom": "~10.4.0", "@testing-library/jest-dom": "~6.5.0", "@testing-library/react": "~16.0.1", diff --git a/core/package.json b/core/package.json index cce17d9077d..429500c431f 100644 --- a/core/package.json +++ b/core/package.json @@ -54,7 +54,7 @@ } }, "dependencies": { - "@labkey/components": "5.10.0", + "@labkey/components": "5.11.0", "@labkey/themes": "1.3.3" }, "devDependencies": { diff --git a/core/webapp/vis/src/internal/D3Renderer.js b/core/webapp/vis/src/internal/D3Renderer.js index 8eb45520bb3..4201950e373 100644 --- a/core/webapp/vis/src/internal/D3Renderer.js +++ b/core/webapp/vis/src/internal/D3Renderer.js @@ -15,7 +15,7 @@ LABKEY.vis.internal.Axis = function() { tickRectCls, tickRectHeightOffset = 12, tickRectWidthOffset = 8, tickClick, axisSel, tickSel, textSel, gridLineSel, borderSel, grid, scalesList = [], gridLinesVisible = 'both', tickDigits, tickValues, tickMax, tickLabelMax, tickColor = '#000000', tickTextColor = '#000000', gridLineColor = '#DDDDDD', borderColor = '#000000', - tickPadding = 0, tickLength = 8, tickWidth = 1, tickOverlapRotation = 15, gridLineWidth = 1, borderWidth = 1, + tickPadding = 0, tickLength = 8, tickWidth = 1, tickOverlapRotation = 25, gridLineWidth = 1, borderWidth = 1, fontFamily = 'Roboto, arial, helvetica, sans-serif', fontSize = 11, adjustedStarts, adjustedEnds, xLogGutterBorder = 0, yLogGutterBorder = 0, yGutterXOffset = 0, xGutterYOffset = 0, addLogGutterLabel = false, xGridExtension = 0, yGridExtension = 0, logGutterSel; diff --git a/experiment/package-lock.json b/experiment/package-lock.json index 6bd79ddbf76..c2f73dc1d4b 100644 --- a/experiment/package-lock.json +++ b/experiment/package-lock.json @@ -8,7 +8,7 @@ "name": "experiment", "version": "0.0.0", "dependencies": { - "@labkey/components": "5.8.2" + "@labkey/components": "5.11.0" }, "devDependencies": { "@labkey/build": "7.7.1", @@ -3255,9 +3255,9 @@ } }, "node_modules/@labkey/api": { - "version": "1.35.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.35.1.tgz", - "integrity": "sha512-oVThx06H9Hnz2TJ92Xg8RRJ6nAARA4IsjgTXFyJNDNWOYcy7Zpl9Th2iujiboyg4mppZ6WaWJl9cNVaGi7DFYg==" + "version": "1.35.2", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.35.2.tgz", + "integrity": "sha512-ksOQ9GpkWyuL04p3h0lr2JvKwRfYR9xw5AH6KP5ZsCiX3ScqaJA30hHM6Qy+RD9vm+HRQvWsa41U3van+qJtaA==" }, "node_modules/@labkey/build": { "version": "7.7.1", @@ -3296,12 +3296,12 @@ } }, "node_modules/@labkey/components": { - "version": "5.8.2", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-5.8.2.tgz", - "integrity": "sha512-/DLBB/g0by5tVI0aycrM8gCg6sFHS3N/VayJrrSqqf+lMUfWtJK7n2seBLJL3bPxY2R7eatWzoHG/PS4zKYYXA==", + "version": "5.11.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-5.11.0.tgz", + "integrity": "sha512-t+J0owiGHlFxfVL6ACy49x3Qu5KAtpJ0x22KmV5tycguSIZlyYsqZzv4vsyMnnE6vzDQ9Nvukh7IPsd/jhtZEQ==", "dependencies": { "@hello-pangea/dnd": "16.6.0", - "@labkey/api": "1.35.1", + "@labkey/api": "1.35.2", "@testing-library/dom": "~10.4.0", "@testing-library/jest-dom": "~6.5.0", "@testing-library/react": "~16.0.1", diff --git a/experiment/package.json b/experiment/package.json index ca42464d7e5..b15072dfe53 100644 --- a/experiment/package.json +++ b/experiment/package.json @@ -13,7 +13,7 @@ "test-integration": "cross-env NODE_ENV=test jest --ci --runInBand -c test/js/jest.config.integration.js" }, "dependencies": { - "@labkey/components": "5.8.2" + "@labkey/components": "5.11.0" }, "devDependencies": { "@labkey/build": "7.7.1", diff --git a/pipeline/package-lock.json b/pipeline/package-lock.json index 66329df73bc..f3fdd92acef 100644 --- a/pipeline/package-lock.json +++ b/pipeline/package-lock.json @@ -8,7 +8,7 @@ "name": "pipeline", "version": "0.0.0", "dependencies": { - "@labkey/components": "5.7.0" + "@labkey/components": "5.11.0" }, "devDependencies": { "@labkey/build": "7.7.1", @@ -2701,9 +2701,9 @@ } }, "node_modules/@labkey/api": { - "version": "1.35.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.35.1.tgz", - "integrity": "sha512-oVThx06H9Hnz2TJ92Xg8RRJ6nAARA4IsjgTXFyJNDNWOYcy7Zpl9Th2iujiboyg4mppZ6WaWJl9cNVaGi7DFYg==" + "version": "1.35.2", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.35.2.tgz", + "integrity": "sha512-ksOQ9GpkWyuL04p3h0lr2JvKwRfYR9xw5AH6KP5ZsCiX3ScqaJA30hHM6Qy+RD9vm+HRQvWsa41U3van+qJtaA==" }, "node_modules/@labkey/build": { "version": "7.7.1", @@ -2742,12 +2742,12 @@ } }, "node_modules/@labkey/components": { - "version": "5.7.0", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-5.7.0.tgz", - "integrity": "sha512-pEIrN0ackB9R4b2jcxu8ozVPV1UGrOEkRZ1Q80VIXCUc/QmPcGlXCbpnW3dt+C7Y6SeUfGy+6JCoLihlo3V1xA==", + "version": "5.11.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-5.11.0.tgz", + "integrity": "sha512-t+J0owiGHlFxfVL6ACy49x3Qu5KAtpJ0x22KmV5tycguSIZlyYsqZzv4vsyMnnE6vzDQ9Nvukh7IPsd/jhtZEQ==", "dependencies": { "@hello-pangea/dnd": "16.6.0", - "@labkey/api": "1.35.1", + "@labkey/api": "1.35.2", "@testing-library/dom": "~10.4.0", "@testing-library/jest-dom": "~6.5.0", "@testing-library/react": "~16.0.1", diff --git a/pipeline/package.json b/pipeline/package.json index cb12ac6bdf5..4b5ce33fa7d 100644 --- a/pipeline/package.json +++ b/pipeline/package.json @@ -14,7 +14,7 @@ "build-prod": "npm run clean && cross-env NODE_ENV=production PROD_SOURCE_MAP=source-map webpack --config node_modules/@labkey/build/webpack/prod.config.js --color --progress --profile" }, "dependencies": { - "@labkey/components": "5.7.0" + "@labkey/components": "5.11.0" }, "devDependencies": { "@labkey/build": "7.7.1", diff --git a/query/src/org/labkey/query/QueryModule.java b/query/src/org/labkey/query/QueryModule.java index 204998781a4..c74886310c2 100644 --- a/query/src/org/labkey/query/QueryModule.java +++ b/query/src/org/labkey/query/QueryModule.java @@ -106,6 +106,7 @@ import org.labkey.query.reports.LinkReport; import org.labkey.query.reports.ModuleReportCache; import org.labkey.query.reports.ReportAndDatasetChangeDigestProviderImpl; +import org.labkey.query.reports.ReportAuditProvider; import org.labkey.query.reports.ReportImporter; import org.labkey.query.reports.ReportNotificationInfoProvider; import org.labkey.query.reports.ReportServiceImpl; @@ -288,6 +289,7 @@ public void doStartup(ModuleContext moduleContext) AuditLogService.get().registerAuditType(new QueryExportAuditProvider()); AuditLogService.get().registerAuditType(new QueryUpdateAuditProvider()); } + AuditLogService.get().registerAuditType(new ReportAuditProvider()); ReportAndDatasetChangeDigestProvider.get().addNotificationInfoProvider(new ReportNotificationInfoProvider()); DailyMessageDigest.getInstance().addProvider(ReportAndDatasetChangeDigestProvider.get()); diff --git a/query/src/org/labkey/query/reports/ReportAuditProvider.java b/query/src/org/labkey/query/reports/ReportAuditProvider.java new file mode 100644 index 00000000000..09d45f617e5 --- /dev/null +++ b/query/src/org/labkey/query/reports/ReportAuditProvider.java @@ -0,0 +1,195 @@ +package org.labkey.query.reports; + + +import org.jetbrains.annotations.NotNull; +import org.labkey.api.audit.AbstractAuditTypeProvider; +import org.labkey.api.audit.AuditTypeEvent; +import org.labkey.api.audit.query.AbstractAuditDomainKind; +import org.labkey.api.data.Container; +import org.labkey.api.exp.PropertyDescriptor; +import org.labkey.api.exp.PropertyType; +import org.labkey.api.query.FieldKey; +import org.labkey.api.reports.report.ReportDB; +import org.labkey.api.reports.report.ReportDescriptor; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ReportAuditProvider extends AbstractAuditTypeProvider +{ + private static final String EVENT_NAME = "ReportEvent"; + private static final String COLUMN_NAME_REPORT_ID = "ReportId"; + private static final String COLUMN_NAME_REPORT_NAME = "ReportName"; + private static final String COLUMN_NAME_REPORT_KEY = "ReportKey"; + private static final String COLUMN_NAME_REPORT_TYPE = "ReportType"; + + + static final List defaultVisibleColumns = new ArrayList<>(); + + static { + defaultVisibleColumns.add(FieldKey.fromParts(COLUMN_NAME_CREATED)); + defaultVisibleColumns.add(FieldKey.fromParts(COLUMN_NAME_CONTAINER)); + defaultVisibleColumns.add(FieldKey.fromParts(COLUMN_NAME_CREATED_BY)); + defaultVisibleColumns.add(FieldKey.fromParts(COLUMN_NAME_IMPERSONATED_BY)); + defaultVisibleColumns.add(FieldKey.fromParts(COLUMN_NAME_REPORT_ID)); + defaultVisibleColumns.add(FieldKey.fromParts(COLUMN_NAME_REPORT_NAME)); + defaultVisibleColumns.add(FieldKey.fromParts(COLUMN_NAME_REPORT_KEY)); + defaultVisibleColumns.add(FieldKey.fromParts(COLUMN_NAME_REPORT_TYPE)); + defaultVisibleColumns.add(FieldKey.fromParts(COLUMN_NAME_COMMENT)); + } + + @Override + protected AbstractAuditDomainKind getDomainKind() + { + return new ReportAuditDomainKind(); + } + + @Override + public String getEventName() + { + return EVENT_NAME; + } + + @Override + public String getLabel() + { + return "Report events"; + } + + @Override + public String getDescription() + { + return "Events related to the creation and modification of reports and charts."; + } + + @Override + public Class getEventClass() + { + return (Class) ReportAuditEvent.class; + } + + @Override + public List getDefaultVisibleColumns() + { + return defaultVisibleColumns; + } + + public static class ReportAuditEvent extends AuditTypeEvent + { + private int reportId; + private String reportName; + private String reportKey; + private String reportType; + + public ReportAuditEvent() + { + } + + public ReportAuditEvent(@NotNull ReportDB report, @NotNull ReportDescriptor descriptor, Container container, String comment) + { + this(report.getRowId(), descriptor, container, comment); + this.reportKey = report.getReportKey(); + } + + public ReportAuditEvent(int reportId, @NotNull ReportDescriptor descriptor, Container container, String comment) + { + super(EVENT_NAME, container, comment); + this.reportId = reportId; + this.reportName = descriptor.getReportName(); + this.reportKey = descriptor.getReportKey(); + this.reportType = descriptor.getReportType(); + } + + public int getReportId() + { + return reportId; + } + + public void setReportId(int reportId) + { + this.reportId = reportId; + } + + public String getReportName() + { + return reportName; + } + + public void setReportName(String reportName) + { + this.reportName = reportName; + } + + public String getReportKey() + { + return reportKey; + } + + public void setReportKey(String reportKey) + { + this.reportKey = reportKey; + } + + public String getReportType() + { + return reportType; + } + + public void setReportType(String reportType) + { + this.reportType = reportType; + } + + @Override + public Map getAuditLogMessageElements() + { + Map elements = super.getAuditLogMessageElements(); + elements.put("reportId", reportId); + elements.put("reportName", reportName); + elements.put("reportKey", reportKey); + elements.put("reportType", reportType); + return elements; + } + + } + + public static class ReportAuditDomainKind extends AbstractAuditDomainKind + { + public static final String NAME = "ReportAuditDomain"; + public static String NAMESPACE_PREFIX = "Report-" + NAME; + + private final Set fields; + + public ReportAuditDomainKind() + { + super(EVENT_NAME); + + fields = new LinkedHashSet<>(); + fields.add(createPropertyDescriptor(COLUMN_NAME_REPORT_ID, PropertyType.INTEGER, "Report Id", null, true)); + fields.add(createPropertyDescriptor(COLUMN_NAME_REPORT_NAME, PropertyType.STRING, "Report Name", null, true)); + fields.add(createPropertyDescriptor(COLUMN_NAME_REPORT_KEY, PropertyType.STRING, "Report Key", null, false)); + fields.add(createPropertyDescriptor(COLUMN_NAME_REPORT_TYPE, PropertyType.STRING, "Report Type", null, true)); + } + + @Override + protected String getNamespacePrefix() + { + return NAMESPACE_PREFIX; + } + + @Override + public Set getProperties() + { + return fields; + } + + @Override + public String getKindName() + { + return NAME; + } + } +} diff --git a/query/src/org/labkey/query/reports/ReportServiceImpl.java b/query/src/org/labkey/query/reports/ReportServiceImpl.java index 337648f45b6..d6bf0a9d754 100644 --- a/query/src/org/labkey/query/reports/ReportServiceImpl.java +++ b/query/src/org/labkey/query/reports/ReportServiceImpl.java @@ -29,6 +29,7 @@ import org.jetbrains.annotations.Nullable; import org.labkey.api.admin.FolderExportContext; import org.labkey.api.admin.FolderImportContext; +import org.labkey.api.audit.AuditLogService; import org.labkey.api.collections.MultiSetUtils; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; @@ -309,18 +310,21 @@ public void deleteReport(ContainerUser context, Report report) report.beforeDelete(context); final ReportDescriptor descriptor = report.getDescriptor(); - _deleteReport(context.getContainer(), reportId.getRowId()); + _deleteReport(context.getContainer(), context.getUser(), reportId.getRowId(), descriptor); SecurityPolicyManager.deletePolicy(descriptor); tx.commit(); } } - private void _deleteReport(Container c, int reportId) + private void _deleteReport(Container c, User u, int reportId, ReportDescriptor descriptor) { SimpleFilter filter = new SimpleFilter(FieldKey.fromParts("ContainerId"), c.getId()); filter.addCondition(FieldKey.fromParts("RowId"), reportId); Table.delete(getTable(), filter); DatabaseReportCache.uncache(c); + + ReportAuditProvider.ReportAuditEvent event = new ReportAuditProvider.ReportAuditEvent(reportId, descriptor, c, "Report deleted"); + AuditLogService.get().addEvent(u, event); } @Override @@ -432,14 +436,17 @@ private ReportDB _saveDbReport(User user, Container c, String key, ReportDescrip else throw new RuntimeException("Can't save a report that is not stored in the database!"); - - if (null != reportId && reportExists(reportId.getRowId())) + boolean reportExists = null != reportId && reportExists(reportId.getRowId()); + if (reportExists) reportDB = Table.update(user, getTable(), reportDB, reportId.getRowId()); else reportDB = Table.insert(user, getTable(), reportDB); DatabaseReportCache.uncache(c); + ReportAuditProvider.ReportAuditEvent event = new ReportAuditProvider.ReportAuditEvent(reportDB, descriptor, c, reportExists ? "Report updated" : "Report created"); + AuditLogService.get().addEvent(user, event); + return reportDB; } @@ -606,7 +613,7 @@ public Report getReport(Container c, int rowId) if (null != report) { - if ((report.getDescriptor().getFlags() & ReportDescriptor.FLAG_INHERITABLE) != 0) + if (report.getDescriptor().isInheritable()) return report; else return null; diff --git a/visualization/resources/web/vis/chartWizard/baseChartWizardPanel.js b/visualization/resources/web/vis/chartWizard/baseChartWizardPanel.js index fc44f629621..7296f2df8c4 100644 --- a/visualization/resources/web/vis/chartWizard/baseChartWizardPanel.js +++ b/visualization/resources/web/vis/chartWizard/baseChartWizardPanel.js @@ -16,6 +16,7 @@ Ext4.define('LABKEY.ext4.BaseChartWizardPanel', { canShare: false, isDeveloper: false, defaultNumberFormat: null, + allowInherit: false, allowEditMode: false, editModeURL: null, baseUrl: null, @@ -116,6 +117,7 @@ Ext4.define('LABKEY.ext4.BaseChartWizardPanel', { canShare: this.canShare, isDeveloper: this.isDeveloper, defaultNumberFormat: this.defaultNumberFormat, + allowInherit: this.allowInherit, allowEditMode: this.allowEditMode, editModeURL: this.editModeURL, @@ -151,6 +153,7 @@ Ext4.define('LABKEY.ext4.BaseChartWizardPanel', { canShare: this.canShare, isDeveloper: this.isDeveloper, defaultNumberFormat: this.defaultNumberFormat, + allowInherit: this.allowInherit, allowEditMode: this.allowEditMode, editModeURL: this.editModeURL, diff --git a/visualization/resources/web/vis/chartWizard/genericChartPanel.js b/visualization/resources/web/vis/chartWizard/genericChartPanel.js index bb20885d61e..db9fb40c017 100644 --- a/visualization/resources/web/vis/chartWizard/genericChartPanel.js +++ b/visualization/resources/web/vis/chartWizard/genericChartPanel.js @@ -632,6 +632,7 @@ Ext4.define('LABKEY.ext4.GenericChartPanel', { if (!this.savePanel) { this.savePanel = Ext4.create('LABKEY.vis.SaveOptionsPanel', { + allowInherit: this.allowInherit, canEdit: this.canEdit, canShare: this.canShare, listeners: { @@ -920,7 +921,7 @@ Ext4.define('LABKEY.ext4.GenericChartPanel', { columns.push(this.autoColumnName.toString()); } - Ext4.each(['color', 'shape', 'series'], function(name) { + Ext4.each(['ySub', 'xSub', 'color', 'shape', 'series'], function(name) { if (measures[name]) { this.addMeasureForColumnQuery(columns, measures[name]); } @@ -1079,6 +1080,7 @@ Ext4.define('LABKEY.ext4.GenericChartPanel', { reportConfig.description = data.reportDescription; reportConfig["public"] = data.shared; + reportConfig.inheritable = data.inheritable; reportConfig.thumbnailType = data.thumbnailType; reportConfig.svg = this.chartSVG; @@ -1189,6 +1191,7 @@ Ext4.define('LABKEY.ext4.GenericChartPanel', { name: config.name, description: config.description, shared: config.shared, + inheritable: config.inheritable, reportProps: config.reportProps, thumbnailURL: config.thumbnailURL }); diff --git a/visualization/resources/web/vis/chartWizard/saveOptionsPanel.js b/visualization/resources/web/vis/chartWizard/saveOptionsPanel.js index 4e41116d043..82b686422b5 100644 --- a/visualization/resources/web/vis/chartWizard/saveOptionsPanel.js +++ b/visualization/resources/web/vis/chartWizard/saveOptionsPanel.js @@ -33,7 +33,6 @@ Ext4.define('LABKEY.vis.SaveOptionsPanel', { this.thumbnailType = this.isSavedReport() && this.reportInfo.reportProps && this.reportInfo.reportProps.thumbnailType ? this.reportInfo.reportProps.thumbnailType : 'AUTO'; // Note that Readers are allowed to save new charts (readers own new charts they're creating)- this is by design. - this.currentlyShared = (this.isSavedReport() && this.reportInfo.shared) || (!this.isSavedReport() && this.canSaveSharedCharts()); this.createdBy = this.isSavedReport() ? this.reportInfo.createdBy : LABKEY.Security.currentUser.id; // generate unique id for the thumbnail preview div @@ -61,6 +60,7 @@ Ext4.define('LABKEY.vis.SaveOptionsPanel', { this.saveForm = Ext4.create('Ext.form.Panel', { region: 'center', cls: 'region-panel save-form-panel', + bodyStyle: 'padding: 10px;', border: false, items: [ Ext4.create('Ext.form.field.Text', { @@ -102,14 +102,23 @@ Ext4.define('LABKEY.vis.SaveOptionsPanel', { value: Ext4.util.Format.htmlEncode(this.isSavedReport() ? this.reportInfo.description : null), anchor: '100%' }), + Ext4.create('Ext.form.Checkbox', { + itemId: 'reportInheritable', + name: 'reportInheritable', + fieldLabel: 'Inherit', + boxLabel: 'Make this report available in child folders', + hidden: !this.shouldAllowInherit(), + labelWidth: 125, + width: 400, + }), Ext4.create('Ext.form.RadioGroup', { itemId: 'reportShared', fieldLabel: 'Viewable By', labelWidth: 125, width: 350, items : [ - { itemId: 'allReaders', name: 'reportShared', boxLabel: 'All readers', inputValue: 'true', disabled: !this.canSaveSharedCharts(), checked: this.currentlyShared, width: 140 }, - { itemId: 'onlyMe', name: 'reportShared', boxLabel: 'Only me', inputValue: 'false', disabled: !this.canSaveSharedCharts(), checked: !this.currentlyShared, width: 140 } + { itemId: 'allReaders', name: 'reportShared', boxLabel: 'All readers', inputValue: 'true', disabled: !this.canSaveSharedCharts(), width: 140 }, + { itemId: 'onlyMe', name: 'reportShared', boxLabel: 'Only me', inputValue: 'false', disabled: !this.canSaveSharedCharts(), width: 140 } ] }), Ext4.create('Ext.form.RadioGroup', { @@ -197,11 +206,13 @@ Ext4.define('LABKEY.vis.SaveOptionsPanel', { // the save button will not allow for replace if this is a new chart, // but will force replace if this is a change to a saved chart var shared = Ext4.isString(formVals.reportShared) ? 'true' == formVals.reportShared : (new Boolean(formVals.reportShared)).valueOf(); + var inheritable = Ext4.isString(formVals.reportInheritable) ? 'on' == formVals.reportInheritable : (new Boolean(formVals.reportInheritable)).valueOf(); this.fireEvent('saveChart', { isSaveAs: this.isSaveAs, replace: !this.isSaveAs ? this.isSavedReport() : false, reportName: formVals.reportName, reportDescription: formVals.reportDescription, + inheritable: inheritable, shared: shared, thumbnailType: formVals.reportThumbnailType, canSaveSharedCharts: this.canSaveSharedCharts(), @@ -209,10 +220,11 @@ Ext4.define('LABKEY.vis.SaveOptionsPanel', { }); // store the update report properties - if(this.reportInfo){ + if (this.reportInfo){ this.reportInfo.name = formVals.reportName; this.reportInfo.description = formVals.reportDescription; - this.currentlyShared = shared; + this.reportInfo.shared = shared; + this.reportInfo.inheritable = inheritable; this.thumbnailType = formVals.reportThumbnailType; } @@ -250,6 +262,11 @@ Ext4.define('LABKEY.vis.SaveOptionsPanel', { return Ext4.isObject(this.reportInfo); }, + shouldAllowInherit : function() + { + return this.allowInherit; + }, + canSaveChanges : function() { return this.canEdit; @@ -280,6 +297,7 @@ Ext4.define('LABKEY.vis.SaveOptionsPanel', { this.down('#onlyMe').setValue(true); else this.down('#allReaders').setValue(true); + this.down('#reportInheritable').setValue(false); this.down('#reportThumbnailType').setValue({reportThumbnailType: 'AUTO'}); this.down('#keepCustom').hide(); @@ -299,11 +317,14 @@ Ext4.define('LABKEY.vis.SaveOptionsPanel', { this.down('#reportDescriptionDisplay').setVisible(!this.canSaveChanges()); this.down('#reportDescriptionDisplay').setValue(Ext4.util.Format.htmlEncode(this.isSavedReport() ? this.reportInfo.description : null)); - if (!this.currentlyShared) + if (this.isSavedReport() && !this.reportInfo.shared) this.down('#onlyMe').setValue(true); else this.down('#allReaders').setValue(true); + this.down('#reportInheritable').setVisible(this.shouldAllowInherit()); + this.down('#reportInheritable').setValue(this.isSavedReport() ? this.reportInfo.inheritable : false); + this.down('#reportThumbnailType').setValue({reportThumbnailType: this.thumbnailType}); // hide the Keep existing option if there hasn't been a custom thumbnail saved for this report if (this.thumbnailType == 'CUSTOM') @@ -368,6 +389,7 @@ Ext4.define('LABKEY.vis.SaveOptionsPanel', { this.down('#reportDescription').setValue(config.description); this.down('#reportDescriptionDisplay').setValue(config.description); this.down('#reportShared').setValue(config.shared); + this.down('#reportInheritable').setValue(config.inheritable); if(config.reportProps && config.reportProps.thumbnailType){ this.thumbnailType = config.reportProps.thumbnailType; diff --git a/visualization/resources/web/vis/genericChart/genericChartHelper.js b/visualization/resources/web/vis/genericChart/genericChartHelper.js index dd9fa76fec6..a99e4573874 100644 --- a/visualization/resources/web/vis/genericChart/genericChartHelper.js +++ b/visualization/resources/web/vis/genericChart/genericChartHelper.js @@ -1026,7 +1026,8 @@ LABKEY.vis.GenericChartHelper = new function(){ renderTo: renderTo, rendererType: 'd3', width: chartConfig.width, - height: chartConfig.height + height: chartConfig.height, + gridLinesVisible: chartConfig.gridLinesVisible, }; if (renderType === 'pie_chart') { @@ -1181,7 +1182,7 @@ LABKEY.vis.GenericChartHelper = new function(){ var _willRotateXAxisTickText = function(scales, plotConfig, maxTickLength, data) { if (scales.x && scales.x.scaleType === 'discrete') { var tickCount = scales.x && scales.x.tickLabelMax ? Math.min(scales.x.tickLabelMax, data.length) : data.length; - return (tickCount * maxTickLength * 5) > (plotConfig.width - 150); + return (tickCount * maxTickLength * 4) > (plotConfig.width - 150); } return false; @@ -1195,15 +1196,17 @@ LABKEY.vis.GenericChartHelper = new function(){ var maxLen = 0; $.each(data, function(idx, d) { var val = LABKEY.Utils.isFunction(aes.x) ? aes.x(d) : d[aes.x]; - if (LABKEY.Utils.isString(val)) { + var subVal = LABKEY.Utils.isFunction(aes.xSub) ? aes.xSub(d) : d[aes.xSub]; + if (LABKEY.Utils.isString(subVal)) { + maxLen = Math.max(maxLen, subVal.length); + } else if (LABKEY.Utils.isString(val)) { maxLen = Math.max(maxLen, val.length); } }); if (_willRotateXAxisTickText(scales, plotConfig, maxLen, data)) { - // min bottom margin: 50, max bottom margin: 275 - var bottomMargin = Math.min(Math.max(50, maxLen*5), 275); - margins.bottom = bottomMargin; + // min bottom margin: 50, max bottom margin: 150 + margins.bottom = Math.min(Math.max(50, maxLen*5), 175); } } diff --git a/visualization/src/org/labkey/visualization/VisualizationController.java b/visualization/src/org/labkey/visualization/VisualizationController.java index d4c6de080d2..424ba68f409 100644 --- a/visualization/src/org/labkey/visualization/VisualizationController.java +++ b/visualization/src/org/labkey/visualization/VisualizationController.java @@ -984,12 +984,22 @@ public void validateForm(ChartWizardReportForm form, Errors errors) errors.reject(ERROR_MSG, "Visualization \"" + form.getName() + "\" does not exist in " + getContainer().getPath() + "."); } - if (report == null || report.getDescriptor().getContainerId() == null || !report.getDescriptor().getContainerId().equals(getContainer().getId())) + if (report == null) { - errors.reject(ERROR_MSG, "Visualization \"" + form.getName() + "\" does not exist in " + getContainer().getPath() + "."); + String reportId = form.getReportId() != null ? form.getReportId().toString() : form.getName(); + errors.reject(ERROR_MSG, "Visualization for \"" + reportId + "\" does not exist in " + getContainer().getPath() + "."); return; } + if (report.getDescriptor().getContainerId() == null || !report.getDescriptor().getContainerId().equals(getContainer().getId())) + { + if (!report.getDescriptor().isInherited(getContainer())) + { + errors.reject(ERROR_MSG, "Visualization \"" + report.getDescriptor().getReportName() + "\" does not exist in " + getContainer().getPath() + "."); + return; + } + } + if (!report.getDescriptor().isShared() && report.getDescriptor().getOwner() != getUser().getUserId()) { errors.reject(ERROR_MSG, "You do not have permissions to view this private report."); @@ -1019,6 +1029,7 @@ public ApiResponse execute(ChartWizardReportForm form, BindException errors) thr resp.put("viewName", vizDescriptor.getProperty(ReportDescriptor.Prop.viewName)); resp.put("type", vizDescriptor.getReportType()); resp.put("shared", vizDescriptor.isShared()); + resp.put("inheritable", vizDescriptor.isInheritable()); resp.put("ownerId", !vizDescriptor.isShared() ? vizDescriptor.getOwner() : null); resp.put("createdBy", vizDescriptor.getCreatedBy()); resp.put("reportProps", vizDescriptor.getReportProps()); @@ -1312,6 +1323,11 @@ private Report getGenericReport(ChartWizardReportForm form) descriptor.setOwner(getUser().getUserId()); else descriptor.setOwner(null); + + if (form.isInheritable()) + descriptor.setFlags(descriptor.getFlags() | ReportDescriptor.FLAG_INHERITABLE); + else + descriptor.setFlags(descriptor.getFlags() & ~ReportDescriptor.FLAG_INHERITABLE); } return report; } @@ -1444,6 +1460,7 @@ public static class ChartWizardReportForm extends ReportUtil.JsonReportForm private String _autoColumnXName; private String _svg; private String _thumbnailType; + private boolean _inheritable; private boolean _allowToggleMode = false; // view vs. edit mode public String getRenderType() @@ -1528,6 +1545,16 @@ public void setThumbnailType(String thumbnailType) _thumbnailType = thumbnailType; } + public boolean isInheritable() + { + return _inheritable; + } + + public void setInheritable(boolean inheritable) + { + _inheritable = inheritable; + } + public boolean allowToggleMode() { return _allowToggleMode; @@ -1547,6 +1574,7 @@ public void bindJson(JSONObject json) _dataRegionName = json.optString("dataRegionName", null); _svg = json.optString("svg", null); _thumbnailType = json.optString("thumbnailType", null); + _inheritable = json.optBoolean("inheritable", false); Object jsonData = json.opt("jsonData"); if (jsonData != null) diff --git a/visualization/src/org/labkey/visualization/views/chartWizard.jsp b/visualization/src/org/labkey/visualization/views/chartWizard.jsp index a7509b01601..8e57c05d761 100644 --- a/visualization/src/org/labkey/visualization/views/chartWizard.jsp +++ b/visualization/src/org/labkey/visualization/views/chartWizard.jsp @@ -32,6 +32,7 @@ <%@ page import="org.labkey.api.view.ViewContext" %> <%@ page import="org.labkey.api.view.template.ClientDependencies" %> <%@ page import="org.labkey.visualization.VisualizationController" %> +<%@ page import="org.labkey.api.security.roles.ProjectAdminRole" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%! @@ -58,6 +59,7 @@ boolean canShare = ctx.hasPermission(ShareReportPermission.class); boolean isDeveloper = user.isBrowserDev(); boolean allowEditMode = !user.isGuest() && form.allowToggleMode(); + boolean allowInherit = user.hasRootAdminPermission() || ReportUtil.isInRole(user, c, ProjectAdminRole.class); boolean canEdit = false; ActionURL editUrl = null; @@ -126,6 +128,7 @@ canShare: <%=canShare%>, isDeveloper: <%=isDeveloper%>, defaultNumberFormat: eval(<%=q(numberFormatFn)%>), + allowInherit: <%=allowInherit%>, allowEditMode: <%=allowEditMode%>, editModeURL: editUrl,