Skip to content

Commit 167faa4

Browse files
committed
Create annotation to track UtilityActions and Admin Console page to list them
1 parent 5db8f70 commit 167faa4

File tree

5 files changed

+288
-1
lines changed

5 files changed

+288
-1
lines changed

SequenceAnalysis/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ dependencies {
4444
BuildUtils.addLabKeyDependency(project: project, config: "apiImplementation", depProjectPath: ":server:modules:LabDevKitModules:laboratory", depProjectConfig: "apiJarFile")
4545
BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: ":server:modules:LabDevKitModules:LDK", depProjectConfig: "apiJarFile")
4646
BuildUtils.addLabKeyDependency(project: project, config: "apiImplementation", depProjectPath: ":server:modules:LabDevKitModules:LDK", depProjectConfig: "apiJarFile")
47+
BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: ":server:modules:DiscvrLabKeyModules:discvrcore", depProjectConfig: "apiJarFile")
4748
BuildUtils.addExternalDependency(
4849
project,
4950
new ExternalDependency(

SequenceAnalysis/src/org/labkey/sequenceanalysis/SequenceAnalysisController.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
import org.labkey.api.data.Table;
7373
import org.labkey.api.data.TableInfo;
7474
import org.labkey.api.data.TableSelector;
75+
import org.labkey.api.discvrcore.annotation.UtilityAction;
7576
import org.labkey.api.exceptions.OptimisticConflictException;
7677
import org.labkey.api.exp.ExperimentException;
7778
import org.labkey.api.exp.api.DataType;
@@ -501,6 +502,7 @@ public void export(ConvertTextToFileForm form, HttpServletResponse response, Bin
501502
}
502503
}
503504

505+
@UtilityAction(label = "Find Orphan Files", description = "This will start a pipeline job that will inspect all files in this folder to identify potential orphan or otherwise unnecessary files")
504506
@RequiresPermission(ReadPermission.class)
505507
public static class FindOrphanFilesAction extends ConfirmAction<Object>
506508
{
@@ -5065,6 +5067,7 @@ public void setOutputFileIds(Integer[] outputFileIds)
50655067
}
50665068
}
50675069

5070+
@UtilityAction(label = "Update ExpData Path", description = "This will update the DataFileUrl on the selected ExpData to the path provided")
50685071
@RequiresSiteAdmin
50695072
public static class UpdateExpDataPathAction extends ConfirmAction<UpdateExpDataPathForm>
50705073
{
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.labkey.api.discvrcore.annotation;
2+
3+
import org.jetbrains.annotations.Nullable;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
11+
public @interface UtilityAction
12+
{
13+
String description();
14+
15+
String label();
16+
}

discvrcore/src/org/labkey/discvrcore/DiscvrCoreController.java

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,279 @@
1616

1717
package org.labkey.discvrcore;
1818

19+
import org.apache.commons.lang3.StringUtils;
20+
import org.apache.logging.log4j.Logger;
21+
import org.jetbrains.annotations.NotNull;
22+
import org.labkey.api.action.ConfirmAction;
23+
import org.labkey.api.action.SimpleErrorView;
24+
import org.labkey.api.action.SimpleRedirectAction;
25+
import org.labkey.api.action.SimpleViewAction;
1926
import org.labkey.api.action.SpringActionController;
27+
import org.labkey.api.audit.AuditLogService;
28+
import org.labkey.api.data.Container;
29+
import org.labkey.api.data.ContainerManager;
30+
import org.labkey.api.data.CoreSchema;
31+
import org.labkey.api.data.DbSequenceManager;
32+
import org.labkey.api.data.SqlExecutor;
33+
import org.labkey.api.data.TableInfo;
34+
import org.labkey.api.discvrcore.annotation.UtilityAction;
35+
import org.labkey.api.module.Module;
36+
import org.labkey.api.module.ModuleLoader;
37+
import org.labkey.api.pipeline.PipelineUrls;
38+
import org.labkey.api.query.DetailsURL;
39+
import org.labkey.api.query.UserSchema;
40+
import org.labkey.api.security.RequiresPermission;
41+
import org.labkey.api.security.permissions.AdminPermission;
42+
import org.labkey.api.util.DOM;
43+
import org.labkey.api.util.GUID;
44+
import org.labkey.api.util.Link;
45+
import org.labkey.api.util.PageFlowUtil;
46+
import org.labkey.api.util.URLHelper;
47+
import org.labkey.api.util.logging.LogHelper;
48+
import org.labkey.api.view.ActionURL;
49+
import org.labkey.api.view.HtmlView;
50+
import org.labkey.api.view.NavTree;
51+
import org.springframework.validation.BindException;
52+
import org.springframework.validation.Errors;
53+
import org.springframework.web.servlet.ModelAndView;
54+
import org.springframework.web.servlet.mvc.Controller;
55+
56+
import java.util.Arrays;
57+
import java.util.Collection;
58+
import java.util.Map;
59+
import java.util.TreeMap;
60+
61+
import static javax.swing.Spring.width;
62+
import static org.labkey.api.util.DOM.Attribute.valign;
63+
import static org.labkey.api.util.DOM.at;
64+
import static org.labkey.api.util.DOM.cl;
2065

2166
public class DiscvrCoreController extends SpringActionController
2267
{
2368
private static final DefaultActionResolver _actionResolver = new DefaultActionResolver(DiscvrCoreController.class);
2469
public static final String NAME = "discvrcore";
2570

71+
private static final Logger _log = LogHelper.getLogger(DiscvrCoreController.class, "Messages from DISCVR Core Controller");
72+
2673
public DiscvrCoreController()
2774
{
2875
setActionResolver(_actionResolver);
2976
}
77+
78+
@UtilityAction(label = "Truncate Query Audit Log", description = "Provides a mechanism to truncate the query and dataset audit tables for a container")
79+
@RequiresPermission(AdminPermission.class)
80+
public static class TruncateQueryAuditLogAction extends ConfirmAction<Object>
81+
{
82+
@Override
83+
public ModelAndView getConfirmView(Object o, BindException errors) throws Exception
84+
{
85+
setTitle("Truncate Query/Dataset Audit Logs");
86+
87+
return HtmlView.of("This will truncate the query and dataset audit logs for this container. Do you want to continue?");
88+
}
89+
90+
@Override
91+
public boolean handlePost(Object o, BindException errors) throws Exception
92+
{
93+
UserSchema us = AuditLogService.getAuditLogSchema(getUser(), getContainer());
94+
for (String tableName : Arrays.asList("DatasetAuditEvent", "QueryUpdateAuditEvent"))
95+
{
96+
TableInfo ti = us.getTable(tableName);
97+
ti.getUpdateService().truncateRows(getUser(), getContainer(), null, null);
98+
}
99+
100+
return true;
101+
}
102+
103+
@Override
104+
public void validateCommand(Object o, Errors errors)
105+
{
106+
107+
}
108+
109+
@NotNull
110+
@Override
111+
public URLHelper getSuccessURL(Object o)
112+
{
113+
return PageFlowUtil.urlProvider(PipelineUrls.class).urlBegin(getContainer());
114+
}
115+
}
116+
117+
@RequiresPermission(AdminPermission.class)
118+
public static class ShowUtilityActionsAction extends SimpleViewAction<Object>
119+
{
120+
@Override
121+
public ModelAndView getView(Object form, BindException errors)
122+
{
123+
Map<String, DOM.Renderable> items = new TreeMap<>();
124+
Collection<Module> modules = getContainer().isRoot() ? ModuleLoader.getInstance().getModules() : getContainer().getActiveModules();
125+
for (Module m : modules)
126+
{
127+
m.getControllerNameToClass().forEach((key, controllerCls) -> {
128+
Arrays.stream(controllerCls.getDeclaredClasses()).filter(x -> x.isAnnotationPresent(UtilityAction.class)).forEach(x -> {
129+
if (Controller.class.isAssignableFrom(x))
130+
{
131+
UtilityAction annot = x.getAnnotation(UtilityAction.class);
132+
133+
Class<? extends Controller> actionClass = (Class<? extends Controller>)x;
134+
items.put(annot.label(), DOM.TR(
135+
DOM.TD(at(valign,"top"), new Link.LinkBuilder(annot.label()).href(new ActionURL(actionClass, getContainer())).build()),
136+
DOM.TD(at(valign,"top"), annot.description())
137+
));
138+
}
139+
});
140+
});
141+
}
142+
143+
return new HtmlView(DOM.TABLE(cl("labkey-data-region-legacy","labkey-show-borders"), items.values()));
144+
}
145+
146+
@Override
147+
public void addNavTrail(NavTree root)
148+
{
149+
root.addChild("Utility & Management Actions");
150+
}
151+
}
152+
153+
@UtilityAction(label = "Move Workbook", description = "This will move this workbook to the selected folder, renaming this workbook to match the series in that container. Note: there are many reasons this can be problematic, so please do this with great care")
154+
@RequiresPermission(AdminPermission.class)
155+
public static class MoveWorkbookAction extends ConfirmAction<MoveWorkbookForm>
156+
{
157+
private Container _movedWb = null;
158+
159+
@Override
160+
public void validateCommand(MoveWorkbookForm form, Errors errors)
161+
{
162+
163+
}
164+
165+
@Override
166+
public ModelAndView getConfirmView(MoveWorkbookForm form, BindException errors) throws Exception
167+
{
168+
if (!getContainer().isWorkbook())
169+
{
170+
errors.reject(ERROR_MSG, "This is only supported for workbooks");
171+
return new SimpleErrorView(errors);
172+
}
173+
174+
String sb = "This will move this workbook to the selected folder, renaming this workbook to match the series in that container. Note: there are many reasons this can be problematic, so please do this with great care<p>" +
175+
"<input name=\"targetContainer\" type=\"text\"></input>";
176+
177+
return new HtmlView(sb);
178+
}
179+
180+
@Override
181+
public boolean handlePost(MoveWorkbookForm form, BindException errors) throws Exception
182+
{
183+
Container toMove = getContainer();
184+
if (!toMove.isWorkbook())
185+
{
186+
errors.reject(ERROR_MSG, "This is only supported for workbooks");
187+
return false;
188+
}
189+
190+
if (StringUtils.trimToNull(form.getTargetContainer()) == null)
191+
{
192+
errors.reject(ERROR_MSG, "Must provide target container");
193+
return false;
194+
}
195+
196+
Container target = ContainerManager.getForPath(StringUtils.trimToNull(form.getTargetContainer()));
197+
if (target == null)
198+
{
199+
target = ContainerManager.getForId(StringUtils.trimToNull(form.getTargetContainer()));
200+
}
201+
202+
if (target == null)
203+
{
204+
errors.reject(ERROR_MSG, "Unknown container: " + form.getTargetContainer());
205+
return false;
206+
}
207+
208+
if (target.isWorkbook())
209+
{
210+
errors.reject(ERROR_MSG, "Target cannot be a workbook: " + form.getTargetContainer());
211+
return false;
212+
}
213+
214+
if (ContainerManager.isSystemContainer(target))
215+
{
216+
errors.reject(ERROR_MSG, "Cannot move to system containers: " + form.getTargetContainer());
217+
return false;
218+
}
219+
220+
if (target.equals(toMove.getParent()))
221+
{
222+
errors.reject(ERROR_MSG, "Cannot move the workbook to its current parent: " + form.getTargetContainer());
223+
return false;
224+
}
225+
226+
//NOTE: transaction causing problems for larger sites?
227+
//try (DbScope.Transaction transaction = CoreSchema.getInstance().getSchema().getScope().ensureTransaction())
228+
//{
229+
//first rename workbook to make unique
230+
String tempName = new GUID().toString();
231+
int sortOrder = (int) DbSequenceManager.get(target, ContainerManager.WORKBOOK_DBSEQUENCE_NAME).next();
232+
_log.info("renaming workbook to in preparation for move from: " + toMove.getPath() + " to: " + tempName);
233+
ContainerManager.rename(toMove, getUser(), tempName);
234+
toMove = ContainerManager.getForId(toMove.getId());
235+
236+
//then move parent
237+
_log.info("moving workbook from: " + toMove.getPath() + " to: " + target.getPath());
238+
ContainerManager.move(toMove, target, getUser());
239+
toMove = ContainerManager.getForId(toMove.getId());
240+
241+
//finally move to correct name
242+
_log.info("renaming workbook from: " + toMove.getPath() + " to: " + sortOrder);
243+
ContainerManager.rename(toMove, getUser(), String.valueOf(sortOrder));
244+
toMove.setSortOrder(sortOrder);
245+
new SqlExecutor(CoreSchema.getInstance().getSchema()).execute("UPDATE core.containers SET SortOrder = ? WHERE EntityId = ?", toMove.getSortOrder(), toMove.getId());
246+
toMove = ContainerManager.getForId(toMove.getId());
247+
248+
//transaction.commit();
249+
_log.info("workbook move finished");
250+
251+
_movedWb = toMove;
252+
//}
253+
254+
return true;
255+
}
256+
257+
@NotNull
258+
@Override
259+
public URLHelper getSuccessURL(MoveWorkbookForm moveWorkbookForm)
260+
{
261+
if (_movedWb == null)
262+
return getContainer().getStartURL(getUser());
263+
else
264+
return _movedWb.getStartURL(getUser());
265+
}
266+
}
267+
268+
public static class MoveWorkbookForm
269+
{
270+
private String _targetContainer;
271+
272+
public String getTargetContainer()
273+
{
274+
return _targetContainer;
275+
}
276+
277+
public void setTargetContainer(String targetContainer)
278+
{
279+
_targetContainer = targetContainer;
280+
}
281+
}
282+
283+
// This allows registration of this action without creating a dependency between laboratory and discvrcore
284+
@UtilityAction(label = "Set Table Increment Value", description = "This allows you to reset the current value for an auto-incrementing table")
285+
@RequiresPermission(AdminPermission.class)
286+
public class SetTableIncrementValueAction extends SimpleRedirectAction<Object>
287+
{
288+
@Override
289+
public URLHelper getRedirectURL(Object o) throws Exception
290+
{
291+
return DetailsURL.fromString("laboratory/setTableIncrementValue.view", getContainer()).getActionURL();
292+
}
293+
}
30294
}

discvrcore/src/org/labkey/discvrcore/DiscvrCoreModule.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818

1919
import org.jetbrains.annotations.NotNull;
2020
import org.jetbrains.annotations.Nullable;
21+
import org.labkey.api.data.ContainerManager;
2122
import org.labkey.api.module.DefaultModule;
2223
import org.labkey.api.module.ModuleContext;
24+
import org.labkey.api.query.DetailsURL;
25+
import org.labkey.api.settings.AdminConsole;
2326
import org.labkey.api.util.PageFlowUtil;
2427
import org.labkey.api.view.WebPartFactory;
2528

@@ -66,7 +69,7 @@ protected void init()
6669
@Override
6770
public void doStartup(ModuleContext moduleContext)
6871
{
69-
72+
AdminConsole.addLink(AdminConsole.SettingsLinkType.Management, "site utility actions", DetailsURL.fromString("discvrcore/showUtilityActions.view", ContainerManager.getRoot()).getActionURL());
7073
}
7174

7275
@Override

0 commit comments

Comments
 (0)