From c7148a63b98aa5619078fdd6c8c43fed6886719e Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Sun, 6 Oct 2024 20:23:46 -0700 Subject: [PATCH 01/32] Date/time formatting simplifications --- api/src/org/labkey/api/util/DateUtil.java | 9 ++- .../core/admin/lookAndFeelProperties.jsp | 58 ++++++++++++++++--- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/api/src/org/labkey/api/util/DateUtil.java b/api/src/org/labkey/api/util/DateUtil.java index 5ac8e63a122..26eb9774a9f 100644 --- a/api/src/org/labkey/api/util/DateUtil.java +++ b/api/src/org/labkey/api/util/DateUtil.java @@ -104,10 +104,15 @@ public static boolean isStandardDateDisplayFormat(String dateFormat) return STANDARD_DATE_DISPLAY_FORMATS.contains(dateFormat); } - public static boolean isStandardDateTimeDisplayFormat(String dateTimeFormat) + public static String[] splitDateTimeFormat(String dateTimeFormat) { // Tolerate any amount of whitespace between the parts - String[] parts = dateTimeFormat.split("\\s+"); + return dateTimeFormat.split("\\s+"); + } + + public static boolean isStandardDateTimeDisplayFormat(String dateTimeFormat) + { + String[] parts = splitDateTimeFormat(dateTimeFormat); // If one part, must be standard date format // If two parts, must be standard date format followed by standard time format diff --git a/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp b/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp index 9550a5a1d07..12a473f5835 100644 --- a/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp +++ b/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp @@ -31,10 +31,18 @@ <%@ page import="org.labkey.api.util.Formats" %> <%@ page import="org.labkey.api.util.HtmlString" %> <%@ page import="org.labkey.api.util.HtmlStringBuilder" %> +<%@ page import="org.labkey.api.util.element.Select.SelectBuilder" %> <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.core.admin.AdminController" %> <%@ page import="org.labkey.core.admin.AdminController.AdminUrlsImpl" %> +<%@ page import="java.text.SimpleDateFormat" %> +<%@ page import="java.util.Date" %> +<%@ page import="java.util.LinkedHashMap" %> +<%@ page import="java.util.LinkedHashSet" %> +<%@ page import="java.util.Map" %> +<%@ page import="java.util.Set" %> +<%@ page import="java.util.stream.Collectors" %> <%@ page import="static org.labkey.api.settings.LookAndFeelProperties.Properties.*" %> <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> @@ -148,15 +156,15 @@
- <% +<% addHandler("menu_always", "click", "document.getElementById('app-menu-warning').style.display='none';"); addHandler("menu_admin", "click", "document.getElementById('app-menu-warning').style.display='block';"); - %> +%> - <% +<% } - %> +%> > @@ -257,22 +265,24 @@ String dateParsingHelp = sizingPrefix + "This pattern is attempted first when parsing text input for a column that is designated with a date-only data type or annotated with the \"Date\" meta type. Most standard LabKey date columns use date-time data type instead (see below)." + simpleDateFormatDocs + sizingSuffix; String dateTimeParsingHelp = sizingPrefix + "This pattern is attempted first when parsing text input for a column that is designated with a date-time data type or annotated with the \"DateTime\" meta type. Most standard LabKey date columns use this pattern." + simpleDateTimeFormatDocs + sizingSuffix; String timeParsingHelp = sizingPrefix + "This pattern is attempted first when parsing text input for a column that is designated with a time data type or annotated with the \"Time\" meta type. Most standard LabKey time columns use this pattern." + simpleTimeFormatDocs + sizingSuffix; - %> Customize date, time, and number display formats (<%=bean.helpLink%>) <%=helpPopup("Date format", dateFormatHelp, true)%> - + <%=select(defaultDateFormat.name(), DateUtil.STANDARD_DATE_DISPLAY_FORMATS, laf.getDefaultDateFormat(), false)%> <%=helpPopup("Date-time format", dateTimeFormatHelp, true)%> - +<% + String[] parts = DateUtil.splitDateTimeFormat(laf.getDefaultDateTimeFormat()); +%> + <%=select("part1", DateUtil.STANDARD_DATE_DISPLAY_FORMATS, parts.length > 0 ? parts[0] : null, false)%>  <%=select("part2", DateUtil.STANDARD_TIME_DISPLAY_FORMATS, parts.length > 1 ? parts[1] : "", true)%> <%=helpPopup("Time format", timeFormatHelp, true)%> - + <%=select(defaultTimeFormat.name(), DateUtil.STANDARD_TIME_DISPLAY_FORMATS, laf.getDefaultTimeFormat(), false)%> <%=helpPopup("Number format", decimalFormatHelp, true)%> @@ -301,7 +311,8 @@

-<% + +<% } %> @@ -409,3 +420,32 @@ return false; } +<%! + private SelectBuilder select(String id, Set options, String current, boolean addNone) + { + if (!options.contains(current)) + { + Set set = new LinkedHashSet<>(); + set.add(current); + set.addAll(options); + options = set; + } + + Date now = new Date(); + Map map = options.stream() + .collect(Collectors.toMap(option -> option, option -> option + " (" + new SimpleDateFormat(option).format(now) + ")", (x, y) -> y, LinkedHashMap::new)); + + if (addNone) + { + map.put(null, ""); + } + + return select() + .id(id) + .name(id) + .addOptions(map) + .selected(current) + .className(null) + .addStyle("width:225px"); + } +%> From ac05c7a5ef4b4235329f9fc0ad639d05cf9d5d66 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 7 Oct 2024 08:15:20 -0700 Subject: [PATCH 02/32] Add inheritance UI --- .../labkey/api/admin/FolderWriterImpl.java | 5 +- .../settings/LookAndFeelFolderProperties.java | 34 ++++++--- .../core/admin/lookAndFeelProperties.jsp | 71 ++++++++++++++++--- 3 files changed, 86 insertions(+), 24 deletions(-) diff --git a/api/src/org/labkey/api/admin/FolderWriterImpl.java b/api/src/org/labkey/api/admin/FolderWriterImpl.java index f409dec62da..77e020c1fb2 100644 --- a/api/src/org/labkey/api/admin/FolderWriterImpl.java +++ b/api/src/org/labkey/api/admin/FolderWriterImpl.java @@ -143,8 +143,9 @@ private void writeFolderXml(Container c, FolderExportContext ctx, VirtualFile vf if (null != extraTimeParsingPattern) folderXml.setExtraTimeParsingPattern(extraTimeParsingPattern); - if (props.areRestrictedColumnsEnabled()) - folderXml.setRestrictedColumnsEnabled(true); + Boolean areRestricted = props.areRestrictedColumnsEnabledStored(); + if (null != areRestricted) + folderXml.setRestrictedColumnsEnabled(areRestricted); // Save the folder.xml file. This gets called last, after all other writers have populated the other sections. vf.saveXmlBean("folder.xml", ctx.getDocument()); diff --git a/api/src/org/labkey/api/settings/LookAndFeelFolderProperties.java b/api/src/org/labkey/api/settings/LookAndFeelFolderProperties.java index 853c5312ed9..7bf2e43c24d 100644 --- a/api/src/org/labkey/api/settings/LookAndFeelFolderProperties.java +++ b/api/src/org/labkey/api/settings/LookAndFeelFolderProperties.java @@ -138,46 +138,58 @@ public boolean areRestrictedColumnsEnabled() return lookupBooleanValue(_c, restrictedColumnsEnabled, false); } - // Get the value that's actually stored in this container; don't look up the hierarchy. This is useful only for export. + // Get the value that's actually stored in this container or null if inherited; don't look up the hierarchy. This is useful for export and showing inheritance status in the UI. public String getDefaultDateFormatStored() { return super.lookupStringValue(_c, defaultDateFormatString, null); } - // Get the value that's actually stored in this container; don't look up the hierarchy. This is useful only for export. + // Get the value that's actually stored in this container or null if inherited; don't look up the hierarchy. This is useful for export and showing inheritance status in the UI. public String getDefaultDateTimeFormatStored() { return super.lookupStringValue(_c, defaultDateTimeFormatString, null); } - // Get the value that's actually stored in this container; don't look up the hierarchy. This is useful only for export. + // Get the value that's actually stored in this container or null if inherited; don't look up the hierarchy. + // This is useful for export and showing inheritance status in the UI. public String getDefaultNumberFormatStored() { return super.lookupStringValue(_c, defaultNumberFormatString, null); } - // Get the value that's actually stored in this container; don't look up the hierarchy. This is useful only for export. + // Get the value that's actually stored in this container or null if inherited; don't look up the hierarchy. + // This is useful for export and showing inheritance status in the UI. public String getDefaultTimeFormatStored() { return super.lookupStringValue(_c, defaultTimeFormatString, null); } - - // Get the value that's actually stored in this container; don't look up the hierarchy. This is useful only for export. + // Get the value that's actually stored in this container or null if inherited; don't look up the hierarchy. + // This is useful for export and showing inheritance status in the UI. public String getExtraDateParsingPatternStored() { - return super.lookupStringValue(_c, extraDateParsingPattern, null); + return super.lookupStringValue(_c, extraDateParsingPattern.name(), null); } - // Get the value that's actually stored in this container; don't look up the hierarchy. This is useful only for export. + // Get the value that's actually stored in this container or null if inherited; don't look up the hierarchy. + // This is useful for export and showing inheritance status in the UI. public String getExtraDateTimeParsingPatternStored() { - return super.lookupStringValue(_c, extraDateTimeParsingPattern, null); + return super.lookupStringValue(_c, extraDateTimeParsingPattern.name(), null); } - // Get the value that's actually stored in this container; don't look up the hierarchy. This is useful only for export. + // Get the value that's actually stored in this container or null if inherited; don't look up the hierarchy. + // This is useful for export and showing inheritance status in the UI. public String getExtraTimeParsingPatternStored() { - return super.lookupStringValue(_c, extraTimeParsingPattern, null); + return super.lookupStringValue(_c, extraTimeParsingPattern.name(), null); + } + + // Get the value that's actually stored in this container or null if inherited; don't look up the hierarchy. + // This is useful for export and showing inheritance status in the UI. + public Boolean areRestrictedColumnsEnabledStored() + { + String stored = super.lookupStringValue(_c, restrictedColumnsEnabled.name(), null); + return null == stored ? null : "TRUE".equals(stored); } } \ No newline at end of file diff --git a/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp b/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp index 12a473f5835..ab285828947 100644 --- a/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp +++ b/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp @@ -258,34 +258,50 @@ String simpleDateFormatDocs = simpleDateDocHeader + dateDocs + ""; String simpleDateTimeFormatDocs = simpleDateDocHeader + dateDocs + timeDocs + ""; String simpleTimeFormatDocs = simpleDateDocHeader + timeDocs + ""; - String dateFormatHelp = sizingPrefix + "This format is applied when displaying a column that is defined with a date-only data type or annotated with the \"Date\" meta type. Most standard LabKey date columns use date-time data type (see below)." + simpleDateFormatDocs + sizingSuffix; - String dateTimeFormatHelp = sizingPrefix + "This format is applied when displaying a column that is defined with a date-time data type or annotated with the \"DateTime\" meta type. Most standard LabKey date columns use this format." + simpleDateTimeFormatDocs + sizingSuffix; - String timeFormatHelp = sizingPrefix + "This format is applied when displaying a column that is defined with a time data type or annotated with the \"Time\" meta type. Most standard LabKey time columns use this format." + simpleTimeFormatDocs + sizingSuffix; + String dateFormatHelp = sizingPrefix + "This format is applied when displaying a column that is defined with a date-only data type or annotated with the \"Date\" meta type. Most standard LabKey date columns use date-time data type (see below)." + sizingSuffix; + String dateTimeFormatHelp = sizingPrefix + "This format is applied when displaying a column that is defined with a date-time data type or annotated with the \"DateTime\" meta type. Most standard LabKey date columns use this format." + sizingSuffix; + String timeFormatHelp = sizingPrefix + "This format is applied when displaying a column that is defined with a time data type or annotated with the \"Time\" meta type. Most standard LabKey time columns use this format." + sizingSuffix; String dateParsingHelp = sizingPrefix + "This pattern is attempted first when parsing text input for a column that is designated with a date-only data type or annotated with the \"Date\" meta type. Most standard LabKey date columns use date-time data type instead (see below)." + simpleDateFormatDocs + sizingSuffix; String dateTimeParsingHelp = sizingPrefix + "This pattern is attempted first when parsing text input for a column that is designated with a date-time data type or annotated with the \"DateTime\" meta type. Most standard LabKey date columns use this pattern." + simpleDateTimeFormatDocs + sizingSuffix; String timeParsingHelp = sizingPrefix + "This pattern is attempted first when parsing text input for a column that is designated with a time data type or annotated with the \"Time\" meta type. Most standard LabKey time columns use this pattern." + simpleTimeFormatDocs + sizingSuffix; %> - Customize date, time, and number display formats (<%=bean.helpLink%>) + >Customize date, time, and number display formats (<%=bean.helpLink%>) + <% + if (folder) + { + %> + Inherit + <% + } + %> <%=helpPopup("Date format", dateFormatHelp, true)%> + <%=inheritCheckbox(c, defaultDateFormat.name(), laf.getDefaultDateFormatStored())%> <%=select(defaultDateFormat.name(), DateUtil.STANDARD_DATE_DISPLAY_FORMATS, laf.getDefaultDateFormat(), false)%> <%=helpPopup("Date-time format", dateTimeFormatHelp, true)%> + <%=inheritCheckbox(c, defaultDateTimeFormat.name(), laf.getDefaultDateTimeFormatStored())%> <% String[] parts = DateUtil.splitDateTimeFormat(laf.getDefaultDateTimeFormat()); %> - <%=select("part1", DateUtil.STANDARD_DATE_DISPLAY_FORMATS, parts.length > 0 ? parts[0] : null, false)%>  <%=select("part2", DateUtil.STANDARD_TIME_DISPLAY_FORMATS, parts.length > 1 ? parts[1] : "", true)%> + + <%=select("dateSelect", DateUtil.STANDARD_DATE_DISPLAY_FORMATS, parts.length > 0 ? parts[0] : null, false)%>   + <%=select("timeSelect", DateUtil.STANDARD_TIME_DISPLAY_FORMATS, parts.length > 1 ? parts[1] : NONE, true)%> + + <%=helpPopup("Time format", timeFormatHelp, true)%> + <%=inheritCheckbox(c, defaultTimeFormat.name(), laf.getDefaultTimeFormatStored())%> <%=select(defaultTimeFormat.name(), DateUtil.STANDARD_TIME_DISPLAY_FORMATS, laf.getDefaultTimeFormat(), false)%> <%=helpPopup("Number format", decimalFormatHelp, true)%> + <%=inheritCheckbox(c, defaultNumberFormat.name(), laf.getDefaultNumberFormatStored())%> @@ -293,7 +309,7 @@ - Customize date and time parsing behavior (<%=bean.helpLink%>) + Customize date and time parsing behavior (<%=bean.helpLink%>) <% // TODO: This check is temporary and should switch to "if (!folder) {}" once the date parsing methods pass Container consistently @@ -317,14 +333,17 @@ %> <%=helpPopup("Extra date parsing pattern", dateParsingHelp, true)%> + <%=inheritCheckbox(c, extraDateParsingPattern.name(), laf.getExtraDateParsingPatternStored())%> <%=helpPopup("Extra date-time parsing pattern", dateTimeParsingHelp, true, 300)%> + <%=inheritCheckbox(c, extraDateTimeParsingPattern.name(), laf.getExtraDateTimeParsingPatternStored())%> <%=helpPopup("Extra time parsing pattern", timeParsingHelp, true)%> + <%=inheritCheckbox(c, extraTimeParsingPattern.name(), laf.getExtraTimeParsingPatternStored())%> @@ -332,10 +351,11 @@ - Customize column restrictions (<%=bean.customColumnRestrictionHelpLink%>) + Customize column restrictions (<%=bean.customColumnRestrictionHelpLink%>) + <%=inheritCheckbox(c, restrictedColumnsEnabled.name(), laf.areRestrictedColumnsEnabledStored())%> > @@ -385,7 +405,7 @@ { %> - <%=button("Save").submit(true).onClick("_form.setClean();") %>  + <%=button("Save").submit(true).onClick("save();") %>  <%=button("Reset").onClick("return confirmReset();") %> @@ -406,7 +426,7 @@ <%! + private static final String NONE = ""; + private SelectBuilder select(String id, Set options, String current, boolean addNone) { if (!options.contains(current)) { Set set = new LinkedHashSet<>(); - set.add(current); + if (!NONE.equals(current)) + set.add(current); set.addAll(options); options = set; } @@ -437,7 +471,7 @@ if (addNone) { - map.put(null, ""); + map.put(null, NONE); } return select() @@ -448,4 +482,19 @@ .className(null) .addStyle("width:225px"); } + + private HtmlString inheritCheckbox(Container c, String name, Object value) + { + if (c.isRoot()) + return HtmlString.EMPTY_STRING; + + HtmlStringBuilder builder = HtmlStringBuilder.of(HtmlString.unsafe("")).getHtmlString(); + } %> From 7955c5c6d45eae4fcd30f0aa83bce7a4fc76c064 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 7 Oct 2024 13:53:09 -0700 Subject: [PATCH 03/32] Persist inheritance based on new posted values, handle NONE properly, allow empty parsing patterns, fix splitting --- .../WriteableFolderLookAndFeelProperties.java | 24 +-- api/src/org/labkey/api/util/DateUtil.java | 5 +- .../labkey/core/admin/AdminController.java | 146 ++++++++++++++++-- .../core/admin/lookAndFeelProperties.jsp | 6 +- 4 files changed, 155 insertions(+), 26 deletions(-) diff --git a/api/src/org/labkey/api/settings/WriteableFolderLookAndFeelProperties.java b/api/src/org/labkey/api/settings/WriteableFolderLookAndFeelProperties.java index 9338e2c465a..ebca495d363 100644 --- a/api/src/org/labkey/api/settings/WriteableFolderLookAndFeelProperties.java +++ b/api/src/org/labkey/api/settings/WriteableFolderLookAndFeelProperties.java @@ -86,19 +86,19 @@ public void setDefaultDateTimeFormat(String defaultDateTimeFormat) throws Illega storeStringValue(defaultDateTimeFormatString, defaultDateTimeFormat); } - // Allows clearing the property to allow inheriting of this property alone. Should make this more obvious and universal, via "inherit/override" checkboxes and highlighting in the UI + // Allows clearing the property to allow inheriting of this property alone public void clearDefaultDateFormat() { remove(defaultDateFormatString); } - // Allows clearing the property to allow inheriting of this property alone. Should make this more obvious and universal, via "inherit/override" checkboxes and highlighting in the UI + // Allows clearing the property to allow inheriting of this property alone public void clearDefaultDateTimeFormat() { remove(defaultDateTimeFormatString); } - // Allows clearing the property to allow inheriting of this property alone. Should make this more obvious and universal, via "inherit/override" checkboxes and highlighting in the UI + // Allows clearing the property to allow inheriting of this property alone public void clearDefaultTimeFormat() { remove(defaultTimeFormatString); @@ -128,7 +128,7 @@ public static void saveDefaultTimeFormat(Container c, String defaultTimeFormat) props.save(); } - // Allows clearing the property to allow inheriting of this property alone. Should make this more obvious and universal, via "inherit/override" checkboxes and highlighting in the UI + // Allows clearing the property to allow inheriting of this property alone public void clearDefaultNumberFormat() { remove(defaultNumberFormatString); @@ -153,7 +153,8 @@ public static void saveDefaultNumberFormat(Container c, String defaultNumberForm public void setExtraDateParsingPattern(String pattern) throws IllegalArgumentException { // Check for legal format - FastDateFormat.getInstance(pattern); + if (null != pattern) + FastDateFormat.getInstance(pattern); storeStringValue(extraDateParsingPattern, pattern); } @@ -161,7 +162,8 @@ public void setExtraDateParsingPattern(String pattern) throws IllegalArgumentExc public void setExtraDateTimeParsingPattern(String pattern) throws IllegalArgumentException { // Check for legal format - FastDateFormat.getInstance(pattern); + if (null != pattern) + FastDateFormat.getInstance(pattern); storeStringValue(extraDateTimeParsingPattern, pattern); } @@ -169,23 +171,24 @@ public void setExtraDateTimeParsingPattern(String pattern) throws IllegalArgumen public void setExtraTimeParsingPattern(String pattern) throws IllegalArgumentException { // Check for legal format - FastDateFormat.getInstance(pattern); + if (null != pattern) + FastDateFormat.getInstance(pattern); storeStringValue(extraTimeParsingPattern, pattern); } - // Allows clearing the property to allow inheriting of this property alone. Should make this more obvious and universal, via "inherit/override" checkboxes and highlighting in the UI + // Allows clearing the property to allow inheriting of this property alone public void clearExtraDateParsingPattern() { remove(extraDateParsingPattern); } - // Allows clearing the property to allow inheriting of this property alone. Should make this more obvious and universal, via "inherit/override" checkboxes and highlighting in the UI + // Allows clearing the property to allow inheriting of this property alone public void clearExtraDateTimeParsingPattern() { remove(extraDateTimeParsingPattern); } - // Allows clearing the property to allow inheriting of this property alone. Should make this more obvious and universal, via "inherit/override" checkboxes and highlighting in the UI + // Allows clearing the property to allow inheriting of this property alone public void clearExtraTimeParsingPattern() { remove(extraTimeParsingPattern); @@ -215,7 +218,6 @@ public static void saveExtraTimeParsingPattern(Container c, String extraTimePars props.save(); } - public void clearRestrictedColumnsEnabled() { remove(restrictedColumnsEnabled); diff --git a/api/src/org/labkey/api/util/DateUtil.java b/api/src/org/labkey/api/util/DateUtil.java index 26eb9774a9f..92149050dad 100644 --- a/api/src/org/labkey/api/util/DateUtil.java +++ b/api/src/org/labkey/api/util/DateUtil.java @@ -106,8 +106,9 @@ public static boolean isStandardDateDisplayFormat(String dateFormat) public static String[] splitDateTimeFormat(String dateTimeFormat) { - // Tolerate any amount of whitespace between the parts - return dateTimeFormat.split("\\s+"); + // Tolerate any amount of whitespace between the parts -- note that standard time portion could have + // a space ("hh:mm a"), hence the limit + return dateTimeFormat.split("\\s+", 2); } public static boolean isStandardDateTimeDisplayFormat(String dateTimeFormat) diff --git a/core/src/org/labkey/core/admin/AdminController.java b/core/src/org/labkey/core/admin/AdminController.java index 60b034bdf1c..e7c93026b07 100644 --- a/core/src/org/labkey/core/admin/AdminController.java +++ b/core/src/org/labkey/core/admin/AdminController.java @@ -1700,6 +1700,7 @@ default boolean isFolderSetup() } } + // TODO: Delete pointless interface public interface SettingsForm { String getDefaultDateFormat(); @@ -1770,7 +1771,7 @@ public String description() public abstract String description(); } - public static class ProjectSettingsForm implements SettingsForm + public static class ProjectSettingsForm extends FolderSettingsForm { private boolean _shouldInherit; // new subfolders should inherit parent permissions private String _systemDescription; @@ -5316,7 +5317,7 @@ private FolderImportConfig getFolderImportConfigFromTemplateFolder(final ImportF ); } - private class FolderImportConfig { + private static class FolderImportConfig { Path pipelineUnzipFile; String originalFileName; Path archiveFile; @@ -5379,13 +5380,21 @@ private Set getRegisteredFolderWritersForImplicitExport(Container source public static class FolderSettingsForm implements SettingsForm { private String _defaultDateFormat; + private boolean _defaultDateFormatInherited; private String _defaultDateTimeFormat; + private boolean _defaultDateTimeFormatInherited; private String _defaultTimeFormat; + private boolean _defaultTimeFormatInherited; private String _defaultNumberFormat; + private boolean _defaultNumberFormatInherited; private String _extraDateParsingPattern; + private boolean _extraDateParsingPatternInherited; private String _extraDateTimeParsingPattern; + private boolean _extraDateTimeParsingPatternInherited; private String _extraTimeParsingPattern; + private boolean _extraTimeParsingPatternInherited; private boolean _restrictedColumnsEnabled; + private boolean _restrictedColumnsEnabledInherited; @Override public String getDefaultDateFormat() @@ -5399,6 +5408,17 @@ public void setDefaultDateFormat(String defaultDateFormat) _defaultDateFormat = defaultDateFormat; } + public boolean isDefaultDateFormatInherited() + { + return _defaultDateFormatInherited; + } + + @SuppressWarnings("unused") + public void setDefaultDateFormatInherited(boolean defaultDateFormatInherited) + { + _defaultDateFormatInherited = defaultDateFormatInherited; + } + @Override public String getDefaultDateTimeFormat() { @@ -5406,11 +5426,23 @@ public String getDefaultDateTimeFormat() } @Override + @SuppressWarnings("unused") public void setDefaultDateTimeFormat(String defaultDateTimeFormat) { _defaultDateTimeFormat = defaultDateTimeFormat; } + public boolean isDefaultDateTimeFormatInherited() + { + return _defaultDateTimeFormatInherited; + } + + @SuppressWarnings("unused") + public void setDefaultDateTimeFormatInherited(boolean defaultDateTimeFormatInherited) + { + _defaultDateTimeFormatInherited = defaultDateTimeFormatInherited; + } + @Override public String getDefaultTimeFormat() { @@ -5424,6 +5456,17 @@ public void setDefaultTimeFormat(String defaultTimeFormat) _defaultTimeFormat = defaultTimeFormat; } + public boolean isDefaultTimeFormatInherited() + { + return _defaultTimeFormatInherited; + } + + @SuppressWarnings("unused") + public void setDefaultTimeFormatInherited(boolean defaultTimeFormatInherited) + { + _defaultTimeFormatInherited = defaultTimeFormatInherited; + } + @Override public String getDefaultNumberFormat() { @@ -5436,6 +5479,17 @@ public void setDefaultNumberFormat(String defaultNumberFormat) _defaultNumberFormat = defaultNumberFormat; } + public boolean isDefaultNumberFormatInherited() + { + return _defaultNumberFormatInherited; + } + + @SuppressWarnings("unused") + public void setDefaultNumberFormatInherited(boolean defaultNumberFormatInherited) + { + _defaultNumberFormatInherited = defaultNumberFormatInherited; + } + @Override public String getExtraDateParsingPattern() { @@ -5448,6 +5502,17 @@ public void setExtraDateParsingPattern(String extraDateParsingPattern) _extraDateParsingPattern = extraDateParsingPattern; } + public boolean isExtraDateParsingPatternInherited() + { + return _extraDateParsingPatternInherited; + } + + @SuppressWarnings("unused") + public void setExtraDateParsingPatternInherited(boolean extraDateParsingPatternInherited) + { + _extraDateParsingPatternInherited = extraDateParsingPatternInherited; + } + @Override public String getExtraDateTimeParsingPattern() { @@ -5460,6 +5525,17 @@ public void setExtraDateTimeParsingPattern(String extraDateTimeParsingPattern) _extraDateTimeParsingPattern = extraDateTimeParsingPattern; } + public boolean isExtraDateTimeParsingPatternInherited() + { + return _extraDateTimeParsingPatternInherited; + } + + @SuppressWarnings("unused") + public void setExtraDateTimeParsingPatternInherited(boolean extraDateTimeParsingPatternInherited) + { + _extraDateTimeParsingPatternInherited = extraDateTimeParsingPatternInherited; + } + @Override public String getExtraTimeParsingPattern() { @@ -5473,6 +5549,17 @@ public void setExtraTimeParsingPattern(String extraTimeParsingPattern) _extraTimeParsingPattern = extraTimeParsingPattern; } + public boolean isExtraTimeParsingPatternInherited() + { + return _extraTimeParsingPatternInherited; + } + + @SuppressWarnings("unused") + public void setExtraTimeParsingPatternInherited(boolean extraTimeParsingPatternInherited) + { + _extraTimeParsingPatternInherited = extraTimeParsingPatternInherited; + } + @Override public boolean areRestrictedColumnsEnabled() { @@ -5480,10 +5567,23 @@ public boolean areRestrictedColumnsEnabled() } @Override + @SuppressWarnings("unused") public void setRestrictedColumnsEnabled(boolean restrictedColumnsEnabled) { _restrictedColumnsEnabled = restrictedColumnsEnabled; } + + // TODO: Handle inheritance + public boolean isRestrictedColumnsEnabledInherited() + { + return _restrictedColumnsEnabledInherited; + } + + @SuppressWarnings("unused") + public void setRestrictedColumnsEnabledInherited(boolean restrictedColumnsEnabledInherited) + { + _restrictedColumnsEnabledInherited = restrictedColumnsEnabledInherited; + } } @@ -11300,25 +11400,26 @@ public URLHelper getSuccessURL(FilesForm form) // Validate and populate the folder settings; save & log all changes - private static boolean saveFolderSettings(Container c, SettingsForm form, WriteableFolderLookAndFeelProperties props, User user, BindException errors) + private static boolean saveFolderSettings(Container c, FolderSettingsForm form, WriteableFolderLookAndFeelProperties props, User user, BindException errors) { - if (!validateAndSaveFormat(form.getDefaultDateFormat(), props::clearDefaultDateFormat, props::setDefaultDateFormat, errors, "date")) + if (!validateAndSaveFormat(form.getDefaultDateFormat(), form.isDefaultDateFormatInherited(), props::clearDefaultDateFormat, props::setDefaultDateFormat, errors, "date")) return false; - if (!validateAndSaveFormat(form.getDefaultDateTimeFormat(), props::clearDefaultDateTimeFormat, props::setDefaultDateTimeFormat, errors, "date-time")) + if (!validateAndSaveFormat(form.getDefaultDateTimeFormat(), form.isDefaultDateTimeFormatInherited(), props::clearDefaultDateTimeFormat, props::setDefaultDateTimeFormat, errors, "date-time")) return false; - if (!validateAndSaveFormat(form.getDefaultTimeFormat(), props::clearDefaultTimeFormat, props::setDefaultTimeFormat, errors, "time")) + if (!validateAndSaveFormat(form.getDefaultTimeFormat(), form.isDefaultTimeFormatInherited(), props::clearDefaultTimeFormat, props::setDefaultTimeFormat, errors, "time")) return false; - if (!validateAndSaveFormat(form.getDefaultNumberFormat(), props::clearDefaultNumberFormat, props::setDefaultNumberFormat, errors, "number")) + if (!validateAndSaveFormat(form.getDefaultNumberFormat(), form.isDefaultNumberFormatInherited(), props::clearDefaultNumberFormat, props::setDefaultNumberFormat, errors, "number")) return false; - if (!validateAndSaveFormat(form.getExtraDateParsingPattern(), props::clearExtraDateParsingPattern, props::setExtraDateParsingPattern, errors, "date")) + if (!validateAndSaveFormat(form.getExtraDateParsingPattern(), form.isExtraDateParsingPatternInherited(), props::clearExtraDateParsingPattern, props::setExtraDateParsingPattern, errors, "date")) return false; - if (!validateAndSaveFormat(form.getExtraDateTimeParsingPattern(), props::clearExtraDateTimeParsingPattern, props::setExtraDateTimeParsingPattern, errors, "date-time")) + if (!validateAndSaveFormat(form.getExtraDateTimeParsingPattern(), form.isExtraDateTimeParsingPatternInherited(), props::clearExtraDateTimeParsingPattern, props::setExtraDateTimeParsingPattern, errors, "date-time")) return false; - if (!validateAndSaveFormat(form.getExtraTimeParsingPattern(), props::clearExtraTimeParsingPattern, props::setExtraTimeParsingPattern, errors, "time")) + if (!validateAndSaveFormat(form.getExtraTimeParsingPattern(), form.isExtraTimeParsingPatternInherited(), props::clearExtraTimeParsingPattern, props::setExtraTimeParsingPattern, errors, "time")) return false; try { + // TODO: Handle inheritance props.setRestrictedColumnsEnabled(form.areRestrictedColumnsEnabled()); } catch (IllegalArgumentException e) @@ -11340,6 +11441,7 @@ private interface FormatSaver void save(String format) throws IllegalArgumentException; } + // TODO: Migrate callers and delete private static boolean validateAndSaveFormat(String format, Runnable clearer, FormatSaver saver, BindException errors, String what) { String defaultFormat = StringUtils.trimToNull(format); @@ -11363,6 +11465,30 @@ private static boolean validateAndSaveFormat(String format, Runnable clearer, Fo return true; } + // TODO: Use this! + private static boolean validateAndSaveFormat(String format, boolean inherited, Runnable clearer, FormatSaver saver, BindException errors, String what) + { + String defaultFormat = StringUtils.trimToNull(format); + if (inherited) + { + clearer.run(); + } + else + { + try + { + saver.save(defaultFormat); + } + catch (IllegalArgumentException e) + { + errors.reject(ERROR_MSG, "Invalid " + what + " format: " + e.getMessage()); + return false; + } + } + + return true; + } + public static class LookAndFeelView extends JspView { LookAndFeelView(BindException errors) diff --git a/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp b/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp index ab285828947..571fff352e6 100644 --- a/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp +++ b/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp @@ -471,14 +471,14 @@ if (addNone) { - map.put(null, NONE); + map.put("", NONE); } return select() .id(id) .name(id) .addOptions(map) - .selected(current) + .selected(NONE.equals(current) ? "" : current) .className(null) .addStyle("width:225px"); } @@ -489,7 +489,7 @@ return HtmlString.EMPTY_STRING; HtmlStringBuilder builder = HtmlStringBuilder.of(HtmlString.unsafe(" Date: Tue, 8 Oct 2024 08:41:14 -0700 Subject: [PATCH 04/32] Comments --- core/src/org/labkey/core/admin/AdminController.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/org/labkey/core/admin/AdminController.java b/core/src/org/labkey/core/admin/AdminController.java index e7c93026b07..9660cfc7e70 100644 --- a/core/src/org/labkey/core/admin/AdminController.java +++ b/core/src/org/labkey/core/admin/AdminController.java @@ -1771,6 +1771,7 @@ public String description() public abstract String description(); } + // TODO: Remove the folder-specific properties public static class ProjectSettingsForm extends FolderSettingsForm { private boolean _shouldInherit; // new subfolders should inherit parent permissions @@ -11014,7 +11015,7 @@ protected TYPE getType() } @RequiresPermission(AdminPermission.class) - // TODO Should this be renamed UpdateContainerSettingsAction? There's nothing that explicity checks for this being a project vs. a folder + // TODO Should this be renamed UpdateContainerSettingsAction? There's nothing that explicitly checks for this being a project vs. a folder public static class UpdateProjectSettingsAction extends MutatingApiAction { @Override @@ -11037,6 +11038,9 @@ public Object execute(SimpleApiJsonForm form, BindException errors) } } + // TODO: Before updating this to handle the new-style inheritance parameters getting posted, we should seriously + // consider flipping the approach to where both callers pass in a ProjectSettingsForm and then change this method + // to call saveFolderSettings() instead of duplicating all the folder settings private static boolean saveProjectSettings(JSONObject json, User user, Container c, BindException errors) { WriteableLookAndFeelProperties props = LookAndFeelProperties.getWriteableInstance(c); @@ -11071,7 +11075,6 @@ private static boolean saveProjectSettings(JSONObject json, User user, Container } } - // a few properties on this page should be restricted to operational permissions (i.e. site admin) if (hasAdminOpsPerm) { @@ -11114,7 +11117,6 @@ private static boolean saveProjectSettings(JSONObject json, User user, Container } props.setCustomWelcome(welcomeUrl); } - } if (json.has("companyName")) @@ -11165,7 +11167,6 @@ private static boolean saveProjectSettings(JSONObject json, User user, Container { DateParsingMode dateParsingMode = DateParsingMode.fromString(json.optString("dateParsingMode")); props.setDateParsingMode(dateParsingMode); - } if (json.has("defaultDateFormat") && !validateAndSaveFormat(json.optString("defaultDateFormat"), props::clearDefaultDateFormat, props::setDefaultDateFormat, errors, "date")) From 49b2f0effe0334f90280ab74a4ee81a089353ab8 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Tue, 8 Oct 2024 12:43:39 -0700 Subject: [PATCH 05/32] Fix restrict charting columns inheritance --- core/src/org/labkey/core/admin/AdminController.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/core/src/org/labkey/core/admin/AdminController.java b/core/src/org/labkey/core/admin/AdminController.java index 9660cfc7e70..5e191f54756 100644 --- a/core/src/org/labkey/core/admin/AdminController.java +++ b/core/src/org/labkey/core/admin/AdminController.java @@ -11418,16 +11418,10 @@ private static boolean saveFolderSettings(Container c, FolderSettingsForm form, if (!validateAndSaveFormat(form.getExtraTimeParsingPattern(), form.isExtraTimeParsingPatternInherited(), props::clearExtraTimeParsingPattern, props::setExtraTimeParsingPattern, errors, "time")) return false; - try - { - // TODO: Handle inheritance + if (form.isRestrictedColumnsEnabledInherited()) + props.clearRestrictedColumnsEnabled(); + else props.setRestrictedColumnsEnabled(form.areRestrictedColumnsEnabled()); - } - catch (IllegalArgumentException e) - { - errors.reject(ERROR_MSG, "Invalid restricted columns flag: " + e.getMessage()); - return false; - } props.save(); From eb7fa45accf43981ed25a16420849d50d87e4014 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Wed, 9 Oct 2024 12:53:57 -0700 Subject: [PATCH 06/32] Warnings --- .../core/admin/DateDisplayFormatType.java | 8 +- .../core/admin/lookAndFeelProperties.jsp | 91 ++++++++++++++----- 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/core/src/org/labkey/core/admin/DateDisplayFormatType.java b/core/src/org/labkey/core/admin/DateDisplayFormatType.java index 24bc12f3f12..15ab8f27d57 100644 --- a/core/src/org/labkey/core/admin/DateDisplayFormatType.java +++ b/core/src/org/labkey/core/admin/DateDisplayFormatType.java @@ -16,7 +16,7 @@ public enum DateDisplayFormatType Date(JdbcType.DATE, PropertyType.DATE) { @Override - boolean isStandardFormat(String formatPattern) + public boolean isStandardFormat(String formatPattern) { return DateUtil.isStandardDateDisplayFormat(formatPattern); } @@ -30,7 +30,7 @@ String getStoredFormat(LookAndFeelProperties laf) DateTime(JdbcType.TIMESTAMP, PropertyType.DATE_TIME) { @Override - boolean isStandardFormat(String formatPattern) + public boolean isStandardFormat(String formatPattern) { return DateUtil.isStandardDateTimeDisplayFormat(formatPattern); } @@ -44,7 +44,7 @@ String getStoredFormat(LookAndFeelProperties laf) Time(JdbcType.TIME, PropertyType.TIME) { @Override - boolean isStandardFormat(String formatPattern) + public boolean isStandardFormat(String formatPattern) { return DateUtil.isStandardTimeDisplayFormat(formatPattern); } @@ -102,7 +102,7 @@ public static DateDisplayFormatType getForRangeUri(String rangeUri) } @SuppressWarnings("BooleanMethodIsAlwaysInverted") - abstract boolean isStandardFormat(String formatPattern); + public abstract boolean isStandardFormat(String formatPattern); abstract String getStoredFormat(LookAndFeelProperties laf); } diff --git a/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp b/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp index 571fff352e6..5ea5a099e78 100644 --- a/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp +++ b/core/src/org/labkey/core/admin/lookAndFeelProperties.jsp @@ -31,11 +31,12 @@ <%@ page import="org.labkey.api.util.Formats" %> <%@ page import="org.labkey.api.util.HtmlString" %> <%@ page import="org.labkey.api.util.HtmlStringBuilder" %> -<%@ page import="org.labkey.api.util.element.Select.SelectBuilder" %> <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.core.admin.AdminController" %> <%@ page import="org.labkey.core.admin.AdminController.AdminUrlsImpl" %> +<%@ page import="org.labkey.core.admin.DateDisplayFormatType" %> +<%@ page import="java.io.IOException" %> <%@ page import="java.text.SimpleDateFormat" %> <%@ page import="java.util.Date" %> <%@ page import="java.util.LinkedHashMap" %> @@ -62,6 +63,9 @@ boolean hasPremiumModule = ModuleLoader.getInstance().hasModule("Premium"); %> <%=formatMissedErrors("form")%> + <%=getTroubleshooterWarning(canUpdate, HtmlString.unsafe(""))%> @@ -279,29 +283,32 @@ - <%=inheritCheckbox(c, defaultDateFormat.name(), laf.getDefaultDateFormatStored())%> - + <% boolean inherited = null == laf.getDefaultDateFormatStored(); %> + <%=inheritCheckbox(c, inherited, defaultDateFormat.name())%> + - <%=inheritCheckbox(c, defaultDateTimeFormat.name(), laf.getDefaultDateTimeFormatStored())%> + <% inherited = null == laf.getDefaultDateTimeFormatStored(); %> + <%=inheritCheckbox(c, inherited, defaultDateTimeFormat.name(), "dateSelect", "timeSelect")%> <% String[] parts = DateUtil.splitDateTimeFormat(laf.getDefaultDateTimeFormat()); %> - <%=inheritCheckbox(c, defaultTimeFormat.name(), laf.getDefaultTimeFormatStored())%> - + <% inherited = null == laf.getDefaultTimeFormatStored(); %> + <%=inheritCheckbox(c, inherited, defaultTimeFormat.name())%> + - <%=inheritCheckbox(c, defaultNumberFormat.name(), laf.getDefaultNumberFormatStored())%> + <%=inheritCheckbox(c, null == laf.getDefaultNumberFormatStored(), defaultNumberFormat.name())%> @@ -333,17 +340,17 @@ %> - <%=inheritCheckbox(c, extraDateParsingPattern.name(), laf.getExtraDateParsingPatternStored())%> + <%=inheritCheckbox(c, null == laf.getExtraDateParsingPatternStored(), extraDateParsingPattern.name())%> - <%=inheritCheckbox(c, extraDateTimeParsingPattern.name(), laf.getExtraDateTimeParsingPatternStored())%> + <%=inheritCheckbox(c, null == laf.getExtraDateTimeParsingPatternStored(), extraDateTimeParsingPattern.name())%> - <%=inheritCheckbox(c, extraTimeParsingPattern.name(), laf.getExtraTimeParsingPatternStored())%> + <%=inheritCheckbox(c, null == laf.getExtraTimeParsingPatternStored(), extraTimeParsingPattern.name())%> @@ -355,7 +362,7 @@ - <%=inheritCheckbox(c, restrictedColumnsEnabled.name(), laf.areRestrictedColumnsEnabledStored())%> + <%=inheritCheckbox(c, null == laf.areRestrictedColumnsEnabledStored(), restrictedColumnsEnabled.name())%> @@ -426,6 +433,14 @@
"), HtmlString.unsafe("
<%=helpPopup("Date format", dateFormatHelp, true)%><%=select(defaultDateFormat.name(), DateUtil.STANDARD_DATE_DISPLAY_FORMATS, laf.getDefaultDateFormat(), false)%><% select(out, DateDisplayFormatType.Date, defaultDateFormat.name(), DateUtil.STANDARD_DATE_DISPLAY_FORMATS, laf.getDefaultDateFormat(), false, inherited); %>
<%=helpPopup("Date-time format", dateTimeFormatHelp, true)%> - <%=select("dateSelect", DateUtil.STANDARD_DATE_DISPLAY_FORMATS, parts.length > 0 ? parts[0] : null, false)%>   - <%=select("timeSelect", DateUtil.STANDARD_TIME_DISPLAY_FORMATS, parts.length > 1 ? parts[1] : NONE, true)%> + <% select(out, DateDisplayFormatType.Date, "dateSelect", DateUtil.STANDARD_DATE_DISPLAY_FORMATS, parts.length > 0 ? parts[0] : null, false, inherited); %>   + <% select(out, DateDisplayFormatType.Time, "timeSelect", DateUtil.STANDARD_TIME_DISPLAY_FORMATS, parts.length > 1 ? parts[1] : NONE, true, inherited); %>
<%=helpPopup("Time format", timeFormatHelp, true)%><%=select(defaultTimeFormat.name(), DateUtil.STANDARD_TIME_DISPLAY_FORMATS, laf.getDefaultTimeFormat(), false)%><% select(out, DateDisplayFormatType.Time, defaultTimeFormat.name(), DateUtil.STANDARD_TIME_DISPLAY_FORMATS, laf.getDefaultTimeFormat(), false, inherited); %>
<%=helpPopup("Number format", decimalFormatHelp, true)%>
<%=helpPopup("Extra date parsing pattern", dateParsingHelp, true)%>
<%=helpPopup("Extra date-time parsing pattern", dateTimeParsingHelp, true, 300)%>
<%=helpPopup("Extra time parsing pattern", timeParsingHelp, true)%>
>