diff --git a/luminex/src/org/labkey/luminex/query/AnalyteSinglePointControlTable.java b/luminex/src/org/labkey/luminex/query/AnalyteSinglePointControlTable.java index 5e0d830d33..1f81fed166 100644 --- a/luminex/src/org/labkey/luminex/query/AnalyteSinglePointControlTable.java +++ b/luminex/src/org/labkey/luminex/query/AnalyteSinglePointControlTable.java @@ -140,7 +140,7 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep int analyte = (int)ctx.get("analyte"); int singlePointControl = (int)ctx.get("singlePointControl"); - String linkTag = ""; + String linkTag = ""; out.write( String.format(linkTag, protocolId, analyte, singlePointControl, "MFI") ); out.write( String.format("", AppProps.getInstance().getContextPath() + "/luminex/ljPlotIcon.png") ); @@ -165,7 +165,7 @@ public boolean isFilterable() // set the default columns for this table to be those used for the QC Report List defaultCols = new ArrayList<>(); defaultCols.add(FieldKey.fromParts("SinglePointControl", "Run", "Name")); - defaultCols.add(FieldKey.fromParts("LJPlots")); + defaultCols.add(FieldKey.fromParts("L-J Plots")); defaultCols.add(FieldKey.fromParts("SinglePointControl", "Name")); defaultCols.add(FieldKey.fromParts("SinglePointControl", "Run", "Batch", "Network")); defaultCols.add(FieldKey.fromParts("SinglePointControl", "Run", "Batch", "CustomProtocol")); @@ -180,7 +180,7 @@ public boolean isFilterable() defaultCols.add(FieldKey.fromParts("Analyte", "Properties", "LotNumber")); defaultCols.add(FieldKey.fromParts("GuideSet", "Created")); defaultCols.add(FieldKey.fromParts("AverageFiBkgd")); - defaultCols.add(FieldKey.fromParts("AverageFiBkgdQCFlagsEnabled")); + defaultCols.add(FieldKey.fromParts("SinglePointControl", "Run", "QCFlags")); setDefaultVisibleColumns(defaultCols); } diff --git a/luminex/src/org/labkey/luminex/query/AnalyteTitrationTable.java b/luminex/src/org/labkey/luminex/query/AnalyteTitrationTable.java index cdcdd7b710..262be3ef3f 100644 --- a/luminex/src/org/labkey/luminex/query/AnalyteTitrationTable.java +++ b/luminex/src/org/labkey/luminex/query/AnalyteTitrationTable.java @@ -141,7 +141,7 @@ public void renderGridCellContents(RenderContext ctx, Writer out) throws IOExcep int analyte = (int)ctx.get("analyte"); int titration = (int)ctx.get("titration"); - String jsFuncCall = "javascript:LABKEY.LeveyJenningsPlotHelper.getLeveyJenningsPlotWindow(%d,%d,%d,'%s')"; + String jsFuncCall = "javascript:LABKEY.LeveyJenningsPlotHelper.getLeveyJenningsPlotWindow(%d,%d,%d,'%s','Titration')"; NavTree ljPlotsNav = new NavTree("Levey-Jennings Plot Menu"); ljPlotsNav.setImage(AppProps.getInstance().getContextPath() + "/luminex/ljPlotIcon.png", 27, 20); @@ -174,7 +174,7 @@ public boolean isFilterable() // set the default columns for this table to be those used for the QC Report List defaultCols = new ArrayList<>(); defaultCols.add(FieldKey.fromParts("Titration", "Run", "Name")); - defaultCols.add(FieldKey.fromParts("LJPlots")); + defaultCols.add(FieldKey.fromParts("L-J Plots")); defaultCols.add(FieldKey.fromParts("Titration")); defaultCols.add(FieldKey.fromParts("Titration", "Standard")); defaultCols.add(FieldKey.fromParts("Titration", "QCControl")); @@ -196,6 +196,7 @@ public boolean isFilterable() defaultCols.add(FieldKey.fromParts(StatsService.CurveFitType.FIVE_PARAMETER.getLabel() + "CurveFit", "EC50")); defaultCols.add(FieldKey.fromParts("MaxFI")); defaultCols.add(FieldKey.fromParts("TrapezoidalCurveFit", "AUC")); + defaultCols.add(FieldKey.fromParts("Titration", "Run", "QCFlags")); setDefaultVisibleColumns(defaultCols); } diff --git a/luminex/src/org/labkey/luminex/view/leveyJenningsReport.jsp b/luminex/src/org/labkey/luminex/view/leveyJenningsReport.jsp index f232529729..19badf9774 100644 --- a/luminex/src/org/labkey/luminex/view/leveyJenningsReport.jsp +++ b/luminex/src/org/labkey/luminex/view/leveyJenningsReport.jsp @@ -21,7 +21,6 @@ */ %> -<%@ page import="org.labkey.api.util.PageFlowUtil" %> <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.api.view.template.ClientDependencies" %> @@ -41,6 +40,13 @@ LeveyJenningsForm bean = me.getModelBean(); %> + +
@@ -58,7 +64,7 @@ var $h = Ext.util.Format.htmlEncode; - // the default number of records to return for the report when no start and end date are provided + // the default number of records to return for the report when no filters have been applied var defaultRowSize = 30; // local variables for storing the selected graph parameters @@ -189,15 +195,7 @@ controlType: _controlType, assayName: _protocolName, listeners: { - 'applyGraphBtnClicked': function(analyte, isotype, conjugate){ - _analyte = analyte; - _isotype = isotype; - _conjugate = conjugate; - - guideSetPanel.graphParamsSelected(analyte, isotype, conjugate); - trendPlotPanel.graphParamsSelected(analyte, isotype, conjugate); - trackingDataPanel.graphParamsSelected(analyte, isotype, conjugate, null, null); - }, + 'applyGraphBtnClicked': graphParamsSelected, 'graphParamsChanged': function(){ guideSetPanel.disable(); trendPlotPanel.disable(); @@ -231,7 +229,7 @@ 'currentGuideSetUpdated': function() { guideSetPanel.toggleExportBtn(false); trendPlotPanel.setTrendPlotLoading(); - trackingDataPanel.graphParamsSelected(_analyte, _isotype, _conjugate, trendPlotPanel.getStartDate(), trendPlotPanel.getEndDate()); + trackingDataPanel.graphParamsSelected(_analyte, _isotype, _conjugate, true); }, 'exportPdfBtnClicked': function() { trendPlotPanel.exportToPdf(); @@ -255,10 +253,6 @@ has4PLCurveFit: _has4PLCurveFit, has5PLCurveFit: _has5PLCurveFit, listeners: { - 'reportFilterApplied': function(startDate, endDate, network, networkAny, protocol, protocolAny) { - trendPlotPanel.setTrendPlotLoading(); - trackingDataPanel.graphParamsSelected(_analyte, _isotype, _conjugate, startDate, endDate, network, networkAny, protocol, protocolAny); - }, 'togglePdfBtn': function(toEnable) { guideSetPanel.toggleExportBtn(toEnable); } @@ -268,7 +262,6 @@ // initialize the grid panel to display the tracking data var trackingDataPanel = new LABKEY.LeveyJenningsTrackingDataPanel({ renderTo: 'trackingDataPanel', - cls: 'extContainer', controlName: _controlName, controlType: _controlType, assayName: _protocolName, @@ -281,14 +274,31 @@ 'appliedGuideSetUpdated': function() { guideSetPanel.toggleExportBtn(false); trendPlotPanel.setTrendPlotLoading(); - trackingDataPanel.graphParamsSelected(_analyte, _isotype, _conjugate, trendPlotPanel.getStartDate(), trendPlotPanel.getEndDate(), - trendPlotPanel.network, trendPlotPanel.networkAny, trendPlotPanel.protocol, trendPlotPanel.protocolAny); + trackingDataPanel.graphParamsSelected(_analyte, _isotype, _conjugate, true); + }, + 'plotDataLoading': function(store, hasGuideSetUpdates) { + trendPlotPanel.plotDataLoading(store, hasGuideSetUpdates); + }, + 'plotDataLoaded': function(store, hasReportFilter) { + trendPlotPanel.plotDataLoaded(store, hasReportFilter); }, - 'trackingDataLoaded': function(store) { - trendPlotPanel.trackingDataLoaded(store); - } } }); + + function graphParamsSelected(analyte, isotype, conjugate){ + _analyte = analyte; + _isotype = isotype; + _conjugate = conjugate; + + guideSetPanel.graphParamsSelected(analyte, isotype, conjugate); + trendPlotPanel.graphParamsSelected(analyte, isotype, conjugate); + trackingDataPanel.graphParamsSelected(analyte, isotype, conjugate); + } + + var urlParams = LABKEY.ActionURL.getParameters(); + if (urlParams.hasOwnProperty("analyte") && urlParams.hasOwnProperty("isotype") && urlParams.hasOwnProperty("conjugate")) { + graphParamsSelected(urlParams.analyte, urlParams.isotype, urlParams.conjugate); + } } Ext.onReady(init); diff --git a/luminex/test/src/org/labkey/test/tests/luminex/LuminexGuideSetDisablingTest.java b/luminex/test/src/org/labkey/test/tests/luminex/LuminexGuideSetDisablingTest.java index c20cf3c523..e24891622d 100644 --- a/luminex/test/src/org/labkey/test/tests/luminex/LuminexGuideSetDisablingTest.java +++ b/luminex/test/src/org/labkey/test/tests/luminex/LuminexGuideSetDisablingTest.java @@ -208,10 +208,10 @@ public void verifyQCFlagToggling() { // note: this uses the run based guide set - // simular to verifyHighlightUpdatesAfterQCFlagChange (but not quite the same... not sure how this other place works) + // similar to verifyHighlightUpdatesAfterQCFlagChange (but not quite the same... not sure how this other place works) _guideSetHelper.goToLeveyJenningsGraphPage(TEST_ASSAY_LUM, CONTROL_NAME); _guideSetHelper.setUpLeveyJenningsGraphParams(RUN_BASED_ANALYTE); - final String plate1_AUC = "64608.73"; + final String plate1_AUC = "64608.734"; final String plate2_AUC = "61889.88"; validateFlaggedForQC(plate1_AUC, plate2_AUC); @@ -244,23 +244,17 @@ public void verifyQCFlagToggling() private void saveGuideSetParameters() { - // Wait for grid to refresh before checking QC flags - doAndWaitForElementToRefresh(() -> click(Ext4Helper.Locators.windowButton(GUIDE_SET_WINDOW_NAME, "Save")), - Locator.tagWithId("div", "trackingDataPanel").append(Locator.byClass("x-grid3-row-table")), shortWait()); + click(Ext4Helper.Locators.windowButton(GUIDE_SET_WINDOW_NAME, "Save")); _guideSetHelper.waitForGuideSetExtMaskToDisappear(); } private void validateFlaggedForQC(String... texts) { - // NOTE: We sometimes get a bad value here which we are investigating. For now the test will ignore such a value. - List badVals = Arrays.asList("8.08", "7.90"); - - Locator redCellLoc = Locator.tagWithId("div", "trackingDataPanel").append(Locator.tagWithClass("div", "x-grid3-cell-inner").withPredicate("contains(@style,'red')")); - List qcFlaggedCells = redCellLoc.findElements(getDriver()); + Locator redCellLoc = Locator.tagWithClass("table", "labkey-data-region").append(Locator.xpath("//span[contains(@style,'red')]")); + List qcFlaggedCells = isElementPresent(redCellLoc) ? redCellLoc.findElements(getDriver()) : Collections.emptyList(); List expectedQcFlaggedValues = new ArrayList<>(Arrays.asList(texts)); List qcFlaggedValues = getTexts(qcFlaggedCells); - qcFlaggedValues.removeAll(badVals); Collections.sort(expectedQcFlaggedValues); Collections.sort(qcFlaggedValues); diff --git a/luminex/test/src/org/labkey/test/tests/luminex/LuminexGuideSetTest.java b/luminex/test/src/org/labkey/test/tests/luminex/LuminexGuideSetTest.java index 66acb7096f..b5cde4c258 100644 --- a/luminex/test/src/org/labkey/test/tests/luminex/LuminexGuideSetTest.java +++ b/luminex/test/src/org/labkey/test/tests/luminex/LuminexGuideSetTest.java @@ -20,6 +20,7 @@ import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; import org.labkey.test.TestFileUtils; +import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Assays; import org.labkey.test.categories.Daily; import org.labkey.test.pages.ReactAssayDesignerPage; @@ -33,6 +34,7 @@ import java.io.File; import java.util.Calendar; +import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; @@ -139,7 +141,7 @@ private void excludableWellsWithTransformTest() excludeReplicateGroupFromRun("Guide Set plate 5", "A6,B6", 2, 1); _guideSetHelper.goToLeveyJenningsGraphPage(TEST_ASSAY_LUM, "Standard1"); _guideSetHelper.setUpLeveyJenningsGraphParams("GS Analyte B"); - assertTextPresent("28040.51"); + assertTextPresent("28040.512"); } private void excludeReplicateGroupFromRun(String run, String wells, int jobCount, int jobInfoCount) @@ -209,62 +211,56 @@ private void guideSetApiTest() @LogMethod private void applyStartAndEndDateFilter() { - String colValuePrefix = "NETWORK"; - _guideSetHelper.setUpLeveyJenningsGraphParams("GS Analyte B"); - // check that all 5 runs are present in the grid by clicking on them - for (int i = 1; i <= 5; i++) - { - assertElementPresent(ExtHelper.locateGridRowCheckbox(colValuePrefix + i)); - } + // check that all 5 runs are present in the grid and plot + DataRegionTable table = _guideSetHelper.getTrackingDataRegion(); + assertEquals("Initial grid row count not as expected", 5, table.getDataRowCount()); + assertEquals("Initial plot data point count not as expected", 5, Locator.findElements(getDriver(), Locator.tagWithClass("a", "point")).size()); + // set start and end date filter - setFormElement(Locator.name("start-date-field"), "2011-03-26"); - setFormElement(Locator.name("end-date-field"), "2011-03-28"); - waitAndClick(Locator.extButtonEnabled("Apply").index(1)); + table.setFilter("Analyte/Data/AcquisitionDate", "Is Greater Than or Equal To", "2011-03-26", "Is Less Than or Equal To", "2011-03-28"); _guideSetHelper.waitForLeveyJenningsTrendPlot(); + // check that only 3 runs are now present - waitForElementToDisappear(ExtHelper.locateGridRowCheckbox(colValuePrefix + "1"), WAIT_FOR_JAVASCRIPT); - for (int i = 2; i <= 4; i++) - { - waitForElement(ExtHelper.locateGridRowCheckbox(colValuePrefix + i)); - } - assertElementNotPresent(ExtHelper.locateGridRowCheckbox(colValuePrefix + "5")); + assertEquals("Initial grid row count not as expected", 3, table.getDataRowCount()); + assertEquals("Initial plot data point count not as expected", 3, Locator.findElements(getDriver(), Locator.tagWithClass("a", "point")).size()); + List rowNetworkValues = table.getColumnDataAsText("Titration/Run/Batch/Network"); + assertEquals("Filtered grid row value not as expected", "NETWORK4", rowNetworkValues.get(0)); + assertEquals("Filtered grid row value not as expected", "NETWORK3", rowNetworkValues.get(1)); + assertEquals("Filtered grid row value not as expected", "NETWORK2", rowNetworkValues.get(2)); + + // Clear the filter and check that all rows reappear + table.clearAllFilters(); + _guideSetHelper.waitForLeveyJenningsTrendPlot(); + assertEquals("Initial grid row count not as expected", 5, table.getDataRowCount()); + assertEquals("Initial plot data point count not as expected", 5, Locator.findElements(getDriver(), Locator.tagWithClass("a", "point")).size()); } @LogMethod private void applyNetworkProtocolFilter() { - String colNetworkPrefix = "NETWORK"; - String colProtocolPrefix = "PROTOCOL"; - _guideSetHelper.setUpLeveyJenningsGraphParams("GS Analyte B"); - // check that all 5 runs are present in the grid by clicking on them - for (int i = 1; i <= 5; i++) - { - assertElementPresent(ExtHelper.locateGridRowCheckbox(colNetworkPrefix + i)); - } - // set network and protocol filter - _extHelper.selectComboBoxItem(Locator.xpath("//input[@id='network-combo-box']/.."), colNetworkPrefix + "3"); - _extHelper.selectComboBoxItem(Locator.xpath("//input[@id='protocol-combo-box']/.."), colProtocolPrefix + "3"); + // check that all 5 runs are present in the grid and plot + DataRegionTable table = _guideSetHelper.getTrackingDataRegion(); + assertEquals("Initial grid row count not as expected", 5, table.getDataRowCount()); + assertEquals("Initial plot data point count not as expected", 5, Locator.findElements(getDriver(), Locator.tagWithClass("a", "point")).size()); - waitAndClick(Locator.extButtonEnabled("Apply").index(1)); + // set network and protocol filter + table.setFilter("Titration/Run/Batch/Network", "Equals", "NETWORK3"); + table.setFilter("Titration/Run/Batch/CustomProtocol", "Equals", "PROTOCOL3"); _guideSetHelper.waitForLeveyJenningsTrendPlot(); - // check that only 1 runs are now present - waitForElementToDisappear(ExtHelper.locateGridRowCheckbox(colNetworkPrefix + "1"), WAIT_FOR_JAVASCRIPT); - waitForElement(ExtHelper.locateGridRowCheckbox(colNetworkPrefix + "3")); - assertElementNotPresent(ExtHelper.locateGridRowCheckbox(colNetworkPrefix + "1")); - assertElementNotPresent(ExtHelper.locateGridRowCheckbox(colNetworkPrefix + "2")); - assertElementNotPresent(ExtHelper.locateGridRowCheckbox(colNetworkPrefix + "4")); - assertElementNotPresent(ExtHelper.locateGridRowCheckbox(colNetworkPrefix + "5")); + // check that only 1 run is now present + assertEquals("Initial grid row count not as expected", 1, table.getDataRowCount()); + assertEquals("Initial plot data point count not as expected", 1, Locator.findElements(getDriver(), Locator.tagWithClass("a", "point")).size()); + assertEquals("Filtered grid row value not as expected", "NETWORK3", table.getColumnDataAsText("Titration/Run/Batch/Network").get(0)); + assertEquals("Filtered grid row value not as expected", "PROTOCOL3", table.getColumnDataAsText("Titration/Run/Batch/CustomProtocol").get(0)); // Clear the filter and check that all rows reappear - waitAndClick(Locator.extButtonEnabled("Clear")); + table.clearAllFilters(); _guideSetHelper.waitForLeveyJenningsTrendPlot(); - for (int i = 1; i <= 5; i++) - { - assertElementPresent(ExtHelper.locateGridRowCheckbox(colNetworkPrefix + i)); - } + assertEquals("Initial grid row count not as expected", 5, table.getDataRowCount()); + assertEquals("Initial plot data point count not as expected", 5, Locator.findElements(getDriver(), Locator.tagWithClass("a", "point")).size()); } private void applyLogYAxisScale() @@ -280,7 +276,7 @@ private boolean verifyRunFileAssociations(int index) // verify that the PDF of curves file was generated along with the xls file and the Rout file DataRegionTable table = new DataRegionTable("Runs", getDriver()); table.setFilter("Name", "Equals", "Guide Set plate " + index); - clickAndWait(Locator.tagWithAttribute("img", "src", "/labkey/experiment/images/graphIcon.gif")); + clickAndWait(Locator.tagWithAttribute("img", "src", WebTestHelper.getContextPath() + "/experiment/images/graphIcon.gif")); clickAndWait(Locator.linkWithText("Text View")); waitForElement(Locator.css(".labkey-protocol-applications")); // bottom section of the "Text View" tab for the run details page waitForElements(Locator.linkWithText("Guide Set plate " + index + ".Standard1_Control_Curves_4PL.pdf"), 3); @@ -339,9 +335,9 @@ private void verifyExcludingRuns(Map guideSetIds, String[] anal @LogMethod private void verifyGuideSetToRun(String network, String comment) { - click(ExtHelper.locateGridRowCheckbox(network)); + DataRegionTable table = _guideSetHelper.getTrackingDataRegion(); + table.checkCheckbox(table.getRowIndex("Titration/Run/Batch/Network", network)); clickButton("Apply Guide Set", 0); - waitForElement(ExtHelper.locateGridRowCheckbox(network)); waitForElement(ExtHelper.locateGridRowCheckbox(comment)); sleep(1000); // deselect the current guide set to test error message @@ -413,15 +409,16 @@ private void verifyLeveyJenningsPlots() // TODO: add more validation of the plot SVG //verify QC flags + DataRegionTable table = _guideSetHelper.getTrackingDataRegion(); + assertTrue(table.getColumnLabels().contains("QC Flags")); //this locator finds an EC50 flag, then makes sure there's red text outlining - Locator.XPathLocator l = Locator.xpath("//td/div[contains(@style,'red')]/../../td/div/a[contains(text(),'EC50-4')]"); + Locator.XPathLocator l = Locator.xpath("//td/span[contains(@style,'red')]/../../td/a[contains(text(),'EC50-4')]"); assertElementPresent(l,2); - assertTextPresent("QC Flags"); // Verify as much of the Curve Comparison window as we can - most of its content is in the image, so it's opaque to the test for (int i = 1; i <= 5; i++) { - click(ExtHelper.locateGridRowCheckbox("NETWORK" + i)); + table.checkCheckbox(table.getRowIndex("Titration/Run/Batch/Network", "NETWORK" + i)); } clickButton("View 4PL Curves", 0); waitForTextToDisappear("loading curves...", WAIT_FOR_JAVASCRIPT); @@ -479,9 +476,9 @@ private void verifyQCFlagUpdatesAfterWellChange() _guideSetHelper.applyGuideSetToRun("NETWORK5", GUIDE_SET_5_COMMENT, false); //assert ec50 and HMFI red text present waitForText(newQcFlags); - assertElementPresent(Locator.xpath("//div[text()='28040.51' and contains(@style,'red')]")); // EC50 - assertElementPresent(Locator.xpath("//div[text()='79121.45' and contains(@style,'red')]")); // AUC - assertElementPresent(Locator.xpath("//div[text()='32145.80' and contains(@style,'red')]")); // High MFI + assertElementPresent(Locator.xpath("//span[text()='28040.512' and contains(@style,'red')]")); // EC50 + assertElementPresent(Locator.xpath("//span[text()='79121.445' and contains(@style,'red')]")); // AUC + assertElementPresent(Locator.xpath("//span[text()='32145.8' and contains(@style,'red')]")); // High MFI //verify new flags present in run list goToTestAssayHome(); drt = new DataRegionTable("Runs", getDriver()); @@ -494,28 +491,33 @@ private void verifyQCFlagUpdatesAfterWellChange() _guideSetHelper.setUpLeveyJenningsGraphParams("GS Analyte B"); _guideSetHelper.applyGuideSetToRun("NETWORK5", GUIDE_SET_5_COMMENT, true); assertTextNotPresent(newQcFlags); + assertElementPresent(Locator.xpath("//td/span[contains(@style,'red')]"),2); //6. Create new Guide Set for GS Analyte B that includes plate 5 (but not plate 5a) - // - the AUC QC Flag for plate 5 is removed + // - the AUC QC Flag for plate 5 is removed for GS Analyte B but still exists for GS Analyte A Locator.XPathLocator aucLink = Locator.xpath("//a[contains(text(),'AUC')]"); waitForElement(aucLink); int aucCount = getElementCount(aucLink); _guideSetHelper.createGuideSet(false); _guideSetHelper.editRunBasedGuideSet(new String[]{"allRunsRow_1"}, "Guide set includes plate 5", true); - assertEquals("Wrong count for AUC flag links", aucCount-1, (getElementCount(aucLink))); + assertEquals("Wrong count for AUC flag links", aucCount, (getElementCount(aucLink))); + assertElementPresent(Locator.xpath("//td/span[contains(@style,'red')]"),1); //7. Switch to GS Analyte A, and edit the current guide set to include plate 3 // - the QC Flag for plate 3 (the run included) and the other plates (4, 5, and 5a) are all removed as all values are within the guide set ranges _guideSetHelper.setUpLeveyJenningsGraphParams("GS Analyte A"); - assertExpectedAnalyte1QCFlagsPresent(); + assertExpectedAnalyte1QCFlagsInitial(); + assertElementPresent(Locator.xpath("//td/span[contains(@style,'red')]"),7); clickButtonContainingText("Edit", 0); _guideSetHelper.editRunBasedGuideSet(new String[]{"allRunsRow_3"}, "edited analyte 1", false); - assertQCFlagsNotPresent(); + assertExpectedAnalyte1QCFlagsUpdated(); + assertElementPresent(Locator.xpath("//td/span[contains(@style,'red')]"),0); //8. Edit the GS Analyte A guide set and remove plate 3 // - the QC Flags for plates 3, 4, 5, and 5a return (HMFI for all 4 and AUC for plates 4, 5, and 5a) removePlate3FromGuideSet(); - assertExpectedAnalyte1QCFlagsPresent(); + assertExpectedAnalyte1QCFlagsInitial(); + assertElementPresent(Locator.xpath("//td/span[contains(@style,'red')]"),7); } @LogMethod @@ -528,18 +530,17 @@ private void removePlate3FromGuideSet() _guideSetHelper.waitForGuideSetExtMaskToDisappear(); } - private void assertExpectedAnalyte1QCFlagsPresent() + private void assertExpectedAnalyte1QCFlagsInitial() { waitForElements(Locator.xpath("//a[contains(text(),'HMFI')]"), 4); - waitForElements(Locator.xpath("//a[contains(text(),'AUC')]"), 3); + waitForElements(Locator.xpath("//a[contains(text(),'AUC')]"), 9); } - private void assertQCFlagsNotPresent() + private void assertExpectedAnalyte1QCFlagsUpdated() { - for (String flag : new String[] {"AUC", "HMFI", "EC50-4", "EC50-5", "PCV"}) - { + for (String flag : new String[] {"HMFI", "EC50-4", "EC50-5"}) assertElementNotPresent(Locator.xpath("//a[contains(text(),'" + flag + "')]")); - } + waitForElements(Locator.xpath("//a[contains(text(),'AUC')]"), 7); } private void importPlateFiveAgain() diff --git a/luminex/test/src/org/labkey/test/tests/luminex/LuminexRTransformTest.java b/luminex/test/src/org/labkey/test/tests/luminex/LuminexRTransformTest.java index 5e46aa05bb..82b5a787ef 100644 --- a/luminex/test/src/org/labkey/test/tests/luminex/LuminexRTransformTest.java +++ b/luminex/test/src/org/labkey/test/tests/luminex/LuminexRTransformTest.java @@ -20,6 +20,7 @@ import org.junit.experimental.categories.Category; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; +import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Assays; import org.labkey.test.categories.Daily; import org.labkey.test.pages.ReactAssayDesignerPage; @@ -105,7 +106,7 @@ private void verifyScriptVersions() private void verifyPDFsGenerated() { DataRegionTable.DataRegion(getDriver()).find(); // Make sure page is loaded - WebElement curvePng = Locator.tagWithAttribute("img", "src", "/labkey/_images/sigmoidal_curve.png").findElement(getDriver()); + WebElement curvePng = Locator.tagWithAttribute("img", "src", WebTestHelper.getContextPath() + "/_images/sigmoidal_curve.png").findElement(getDriver()); shortWait().until(LabKeyExpectedConditions.animationIsDone(curvePng)); File curvePdf = clickAndWaitForDownload(curvePng); assertEquals("Curve PDF has wrong name: " + curvePdf.getName(), "WithAltNegativeBead.Standard1_Control_Curves_4PL.pdf", curvePdf.getName()); diff --git a/luminex/test/src/org/labkey/test/util/luminex/LuminexGuideSetHelper.java b/luminex/test/src/org/labkey/test/util/luminex/LuminexGuideSetHelper.java index b4fec1ab79..8d56eda143 100644 --- a/luminex/test/src/org/labkey/test/util/luminex/LuminexGuideSetHelper.java +++ b/luminex/test/src/org/labkey/test/util/luminex/LuminexGuideSetHelper.java @@ -89,6 +89,13 @@ public void verifyGuideSetsNotApplied(String assayName) table.clearFilter("GuideSet/Created"); } + public DataRegionTable getTrackingDataRegion() + { + DataRegionTable table = new DataRegionTable.DataRegionFinder(_test.getDriver()).find(); + table.setAsync(true); + return table; + } + public void waitForManageGuideSetWindow(boolean creating) { WebElement window = _test.shortWait().until(ExpectedConditions.visibilityOfElementLocated(GS_WINDOW_LOC)); @@ -180,8 +187,6 @@ public void waitForGuideSetExtMaskToDisappear() _test._extHelper.waitForExt3MaskToDisappear(WebDriverWrapper.WAIT_FOR_JAVASCRIPT); _test.waitForElementToDisappear(GS_WINDOW_LOC); waitForLeveyJenningsTrendPlot(); - // Wait for the grid to populate as well. - _test.waitForElements(Locator.xpath("//div[contains(@class, 'x-grid3-row-checker')]")); } public void goToLeveyJenningsGraphPage(String assayName, String titrationName) @@ -207,10 +212,11 @@ public void applyGuideSetToRun(String network, String comment, boolean useCurren @LogMethod public void applyGuideSetToRun(@LoggedParam String[] networks, @LoggedParam String comment, boolean useCurrent) { + DataRegionTable table = getTrackingDataRegion(); for (String network : networks) - _test.click(ExtHelper.locateGridRowCheckbox(network)); + table.checkCheckbox(table.getRowIndex("Network", network)); - WebElement applyGuideSetButton = _test.scrollIntoView(Locator.button("Apply Guide Set")); + WebElement applyGuideSetButton = _test.scrollIntoView(Locator.lkButton("Apply Guide Set")); _test.doAndWaitForPageSignal(applyGuideSetButton::click, "guideSetSelectionChange"); WebElement applyGuideSetWindow = ExtHelper.Locators.window("Apply Guide Set...").waitForElement(_test.getDriver(), WAIT_FOR_JAVASCRIPT); diff --git a/luminex/webapp/luminex/ApplyGuideSetPanel.js b/luminex/webapp/luminex/ApplyGuideSetPanel.js index 4314f3b561..d63155e4df 100644 --- a/luminex/webapp/luminex/ApplyGuideSetPanel.js +++ b/luminex/webapp/luminex/ApplyGuideSetPanel.js @@ -110,8 +110,8 @@ LABKEY.ApplyGuideSetPanel = Ext.extend(Ext.FormPanel, { selectedHeaderCols.push({header:'Acquisition Date', dataIndex:'Analyte/Data/AcquisitionDate', renderer: this.dateRenderer, width:100}); if (this.controlType == 'Titration') { - selectedHeaderCols.push({header:'EC50 4PL', dataIndex:'Four ParameterCurveFit/EC50', width:75, renderer: this.numberRenderer, align: 'right'}); - selectedHeaderCols.push({header:'EC50 5PL', dataIndex:'Five ParameterCurveFit/EC50', width:75, renderer: this.numberRenderer, align: 'right'}); + selectedHeaderCols.push({header:'EC50 4PL', dataIndex:'Four ParameterCurveFit/EC50', width:75, renderer: this.numberRenderer, align: 'right', hidden: !this.has4PLCurveFit}); + selectedHeaderCols.push({header:'EC50 5PL', dataIndex:'Five ParameterCurveFit/EC50', width:75, renderer: this.numberRenderer, align: 'right', hidden: !this.has5PLCurveFit}); selectedHeaderCols.push({header:'AUC', dataIndex:'TrapezoidalCurveFit/AUC', width:75, renderer: this.numberRenderer, align: 'right'}); selectedHeaderCols.push({header:'High MFI', dataIndex:'MaxFI', width:75, renderer: this.numberRenderer, align: 'right'}); } @@ -216,8 +216,8 @@ LABKEY.ApplyGuideSetPanel = Ext.extend(Ext.FormPanel, { ]; if (this.controlType == 'Titration') { - guideSetColumnModelColumns.push({header: 'Avg EC50 4PL', dataIndex: 'AverageEC504PL', renderer: this.numberRenderer, align: 'right', sortable: false}); - guideSetColumnModelColumns.push({header: 'Avg EC50 5PL', dataIndex: 'AverageEC505PL', renderer: this.numberRenderer, align: 'right', sortable: false}); + guideSetColumnModelColumns.push({header: 'Avg EC50 4PL', dataIndex: 'AverageEC504PL', renderer: this.numberRenderer, align: 'right', sortable: false, hidden: !this.has4PLCurveFit}); + guideSetColumnModelColumns.push({header: 'Avg EC50 5PL', dataIndex: 'AverageEC505PL', renderer: this.numberRenderer, align: 'right', sortable: false, hidden: !this.has5PLCurveFit}); guideSetColumnModelColumns.push({header: 'Avg AUC', dataIndex: 'AverageAUC', renderer: this.numberRenderer, align: 'right', sortable: false}); guideSetColumnModelColumns.push({header: 'Avg High MFI', dataIndex: 'AverageMFI', renderer: this.numberRenderer, align: 'right', sortable: false}); } diff --git a/luminex/webapp/luminex/LeveyJenningsGraphParamsPanel.js b/luminex/webapp/luminex/LeveyJenningsGraphParamsPanel.js index b4acc32aa0..c127725516 100644 --- a/luminex/webapp/luminex/LeveyJenningsGraphParamsPanel.js +++ b/luminex/webapp/luminex/LeveyJenningsGraphParamsPanel.js @@ -52,7 +52,6 @@ LABKEY.LeveyJenningsGraphParamsPanel = Ext.extend(Ext.FormPanel, { }, initComponent : function() { - this.paramsToLoad = 3; var items = []; // need to distinguish between null analyte/isotype/conjugate on URL and not requested (i.e. not on URL) @@ -84,7 +83,7 @@ LABKEY.LeveyJenningsGraphParamsPanel = Ext.extend(Ext.FormPanel, { columns: [{header: '', dataIndex:'value', renderer: this.tooltipRenderer}], listeners: { scope: this, - 'rowClick': function(grid, rowIndex) { + 'rowClick': function(grid, record, rowIndex, skipFireEvent) { if (grid.getSelectionModel().hasSelection()) { this.analyte = grid.getSelectionModel().getSelected().get("value"); @@ -96,7 +95,9 @@ LABKEY.LeveyJenningsGraphParamsPanel = Ext.extend(Ext.FormPanel, { this.filterIsotypeCombo(); this.enableApplyGraphButton(); - this.fireEvent('graphParamsChanged'); + if (!skipFireEvent) { + this.fireEvent('graphParamsChanged'); + } } } }); @@ -105,8 +106,8 @@ LABKEY.LeveyJenningsGraphParamsPanel = Ext.extend(Ext.FormPanel, { if (this.analyte != undefined && index > -1) { this.analyteGrid.getSelectionModel().selectRow(index); - this.analyteGrid.fireEvent('rowClick', this.analyteGrid, index); - this.analyteGrid.getView().focusRow(index); // TODO: this doesn't seem to be working + this.analyteGrid.fireEvent('rowClick', this.analyteGrid, records[index], index, true); + this.analyteGrid.getView().focusRow(index); } else { @@ -116,12 +117,6 @@ LABKEY.LeveyJenningsGraphParamsPanel = Ext.extend(Ext.FormPanel, { } this.analyteGrid.enable(); - - this.paramsToLoad--; - if (this.paramsToLoad == 0) - { - this.allParamsLoaded(); - } }, this); items.push(this.analyteGrid); @@ -140,19 +135,22 @@ LABKEY.LeveyJenningsGraphParamsPanel = Ext.extend(Ext.FormPanel, { tpl: '
{display:htmlEncode}
', listeners: { scope: this, - 'select': function(combo, record, index) { + 'select': function(combo, record, index, skipFireEvent) { this.isotype = combo.getValue(); this.filterConjugateCombo(); this.enableApplyGraphButton(); - this.fireEvent('graphParamsChanged'); + if (!skipFireEvent) { + this.fireEvent('graphParamsChanged'); + } } } }); this.isotypeCombobox.getStore().on('load', function(store, records, options) { - if (this.isotype != undefined && store.findExact('value', this.isotype) > -1) + var index = store.findExact('value', this.isotype); + if (this.isotype != undefined && index > -1) { this.isotypeCombobox.setValue(this.isotype); - this.isotypeCombobox.fireEvent('select', this.isotypeCombobox); + this.isotypeCombobox.fireEvent('select', this.isotypeCombobox, records[index], index, true); this.isotypeCombobox.enable(); } else @@ -162,12 +160,6 @@ LABKEY.LeveyJenningsGraphParamsPanel = Ext.extend(Ext.FormPanel, { this.conjugateCombobox.clearValue(); this.conjugateCombobox.disable(); } - - this.paramsToLoad--; - if (this.paramsToLoad == 0) - { - this.allParamsLoaded(); - } }, this); items.push(this.isotypeCombobox); @@ -203,12 +195,6 @@ LABKEY.LeveyJenningsGraphParamsPanel = Ext.extend(Ext.FormPanel, { { this.conjugate = undefined; } - - this.paramsToLoad--; - if (this.paramsToLoad == 0) - { - this.allParamsLoaded(); - } }, this); items.push(this.conjugateCombobox); @@ -326,14 +312,6 @@ LABKEY.LeveyJenningsGraphParamsPanel = Ext.extend(Ext.FormPanel, { return storeData; }, - allParamsLoaded: function() { - if (this.enableApplyGraphButton()) - { - // fire the applyGraphBtnClicked event so other panels can update based on the selected params - this.fireEvent('applyGraphBtnClicked', this.analyte, this.isotype, this.conjugate); - } - }, - tooltipRenderer: function(value, p, record) { var msg = Ext.util.Format.htmlEncode(value); p.attr = 'ext:qtip="' + msg + '"'; diff --git a/luminex/webapp/luminex/LeveyJenningsGuideSetPanel.js b/luminex/webapp/luminex/LeveyJenningsGuideSetPanel.js index b65e0705c4..c44be824cf 100644 --- a/luminex/webapp/luminex/LeveyJenningsGuideSetPanel.js +++ b/luminex/webapp/luminex/LeveyJenningsGuideSetPanel.js @@ -16,7 +16,7 @@ var $h = Ext.util.Format.htmlEncode; * Class to create a small panel for displaying the current guide set info for the selected graph parameters * and to give the user access to the edit guide set and create new guide set buttons * - * @params titration + * @params titration on single point control * @params assayName */ LABKEY.LeveyJenningsGuideSetPanel = Ext.extend(Ext.FormPanel, { @@ -59,7 +59,7 @@ LABKEY.LeveyJenningsGuideSetPanel = Ext.extend(Ext.FormPanel, { this.paramsDisplayField = new Ext.form.DisplayField({ hideLabel: true, value: "", - style: "font-size:110%; font-weight:bold", + cls: "lj-report-title", width: 738, border: true }); diff --git a/luminex/webapp/luminex/LeveyJenningsPlotHelpers.js b/luminex/webapp/luminex/LeveyJenningsPlotHelpers.js index 9094763ed6..3f3eb0e9aa 100644 --- a/luminex/webapp/luminex/LeveyJenningsPlotHelpers.js +++ b/luminex/webapp/luminex/LeveyJenningsPlotHelpers.js @@ -6,29 +6,71 @@ Ext.namespace('LABKEY', 'Luminex.panel'); LABKEY.LeveyJenningsPlotHelper = {}; -// NOTE: we should be able to avoid passing around values. -// NOTE: consider setting this up to be config based... potential defaulting values here... +LABKEY.LeveyJenningsPlotHelper.PlotTypeMap = { + EC504PL: 'EC50 - 4PL', + EC505PL: 'EC50 - 5PL Rumi', + AUC: 'AUC', + HighMFI: 'High MFI', + MFI: 'MFI' // not sure why we cannot get these named right. +}; + +LABKEY.LeveyJenningsPlotHelper.TitrationColumnsMap = { + "GuideSetId": "GuideSet", + "AcquisitionDate": "Analyte/Data/AcquisitionDate", + "LotNumber": "Analyte/Properties/LotNumber", + "RunRowId": "Titration/Run/RowId", + "AssayId": "Titration/Run/Name", + "Network": "Titration/Run/Batch/Network", + "NotebookNo": "Analyte/Data/Run/NotebookNo", + "AssayType": "Analyte/Data/Run/AssayType", + "ExpPerformer": "Analyte/Data/Run/ExpPerformer", + "EC504PL": "Four ParameterCurveFit/EC50", + "EC505PL": "Five ParameterCurveFit/EC50", + "AUC": "TrapezoidalCurveFit/AUC", + "HighMFI": "MaxFI", +}; + +LABKEY.LeveyJenningsPlotHelper.SinglePointControlColumnsMap = { + "GuideSetId": "GuideSet", + "AcquisitionDate": "Analyte/Data/AcquisitionDate", + "LotNumber": "Analyte/Properties/LotNumber", + "RunRowId": "SinglePointControl/Run/RowId", + "AssayId": "SinglePointControl/Run/Name", + "Network": "SinglePointControl/Run/Batch/Network", + "NotebookNo": "SinglePointControl/Run/NotebookNo", + "AssayType": "SinglePointControl/Run/AssayType", + "ExpPerformer": "SinglePointControl/Run/ExpPerformer", + "MFI": "AverageFiBkgd", +}; + LABKEY.LeveyJenningsPlotHelper.getTrackingDataStore = function(config) { - var controlTypeColName = config.controlType == "SinglePoint" ? "SinglePointControl" : config.controlType; - var whereClause = " WHERE Analyte.Name='" + config.analyte.replace(/'/g, "''") + "'" - + " AND " + controlTypeColName + ".Name='" + config.controlName.replace(/'/g, "''") + "'" - + (config.isotype ? " AND " + controlTypeColName + ".Run.Isotype='" + config.isotype.replace(/'/g, "''") + "'" - : " AND " + controlTypeColName + ".Run.Isotype IS NULL") - + (config.conjugate ? " AND " + controlTypeColName + ".Run.Conjugate='" + config.conjugate.replace(/'/g, "''") + "'" - : " AND " + controlTypeColName + ".Run.Conjugate IS NULL"); - - if (config.controlType == "Titration") { - whereClause += " AND Titration.IncludeInQcReport=true"; - } + var isSinglePointControl = config.controlType === 'SinglePoint'; + var controlTypeColName = isSinglePointControl ? 'SinglePointControl' : config.controlType; + var columnsMap = isSinglePointControl ? LABKEY.LeveyJenningsPlotHelper.SinglePointControlColumnsMap : LABKEY.LeveyJenningsPlotHelper.TitrationColumnsMap; - // add on any filtering (from LeveyJenningsTrackingDataPanel.js) - if (config.whereClause) { - whereClause += config.whereClause; + var store = new LABKEY.ext.Store({ + autoLoad: true, + schemaName: 'assay.Luminex.' + LABKEY.QueryKey.encodePart(config.assayName), + queryName: isSinglePointControl ? 'AnalyteSinglePointControl' : 'AnalyteTitration', + columns: Object.values(columnsMap).join(','), + filterArray: config.filters, + sort: config.sort ? config.sort : '-Analyte/Data/AcquisitionDate, -' + controlTypeColName + '/Run/Created', + maxRows: config.maxRows ? config.maxRows : -1, + containerFilter: LABKEY.Query.containerFilter.allFolders, + scope: config.scope + }); + + if (config.loadListener) { + store.addListener('load', config.loadListener, config.scope); } - // this version of the guide set range values SQL will prevent the need for multiple LEFT OUTER JOINs to the GuideSetCurveFit table - var guideSetRangeValuesSQL = "SELECT gs.RowId, gs.Created, gs.ValueBased,\n" + + return store; +}; + +LABKEY.LeveyJenningsPlotHelper.getGuideSetRangesStore = function(config) +{ + var sql = "SELECT gs.RowId, gs.Created, gs.ValueBased,\n" + " CASE WHEN gs.ValueBased=true THEN gs.EC504PLAverage ELSE cf.EC504PLAverage END AS GuideSetEC504PLAverage,\n" + " CASE WHEN gs.ValueBased=true THEN gs.EC504PLStdDev ELSE cf.EC504PLStdDev END AS GuideSetEC504PLStdDev,\n" + " CASE WHEN gs.ValueBased=true THEN gs.EC505PLAverage ELSE cf.EC505PLAverage END AS GuideSetEC505PLAverage,\n" + @@ -53,103 +95,52 @@ LABKEY.LeveyJenningsPlotHelper.getTrackingDataStore = function(config) " GROUP BY GuideSetId\n" + ") cf ON gs.RowId = cf.GuideSetId"; - // generate sql for the data store (columns depend on the control type) - // issue 22267 : add IFDEFINED to "optional" assay design fields - var sql = "SELECT Analyte" - + ", " + controlTypeColName + ".Run.Created" // NOTE: necessary for union case - + ", Analyte.Data.AcquisitionDate" - + ", IFDEFINED(Analyte.Properties.LotNumber)" - + ", " + controlTypeColName - + ", IFDEFINED(" + controlTypeColName + ".Run.Isotype)" - + ", IFDEFINED(" + controlTypeColName + ".Run.Conjugate)" - + ", " + controlTypeColName + ".Run.RowId AS RunRowId" - + ", " + controlTypeColName + ".Run.Name AS RunName" - + ", " + controlTypeColName + ".Run.Folder.Name AS FolderName" - + ", " + controlTypeColName + ".Run.Folder.EntityId" - + ", IFDEFINED(" + controlTypeColName + ".Run.Batch.Network)" - + ", IFDEFINED(" + controlTypeColName + ".Run.Batch.CustomProtocol)" - + ", IFDEFINED(" + controlTypeColName + ".Run.NotebookNo)" - + ", IFDEFINED(" + controlTypeColName + ".Run.AssayType)" - + ", IFDEFINED(" + controlTypeColName + ".Run.ExpPerformer)" - + ", GuideSet AS GuideSetId" - + ", gs.Created AS GuideSetCreated" - + ", IncludeInGuideSetCalculation" - + ", gs.ValueBased AS GuideSetValueBased"; - if (config.controlType == "Titration") - { - sql += ", \"Four ParameterCurveFit\".EC50 AS EC504PL, \"Four ParameterCurveFit\".EC50QCFlagsEnabled AS EC504PLQCFlagsEnabled" - + ", \"Five ParameterCurveFit\".EC50 AS EC505PL, \"Five ParameterCurveFit\".EC50QCFlagsEnabled AS EC505PLQCFlagsEnabled" - + ", TrapezoidalCurveFit.AUC, TrapezoidalCurveFit.AUCQCFlagsEnabled" - + ", MaxFI AS HighMFI, MaxFIQCFlagsEnabled AS HighMFIQCFlagsEnabled" - //columns needed for guide set ranges (value based or run based) - + ", gs.GuideSetEC504PLAverage" - + ", gs.GuideSetEC504PLStdDev" - + ", gs.GuideSetEC505PLAverage" - + ", gs.GuideSetEC505PLStdDev" - + ", gs.GuideSetAUCAverage" - + ", gs.GuideSetAUCStdDev" - + ", gs.GuideSetHighMFIAverage" - + ", gs.GuideSetHighMFIStdDev" - + " FROM AnalyteTitration " - + " LEFT JOIN (" + guideSetRangeValuesSQL + ") AS gs ON GuideSet = gs.RowId" - + whereClause; - } - else if (config.controlType == "SinglePoint") - { - sql += ", AverageFiBkgd AS MFI, AverageFiBkgdQCFlagsEnabled AS MFIQCFlagsEnabled" - //columns needed for guide set ranges (value based or run based) - + ", gs.GuideSetMFIAverage" - + ", gs.GuideSetMFIStdDev" - + " FROM AnalyteSinglePointControl " - + " LEFT JOIN (" + guideSetRangeValuesSQL + ") AS gs ON GuideSet = gs.RowId" - + whereClause; - } - var store = new LABKEY.ext.Store({ - autoLoad: false, + autoLoad: true, schemaName: 'assay.Luminex.' + LABKEY.QueryKey.encodePart(config.assayName), sql: sql, - sort: config.sort ? config.sort : '-Analyte/Data/AcquisitionDate, -' + controlTypeColName + '/Run/Created', - maxRows: config.maxRows ? config.maxRows : -1, + maxRows: -1, containerFilter: LABKEY.Query.containerFilter.allFolders, scope: config.scope }); - // not assuming scope for now... if (config.loadListener) store.addListener('load', config.loadListener, config.scope); return store; }; -// consider reducing scope of this object...? -LABKEY.LeveyJenningsPlotHelper.PlotTypeMap = { - EC504PL: 'EC50 - 4PL', - EC505PL: 'EC50 - 5PL Rumi', - AUC: 'AUC', - HighMFI: 'High MFI', - MFI: 'MFI' // not sure why we cannot get these named right. -}; - LABKEY.LeveyJenningsPlotHelper.renderPlot = function(config) { var plotData = []; - var records = config.store.getRange(); + var records = config.dataStore.getRange(); var otherHoverProps = ['Network', 'AssayType', 'ExpPerformer', 'AcquisitionDate']; + var columnsMap = config.controlType === 'SinglePoint' ? LABKEY.LeveyJenningsPlotHelper.SinglePointControlColumnsMap : LABKEY.LeveyJenningsPlotHelper.TitrationColumnsMap; var _pushData = function(record) { var data = { - xLabel: record.get('NotebookNo'), - pointColor: record.get('LotNumber'), - value: record.get(config.plotType), - gsMean: record.get('GuideSet' + config.plotType + 'Average'), - gsStdDev: record.get('GuideSet' + config.plotType + 'StdDev') + xLabel: record.get(columnsMap['NotebookNo']), + pointColor: record.get(columnsMap['LotNumber']), + value: record.get(columnsMap[config.plotType]), }; + // fall back to using the AssayId if the NotebookNo prop doesn't exist + if (!record.get(columnsMap['NotebookNo'])) { + data.xLabel = record.get(columnsMap['AssayId']); + } + + // merge in the guide set range mean and stdDev values + var gsRecordIndex = config.guideSetRangeStore ? config.guideSetRangeStore.findExact('RowId', record.get(columnsMap['GuideSetId'])) : -1; + if (gsRecordIndex > -1) { + var gsRecord = config.guideSetRangeStore.getAt(gsRecordIndex); + data['gsMean'] = gsRecord.get('GuideSet' + config.plotType + 'Average'); + data['gsStdDev'] = gsRecord.get('GuideSet' + config.plotType + 'StdDev'); + } + // add some other values to the data object for the hover display Ext.each(otherHoverProps, function(prop) { - var val = record.get(prop); + var val = record.get(columnsMap[prop]); // convert values that are date objects to a display string format if (val != null && LABKEY.Utils.isDate(val)) { @@ -170,7 +161,7 @@ LABKEY.LeveyJenningsPlotHelper.renderPlot = function(config) for (var i = 0; i < records.length; i++) { - if (records[i].get('RunRowId') == config.runId) + if (records[i].get(columnsMap['RunRowId']) == config.runId) { index = i; break; @@ -233,7 +224,7 @@ LABKEY.LeveyJenningsPlotHelper.renderPlot = function(config) colorRange: ['black', 'red', 'green', 'blue', 'purple', 'orange', 'grey', 'brown'], hoverTextFn: function(row){ var display = 'Notebook: ' + row.xLabel - + '\nLot Number: ' + row.pointColor + + '\nLot Number: ' + (row.pointColor ? row.pointColor : '') + '\n' + config.plotType + ': ' + row.value; // add any of the non-null extra display values to the hover @@ -269,8 +260,7 @@ LABKEY.LeveyJenningsPlotHelper.renderPlot = function(config) // plotType: EC504PL, EC505PL, AUC, HighMFI LABKEY.LeveyJenningsPlotHelper.getLeveyJenningsPlotWindow = function(protocolId, analyteId, typeId, plotType, controlType) { - if (controlType == null) - controlType = 'Titration'; + var controlTypeColName = controlType === 'SinglePoint' ? 'SinglePointControl' : controlType; LABKEY.Assay.getById({ id: protocolId, @@ -282,29 +272,41 @@ LABKEY.LeveyJenningsPlotHelper.getLeveyJenningsPlotWindow = function(protocolId, var _getConfig = function(assayName) { - // note make sure to mix assayName into config... LABKEY.Query.selectRows({ + containerFilter: LABKEY.Query.containerFilter.allFolders, schemaName: 'assay.Luminex.' + LABKEY.QueryKey.encodePart(assayName), - queryName: 'Analyte'+controlType, - columns: [controlType+'/Name', 'Analyte/Name', controlType+'/Run/Isotype', controlType+'/Run/Conjugate', controlType+'/Run', 'Analyte/Data/AcquisitionDate'], + queryName: 'Analyte'+controlTypeColName, + columns: [controlTypeColName+'/Name', 'Analyte/Name', controlTypeColName+'/Run/Isotype', controlTypeColName+'/Run/Conjugate', controlTypeColName+'/Run', 'Analyte/Data/AcquisitionDate'], filterArray: [ LABKEY.Filter.create('Analyte', analyteId), - LABKEY.Filter.create(controlType, typeId) + LABKEY.Filter.create(controlTypeColName, typeId) ], success: function(data) { var row = data.rows[0]; + + const filters = [ + LABKEY.Filter.create('Analyte/Name', row['Analyte/Name']), + LABKEY.Filter.create(controlTypeColName + '/Name', row[controlTypeColName+'/Name']), + LABKEY.Filter.create(controlTypeColName + '/Run/Isotype', row[controlTypeColName+'/Run/Isotype']), + LABKEY.Filter.create(controlTypeColName + '/Run/Conjugate', row[controlTypeColName+'/Run/Conjugate']), + ]; + if (controlType === 'Titration') { + filters.push(LABKEY.Filter.create('Titration/IncludeInQcReport', true)); + } + var config = { assayName: assayName, - controlName: row[controlType+'/Name'], - controlType: controlType == "SinglePointControl" ? "SinglePoint" : "Titration", + controlName: row[controlTypeColName+'/Name'], + controlType: controlType, analyte: row['Analyte/Name'], - isotype: row[controlType+'/Run/Isotype'], - conjugate: row[controlType+'/Run/Conjugate'], + isotype: row[controlTypeColName+'/Run/Isotype'], + conjugate: row[controlTypeColName+'/Run/Conjugate'], yAxisScale: 'linear', - scope: this, // shouldn't matter but might blow up without it. plotType: plotType, - runId: row[controlType+'/Run'], - sort: 'Analyte/Data/AcquisitionDate, ' + controlType + '/Run/Created', + runId: row[controlTypeColName+'/Run'], + filters: filters, + sort: 'Analyte/Data/AcquisitionDate, ' + controlTypeColName + '/Run/Created', + scope: this, // shouldn't matter but might blow up without it. }; _createWindow(config); @@ -333,17 +335,31 @@ LABKEY.LeveyJenningsPlotHelper.getLeveyJenningsPlotWindow = function(protocolId, }, listeners : { afterrender: function() { - window.getEl().mask("Loading...", "x-mask-loading"); + var dataStoreLoaded = false; + var guideSetStoreLoaded = false; config.loadListener = function(store) { - config.store = store; - LABKEY.LeveyJenningsPlotHelper.renderPlot(config); - window.getEl().unmask(); + dataStoreLoaded = true; + config.dataStore = store; + if (guideSetStoreLoaded) { + LABKEY.LeveyJenningsPlotHelper.renderPlot(config); + window.getEl().unmask(); + } }; - - var store = LABKEY.LeveyJenningsPlotHelper.getTrackingDataStore(config); - store.load() + LABKEY.LeveyJenningsPlotHelper.getTrackingDataStore(config); + + LABKEY.LeveyJenningsPlotHelper.getGuideSetRangesStore({ + assayName: config.assayName, + loadListener: function(store) { + guideSetStoreLoaded = true; + config.guideSetRangeStore = store; + if (dataStoreLoaded) { + LABKEY.LeveyJenningsPlotHelper.renderPlot(config); + window.getEl().unmask(); + } + }, + }); } } }] diff --git a/luminex/webapp/luminex/LeveyJenningsTrackingDataPanel.js b/luminex/webapp/luminex/LeveyJenningsTrackingDataPanel.js index f34d93b840..66f4156a6e 100644 --- a/luminex/webapp/luminex/LeveyJenningsTrackingDataPanel.js +++ b/luminex/webapp/luminex/LeveyJenningsTrackingDataPanel.js @@ -6,20 +6,15 @@ Ext.namespace('LABKEY'); -/** - * User: cnathe - * Date: Sept 21, 2011 - */ - Ext.QuickTips.init(); /** - * Class to create a labkey editorGridPanel to display the tracking data for the selected graph parameters + * Class to create a QueryWebPart to display the tracking data for the selected graph parameters * * @params controlName * @params assayName */ -LABKEY.LeveyJenningsTrackingDataPanel = Ext.extend(Ext.grid.GridPanel, { +LABKEY.LeveyJenningsTrackingDataPanel = Ext.extend(Ext.Component, { constructor: function (config) { // check that the config properties needed are present @@ -28,26 +23,10 @@ LABKEY.LeveyJenningsTrackingDataPanel = Ext.extend(Ext.grid.GridPanel, { if (!config.assayName || config.assayName == "null") throw "You must specify a assayName!"; - // apply some Ext panel specific properties to the config - Ext.apply(config, { - width: 1375, - autoHeight: true, - title: $h(config.controlName) + ' Tracking Data', - loadMask: {msg: "Loading data..."}, - columnLines: true, - stripeRows: true, - viewConfig: { - forceFit: true, - scrollOffset: 0 - }, - disabled: true, - analyte: null, - isotype: null, - conjugate: null, - userCanUpdate: LABKEY.user.canUpdate - }); + config.html = '
' + + '
'; - this.addEvents('appliedGuideSetUpdated', 'trackingDataLoaded'); + this.addEvents('appliedGuideSetUpdated', 'plotDataLoading', 'plotDataLoaded'); LABKEY.LeveyJenningsTrackingDataPanel.superclass.constructor.call(this, config); }, @@ -55,477 +34,414 @@ LABKEY.LeveyJenningsTrackingDataPanel = Ext.extend(Ext.grid.GridPanel, { initComponent: function () { this.store = new Ext.data.ArrayStore(); - this.selModel = this.getTrackingDataSelModel(); - this.colModel = this.getTrackingDataColModel(); - - // initialize an export button for the toolbar - this.exportMenuButton = new Ext.Button({ - text: 'Export', - menu: [ - { - text: 'Excel', - handler: function () - { - this.exportData('excel'); - }, - scope: this - }, - { - text: 'TSV', - handler: function () - { - this.exportData('tsv'); - }, - scope: this - } - ] - }); - - // initialize the apply guide set button to the toolbar - this.applyGuideSetButton = new Ext.Button({ - disabled: true, - text: 'Apply Guide Set', - handler: this.applyGuideSetClicked, - scope: this - }); - - // initialize the view curves button to the toolbar - this.viewCurvesButton = new Ext.Button({ - disabled: true, - text: 'View 4PL Curves', - tooltip: 'Click to view overlapping curves for the selected runs.', - handler: this.viewCurvesClicked, - scope: this - }); - - // if the controlType is Titration, show the viewCurves 'View 4PL Curves' button, for Single Point Controls do not - if (this.controlType == "Titration") - { - // if the user has permissions to update in this container, show them the Apply Guide Set button - this.tbar = this.userCanUpdate ? [this.exportMenuButton, this.applyGuideSetButton, this.viewCurvesButton] : [this.exportMenuButton, this.viewCurvesButton]; - } - else - { - // if the user has permissions to update in this container, show them the Apply Guide Set button - this.tbar = this.userCanUpdate ? [this.exportMenuButton, this.applyGuideSetButton ] : [this.exportMenuButton]; - } - - this.fbar = [ - {xtype: 'label', text: 'Bold values in the "Guide Set Date" column indicate runs that are members of a guide set.'} - ]; LABKEY.LeveyJenningsTrackingDataPanel.superclass.initComponent.call(this); }, - getNeworkProtocolFilter: function(name, value) { - var fieldName = (this.controlType == "Titration" ? "Titration" : "SinglePointControl") + '.Run.Batch.' + name; - if (value != null) { - return " AND " + fieldName + " = '" + value.replace(/'/g, "''") + "'"; - } - else { - return " AND " + fieldName + " IS NULL"; - } + hasReportFilter: function() { + var userFilters = this.qwp.getUserFilterArray(); + return userFilters.length > 0; }, - storeLoaded: function(store, records, options) { - this.fireEvent('trackingDataLoaded', store); - this.loadQCFlags(store, records, options); - }, - - getTrackingDataSelModel: function () - { - return new Ext.grid.CheckboxSelectionModel({ - listeners: { - scope: this, - 'selectionchange': function (selectionModel) - { - if (selectionModel.hasSelection()) - { - this.applyGuideSetButton.enable(); - this.viewCurvesButton.enable(); - } - else - { - this.applyGuideSetButton.disable(); - this.viewCurvesButton.disable(); - } - } - } + configurePlotDataStore: function(baseFilters) { + this.storeLoading(); + this.store = LABKEY.LeveyJenningsPlotHelper.getTrackingDataStore({ + assayName: this.assayName, + controlName: this.controlName, + controlType: this.controlType, + analyte: this.analyte, + isotype: this.isotype, + conjugate: this.conjugate, + filters: baseFilters.concat(this.qwp.getUserFilterArray()), + maxRows: !this.hasReportFilter() ? this.defaultRowSize : undefined, + scope: this, + loadListener: this.storeLoaded, }); - }, - - getTrackingDataColModel: function () - { - return new Ext.grid.ColumnModel({ - defaults: {sortable: true}, - columns: this.getTrackingDataColumns(), - scope: this + this.store.on('exception', function(store, type, action, options, response){ + var errorJson = Ext.util.JSON.decode(response.responseText); + if (errorJson.exception) { + Ext.get(document.querySelector('.ljTrendPlot').id).update("" + errorJson.exception + ""); + } }); }, - getTrackingDataColumns: function () - { - var cols = [ - this.selModel, - {header: 'Analyte', dataIndex: 'Analyte', hidden: true, renderer: this.encodingRenderer}, - {header: 'Isotype', dataIndex: 'Isotype', hidden: true, renderer: this.encodingRenderer}, - {header: 'Conjugate', dataIndex: 'Conjugate', hidden: true, renderer: this.encodingRenderer}, - {header: 'QC Flags', dataIndex: 'QCFlags', width: 75}, - {header: 'Assay Id', dataIndex: 'RunName', renderer: this.assayIdHrefRenderer, width: 200}, - {header: 'Network', dataIndex: 'Network', width: 75, renderer: this.encodingRenderer, hidden: !this.networkExists}, - {header: 'Protocol', dataIndex: 'CustomProtocol', width: 75, renderer: this.encodingRenderer, hidden: !this.protocolExists}, - {header: 'Folder', dataIndex: 'FolderName', width: 75, renderer: this.encodingRenderer}, - {header: 'Notebook No.', dataIndex: 'NotebookNo', width: 100, renderer: this.encodingRenderer}, - {header: 'Assay Type', dataIndex: 'AssayType', width: 100, renderer: this.encodingRenderer}, - {header: 'Experiment Performer', dataIndex: 'ExpPerformer', width: 100, renderer: this.encodingRenderer}, - {header: 'Acquisition Date', dataIndex: 'AcquisitionDate', renderer: this.dateRenderer, width: 100}, - {header: 'Analyte Lot No.', dataIndex: 'LotNumber', width: 100, renderer: this.encodingRenderer}, - {header: 'Guide Set Start Date', dataIndex: 'GuideSetCreated', renderer: this.formatGuideSetMembers, scope: this, width: 100}, - {header: 'GS Member', dataIndex: 'IncludeInGuideSetCalculation', hidden: true} - ]; + storeLoading: function() { + this.fireEvent('plotDataLoading', this.store, this.hasGuideSetUpdate); - if (this.controlType == "Titration") - { - cols.splice(2, 0, {header: 'Titration', dataIndex: 'Titration', hidden: true, renderer: this.encodingRenderer}); - cols.push({header: 'EC50 4PL', dataIndex: 'EC504PL', width: 75, renderer: this.outOfRangeRenderer("EC504PLQCFlagsEnabled"), scope: this, align: 'right', hidden: !this.has4PLCurveFit}); - cols.push({header: 'EC50 4PL QC Flags Enabled', dataIndex: 'EC504PLQCFlagsEnabled', hidden: true}); - cols.push({header: 'EC50 5PL', dataIndex: 'EC505PL', width: 75, renderer: this.outOfRangeRenderer("EC505PLQCFlagsEnabled"), scope: this, align: 'right', hidden: !this.has5PLCurveFit}); - cols.push({header: 'EC50 5PL QC Flags Enabled', dataIndex: 'EC505PLQCFlagsEnabled', hidden: true}); - cols.push({header: 'AUC', dataIndex: 'AUC', width: 75, renderer: this.outOfRangeRenderer("AUCQCFlagsEnabled"), scope: this, align: 'right'}); - cols.push({header: 'AUC QC Flags Enabled', dataIndex: 'AUCQCFlagsEnabled', hidden: true}); - cols.push({header: 'High MFI', dataIndex: 'HighMFI', width: 75, renderer: this.outOfRangeRenderer("HighMFIQCFlagsEnabled"), scope: this, align: 'right'}); - cols.push({header: 'High QC Flags Enabled', dataIndex: 'HighMFIQCFlagsEnabled', hidden: true}); - } - else if (this.controlType == "SinglePoint") - { - cols.splice(2, 0, {header: 'SinglePointControl', dataIndex: 'SinglePointControl', hidden: true, renderer: this.encodingRenderer}); - cols.push({header: 'MFI', dataIndex: 'MFI', width: 75, renderer: this.outOfRangeRenderer("MFIQCFlagsEnabled"), scope: this, align: 'right'}); - cols.push({header: 'MFI QC Flags Enabled', dataIndex: 'MFIQCFlagsEnabled', hidden: true}); - } + // reset hasGuideSetUpdate so that other grid filter changes won't trigger this as well + this.hasGuideSetUpdate = false; + }, - return cols; + storeLoaded: function() { + this.fireEvent('plotDataLoaded', this.store, this.hasReportFilter()); }, // function called by the JSP when the graph params are selected and the "Apply" button is clicked - graphParamsSelected: function (analyte, isotype, conjugate, startDate, endDate, network, networkAny, protocol, protocolAny) + graphParamsSelected: function (analyte, isotype, conjugate, hasGuideSetUpdate) { + var shouldClearSelections = true; + this.hasGuideSetUpdate = hasGuideSetUpdate; + // store the params locally this.analyte = analyte; this.isotype = isotype; this.conjugate = conjugate; // set the grid title based on the selected graph params - this.setTitle($h(this.controlName) + ' Tracking Data for ' + $h(this.analyte) - + ' - ' + $h(this.isotype == '' ? '[None]' : this.isotype) - + ' ' + $h(this.conjugate == '' ? '[None]' : this.conjugate)); - - var whereClause = ""; - var hasReportFilter = false; - if (startDate) - { - hasReportFilter = true; - whereClause += " AND CAST(Analyte.Data.AcquisitionDate AS DATE) >= '" + startDate + "'"; - } - if (endDate) - { - hasReportFilter = true; - whereClause += " AND CAST(Analyte.Data.AcquisitionDate AS DATE) <= '" + endDate + "'"; + document.getElementById('trackingDataPanel_title').innerHTML = + $h(this.controlName) + ' Tracking Data for ' + $h(this.analyte) + + ' - ' + $h(this.isotype === '' ? '[None]' : this.isotype) + + ' ' + $h(this.conjugate === '' ? '[None]' : this.conjugate); + + var controlTypeColName = this.controlType === 'SinglePoint' ? 'SinglePointControl' : this.controlType; + const filters = [ + LABKEY.Filter.create('Analyte/Name', this.analyte), + LABKEY.Filter.create(controlTypeColName + '/Name', this.controlName), + LABKEY.Filter.create(controlTypeColName + '/Run/Isotype', this.isotype), + LABKEY.Filter.create(controlTypeColName + '/Run/Conjugate', this.conjugate), + ]; + if (this.controlType === 'Titration') { + filters.push(LABKEY.Filter.create('Titration/IncludeInQcReport', true)); } - if (Ext.isDefined(network) && !networkAny) - { - hasReportFilter = true; - whereClause += this.getNeworkProtocolFilter("Network", network); + + var buttonBarItems = [ + LABKEY.QueryWebPart.standardButtons.views, + LABKEY.QueryWebPart.standardButtons.exportRows, + LABKEY.QueryWebPart.standardButtons.print, + ]; + if (LABKEY.user.canUpdate) { + buttonBarItems.push({ + text: 'Apply Guide Set', + requiresSelection: true, + handler: this.applyGuideSetClicked, + }); } - if (Ext.isDefined(protocol) && !protocolAny) - { - hasReportFilter = true; - whereClause += this.getNeworkProtocolFilter("CustomProtocol", protocol); + if (this.controlType === 'Titration') { + buttonBarItems.push({ + text: 'View 4PL Curves', + requiresSelection: true, + handler: this.viewCurvesClicked, + }); } - // create a new store now that the graph params are selected and bind it to the grid - var storeConfig = { - assayName: this.assayName, - controlName: this.controlName, - controlType: this.controlType, - analyte: this.analyte, - isotype: this.isotype, - conjugate: this.conjugate, - maxRows: !hasReportFilter ? this.defaultRowSize : undefined, + this.qwp = new LABKEY.QueryWebPart({ + renderTo: 'trackingDataPanel_QWP', + schemaName: "assay.Luminex." + LABKEY.QueryKey.encodePart(_protocolName), + queryName: this.controlType === 'SinglePoint' ? 'AnalyteSinglePointControl' : 'AnalyteTitration', + containerFilter: LABKEY.Query.containerFilter.allFolders, + maxRows: this.defaultRowSize, + showUpdateColumn : false, + showImportDataButton : false, + showInsertNewButton : false, + showDeleteButton : false, + showReports : false, + frame: 'none', + filters: filters, + sort: '-Analyte/Data/AcquisitionDate, -' + controlTypeColName + '/Run/Created', scope: this, - loadListener: this.storeLoaded, - }; - - if (whereClause != "") - { - storeConfig['whereClause'] = whereClause; - } - - this.store = LABKEY.LeveyJenningsPlotHelper.getTrackingDataStore(storeConfig); + buttonBar: { + includeStandardButtons: false, + items: buttonBarItems, + }, + listeners: { + scope: this, + success: function(dataRegion) { + // clear selections on each graph param change so we don't inadvertently get selections + // row selections from a different analyte/isotype/conjugate + if (shouldClearSelections) { + dataRegion.clearSelected(); + shouldClearSelections = false; + } - this.store.on('exception', function(store, type, action, options, response){ - var errorJson = Ext.util.JSON.decode(response.responseText); - if (errorJson.exception) { - Ext.get(document.querySelector('.ljTrendPlot').id).update("" + errorJson.exception + ""); + this.configurePlotDataStore(filters); + } } }); - var newColModel = this.getTrackingDataColModel(); - this.reconfigure(this.store, newColModel); - this.store.load(); - // enable the trending data grid this.enable(); }, - applyGuideSetClicked: function () - { - // get the selected record list from the grid - var selection = this.selModel.getSelections(); - var selectedRecords = []; - // Copy so that it's available in the scope for the callback function - var controlType = this.controlType; - Ext.each(selection, function (record) - { - var newItem = {Analyte: record.get("Analyte")}; - if (controlType == 'Titration') - { - newItem.ControlId = record.get("Titration"); - } - else - { - newItem.ControlId = record.get("SinglePointControl"); + parseGridSelectionKeys: function(selected, getAnalyteIds) { + var isSinglePoint = this.controlType === 'SinglePoint'; + var selectedRecords = [], analyteIds = []; + for (var i = 0; i < selected.length; i++) { + var keys = selected[i].split(','); + if (keys.length === 3) { + // AnalyteTitration selection keys = [Analyte RowId, Titration RowId, Titration RowId] + // AnalyteSinglePointControl selection keys = [SinglePointControl RowId, Analyte RowId, SinglePointControl RowId] + var analyteId = isSinglePoint ? keys[1] : keys[0]; + var controlId = isSinglePoint ? keys[0] : keys[1]; + selectedRecords.push({ Analyte: analyteId, ControlId: controlId }); + analyteIds.push(analyteId); } - selectedRecords.push(newItem); - }); + } + return getAnalyteIds ? analyteIds : selectedRecords; + }, - // create a pop-up window to display the apply guide set UI - var win = new Ext.Window({ - layout: 'fit', - width: 1115, - height: 500, - closeAction: 'close', - modal: true, - padding: 15, - cls: 'extContainer leveljenningsreport', - bodyStyle: 'background-color: white;', - title: 'Apply Guide Set...', - items: [new LABKEY.ApplyGuideSetPanel({ - assayName: this.assayName, - controlName: this.controlName, - controlType: this.controlType, - analyte: this.analyte, - isotype: this.isotype, - conjugate: this.conjugate, - selectedRecords: selectedRecords, - networkExists: this.networkExists, - protocolExists: this.protocolExists, - listeners: { - scope: this, - 'closeApplyGuideSetPanel': function (hasUpdated) - { - if (hasUpdated) - this.fireEvent('appliedGuideSetUpdated'); - win.close(); - } + applyGuideSetClicked: function(dataRegion) { + var scope = dataRegion.scope; + + // get the selected record list from the grid + dataRegion.getSelected({ + success: function(response) { + var selectedRecords = scope.parseGridSelectionKeys(response.selected, false); + if (selectedRecords.length === 0) { + LABKEY.Utils.alert('Error', 'Please select rows in the tracking data grid before clicking the "Apply Guide Set" button.'); + return; } - })] - }); - // for testing, narrow window puts left aligned buttons off of the page - win.on('show', function(cmp) { - var posArr = cmp.getPosition(); - if (posArr[0] < 0) - cmp.setPosition(0, posArr[1]); - }); + // create a pop-up window to display the apply guide set UI + var win = new Ext.Window({ + layout: 'fit', + width: 1115, + height: 500, + closeAction: 'close', + modal: true, + padding: 15, + cls: 'extContainer leveljenningsreport', + bodyStyle: 'background-color: white;', + title: 'Apply Guide Set...', + items: [new LABKEY.ApplyGuideSetPanel({ + selectedRecords: selectedRecords, + assayName: scope.assayName, + controlName: scope.controlName, + controlType: scope.controlType, + analyte: scope.analyte, + isotype: scope.isotype, + conjugate: scope.conjugate, + networkExists: scope.networkExists, + protocolExists: scope.protocolExists, + has4PLCurveFit: scope.has4PLCurveFit, + has5PLCurveFit: scope.has5PLCurveFit, + listeners: { + 'closeApplyGuideSetPanel': function (hasUpdated) + { + if (hasUpdated) + scope.fireEvent('appliedGuideSetUpdated'); + win.close(); + } + } + })] + }); - win.show(this); - }, + // for testing, narrow window puts left aligned buttons off of the page + win.on('show', function(cmp) { + var posArr = cmp.getPosition(); + if (posArr[0] < 0) + cmp.setPosition(0, posArr[1]); + }); - viewCurvesClicked: function () - { - // create a pop-up window to display the plot - var plotDiv = new Ext.Container({ - height: 600, - width: 900, - autoEl: {tag: 'div'} - }); - var pdfDiv = new Ext.Container({ - hidden: true, - autoEl: {tag: 'div'} + win.show(scope); + }, + failure: function() { + LABKEY.Utils.alert('Error', 'Unable to get the tracking data region row selections.'); + } }); + }, - var yAxisScaleDefault = 'Linear'; - var yAxisScaleStore = new Ext.data.ArrayStore({ - fields: ['value'], - data: [['Linear'], ['Log']] - }); + viewCurvesClicked: function(dataRegion) { + var scope = dataRegion.scope; - var yAxisColDefault = 'FIBackground', yAxisColDisplayDefault = 'FI-Bkgd'; - var yAxisColStore = new Ext.data.ArrayStore({ - fields: ['name', 'value'], - data: [['FI', 'FI'], ['FI-Bkgd', 'FIBackground'], ['FI-Bkgd-Neg', 'FIBackgroundNegative']] - }); + // get the selected record list from the grid + dataRegion.getSelected({ + success: function(response) { + var analyteIds = scope.parseGridSelectionKeys(response.selected, true); + if (analyteIds.length === 0) { + LABKEY.Utils.alert('Error', 'Please select rows in the tracking data grid before clicking the "View 4PL Curves" button.'); + return; + } - var legendColDefault = 'Name'; - var legendColStore = new Ext.data.ArrayStore({ - fields: ['name', 'value'], - data: [['Assay Type', 'AssayType'], ['Experiment Performer', 'ExpPerformer'], ['Assay Id', 'Name'], ['Notebook No.', 'NotebookNo']] - }); + LABKEY.Query.selectRows({ + schemaName: "assay.Luminex." + LABKEY.QueryKey.encodePart(_protocolName), + queryName: this.controlType === 'SinglePoint' ? 'AnalyteSinglePointControl' : 'AnalyteTitration', + columns: 'Analyte/Data/Run/RowId', + filterArray: [LABKEY.Filter.create('Analyte', analyteIds, LABKEY.Filter.Types.IN)], + success: function(data) { + var runIds = []; + for (var i = 0; i < data.rows.length; i++) { + runIds.push(data.rows[i]['Analyte/Data/Run/RowId']); + } - var win = new Ext.Window({ - layout: 'fit', - width: 900, - minWidth: 600, - height: 660, - minHeight: 500, - closeAction: 'hide', - modal: true, - cls: 'extContainer', - title: 'Curve Comparison', - items: [plotDiv, pdfDiv], - // stash the default values for the plot options on the win component - yAxisScale: yAxisScaleDefault, - yAxisCol: yAxisColDefault, - yAxisDisplay: yAxisColDisplayDefault, - legendCol: legendColDefault, - buttonAlign: 'left', - buttons: [{ - xtype: 'label', - text: 'Y-Axis:' - },{ - xtype: 'combo', - width: 80, - id: 'curvecomparison-yaxis-combo', - store: yAxisColStore , - displayField: 'name', - valueField: 'value', - mode: 'local', - editable: false, - forceSelection: true, - triggerAction: 'all', - value: yAxisColDefault, - listeners: { - scope: this, - select: function(cmp, record) { - win.yAxisCol = record.data.value; - win.yAxisDisplay = record.data.name; - this.updateCurvesPlot(win, plotDiv.getId(), false); - } - } - },{ - xtype: 'label', - text: 'Scale:' - },{ - xtype: 'combo', - width: 75, - id: 'curvecomparison-scale-combo', - store: yAxisScaleStore , - displayField: 'value', - valueField: 'value', - mode: 'local', - editable: false, - forceSelection: true, - triggerAction: 'all', - value: yAxisScaleDefault, - listeners: { - scope: this, - select: function(cmp, record) { - win.yAxisScale = record.data.value; - this.updateCurvesPlot(win, plotDiv.getId(), false); - } - } - },{ - xtype: 'label', - text: 'Legend:' - },{ - xtype: 'combo', - width: 140, - id: 'curvecomparison-legend-combo', - store: legendColStore , - displayField: 'name', - valueField: 'value', - mode: 'local', - editable: false, - forceSelection: true, - triggerAction: 'all', - value: legendColDefault, - listeners: { - scope: this, - select: function(cmp, record) { - win.legendCol = record.data.value; - this.updateCurvesPlot(win, plotDiv.getId(), false); + // create a pop-up window to display the plot + var plotDiv = new Ext.Container({ + height: 600, + width: 900, + autoEl: {tag: 'div'} + }); + var pdfDiv = new Ext.Container({ + hidden: true, + autoEl: {tag: 'div'} + }); + + var yAxisScaleDefault = 'Linear'; + var yAxisScaleStore = new Ext.data.ArrayStore({ + fields: ['value'], + data: [['Linear'], ['Log']] + }); + + var yAxisColDefault = 'FIBackground', yAxisColDisplayDefault = 'FI-Bkgd'; + var yAxisColStore = new Ext.data.ArrayStore({ + fields: ['name', 'value'], + data: [['FI', 'FI'], ['FI-Bkgd', 'FIBackground'], ['FI-Bkgd-Neg', 'FIBackgroundNegative']] + }); + + var legendColDefault = 'Name'; + var legendColStore = new Ext.data.ArrayStore({ + fields: ['name', 'value'], + data: [['Assay Type', 'AssayType'], ['Experiment Performer', 'ExpPerformer'], ['Assay Id', 'Name'], ['Notebook No.', 'NotebookNo']] + }); + + var win = new Ext.Window({ + layout: 'fit', + width: 900, + minWidth: 600, + height: 660, + minHeight: 500, + closeAction: 'hide', + modal: true, + cls: 'extContainer', + title: 'Curve Comparison', + items: [plotDiv, pdfDiv], + // stash the default values for the plot options on the win component + runIds: runIds, + yAxisScale: yAxisScaleDefault, + yAxisCol: yAxisColDefault, + yAxisDisplay: yAxisColDisplayDefault, + legendCol: legendColDefault, + buttonAlign: 'left', + buttons: [{ + xtype: 'label', + text: 'Y-Axis:' + },{ + xtype: 'combo', + width: 80, + id: 'curvecomparison-yaxis-combo', + store: yAxisColStore , + displayField: 'name', + valueField: 'value', + mode: 'local', + editable: false, + forceSelection: true, + triggerAction: 'all', + value: yAxisColDefault, + listeners: { + scope: scope, + select: function(cmp, record) { + win.yAxisCol = record.data.value; + win.yAxisDisplay = record.data.name; + scope.updateCurvesPlot(win, plotDiv.getId(), false); + } + } + },{ + xtype: 'label', + text: 'Scale:' + },{ + xtype: 'combo', + width: 75, + id: 'curvecomparison-scale-combo', + store: yAxisScaleStore , + displayField: 'value', + valueField: 'value', + mode: 'local', + editable: false, + forceSelection: true, + triggerAction: 'all', + value: yAxisScaleDefault, + listeners: { + scope: scope, + select: function(cmp, record) { + win.yAxisScale = record.data.value; + scope.updateCurvesPlot(win, plotDiv.getId(), false); + } + } + },{ + xtype: 'label', + text: 'Legend:' + },{ + xtype: 'combo', + width: 140, + id: 'curvecomparison-legend-combo', + store: legendColStore , + displayField: 'name', + valueField: 'value', + mode: 'local', + editable: false, + forceSelection: true, + triggerAction: 'all', + value: legendColDefault, + listeners: { + scope: scope, + select: function(cmp, record) { + win.legendCol = record.data.value; + scope.updateCurvesPlot(win, plotDiv.getId(), false); + } + } + }, + '->', + { + xtype: 'button', + text: 'Export to PDF', + handler: function (btn) { + scope.updateCurvesPlot(win, pdfDiv.getId(), true); + }, + scope: scope + }, { + xtype: 'button', + text: 'Close', + handler: function () { + win.hide(); + } + }], + listeners: { + scope: scope, + 'resize': function (w, width, height) { + // update the curve plot to the new size of the window + scope.updateCurvesPlot(win, plotDiv.getId(), false); + } + } + }); + + // for testing, narrow window puts left aligned buttons off of the page + win.on('show', function(cmp) { + var posArr = cmp.getPosition(); + if (posArr[0] < 0) + cmp.setPosition(0, posArr[1]); + }); + + win.show(scope); + }, + failure: function() { + LABKEY.Utils.alert('Error', 'Unable to get the tracking data run RowIds from the selections.'); } - } - }, - '->', - { - xtype: 'button', - text: 'Export to PDF', - handler: function (btn) - { - this.updateCurvesPlot(win, pdfDiv.getId(), true); - }, - scope: this + }); }, - { - xtype: 'button', - text: 'Close', - handler: function () - { - win.hide(); - } - }], - listeners: { - scope: this, - 'resize': function (w, width, height) - { - // update the curve plot to the new size of the window - this.updateCurvesPlot(win, plotDiv.getId(), false); - } + failure: function() { + LABKEY.Utils.alert('Error', 'Unable to get the tracking data region row selections.'); } }); - - // for testing, narrow window puts left aligned buttons off of the page - win.on('show', function(cmp) { - var posArr = cmp.getPosition(); - if (posArr[0] < 0) - cmp.setPosition(0, posArr[1]); - }); - - win.show(this); - - this.updateCurvesPlot(win, plotDiv.getId(), false); }, updateCurvesPlot: function (win, divId, outputPdf) { win.getEl().mask("loading curves...", "x-mask-loading"); - // get the selected record list from the grid - var selection = this.selModel.getSelections(); - var runIds = []; - Ext.each(selection, function (record) - { - runIds.push(record.get("RunRowId")); - }); - // build the config object of the properties that will be needed by the R report var config = {reportId: 'module:luminex/CurveComparisonPlot.r', showSection: 'Curve Comparison Plot'}; - config['RunIds'] = runIds.join(";"); + config['RunIds'] = win.runIds.join(";"); config['Protocol'] = this.assayName; config['Titration'] = this.controlName; config['Analyte'] = this.analyte; config['YAxisScale'] = !outputPdf ? win.yAxisScale : 'Linear'; - config['YAxisCol'] = win.yAxisCol, - config['YAxisDisplay'] = win.yAxisDisplay, - config['LegendCol'] = win.legendCol, + config['YAxisCol'] = win.yAxisCol; + config['YAxisDisplay'] = win.yAxisDisplay; + config['LegendCol'] = win.legendCol; config['MainTitle'] = $h(this.controlName) + ' 4PL for ' + $h(this.analyte) + ' - ' + $h(this.isotype === '' ? '[None]' : this.isotype) + ' ' + $h(this.conjugate === '' ? '[None]' : this.conjugate); config['PlotHeight'] = win.getHeight(); config['PlotWidth'] = win.getWidth(); - if (outputPdf) - config['PdfOut'] = true; + if (outputPdf) config['PdfOut'] = true; // call and display the Report webpart new LABKEY.WebPart({ @@ -533,15 +449,12 @@ LABKEY.LeveyJenningsTrackingDataPanel = Ext.extend(Ext.grid.GridPanel, { renderTo: divId, frame: 'none', partConfig: config, - success: function () - { + success: function () { this.getEl().unmask(); - if (outputPdf) - { + if (outputPdf) { // ugly way of getting the href for the pdf file and open it - if (Ext.getDom(divId)) - { + if (Ext.getDom(divId)) { var html = Ext.getDom(divId).innerHTML; html = html.replace(/&/g, "&"); var pdfHref = html.substring(html.indexOf('href="') + 6, html.indexOf('&attachment=true')); @@ -550,304 +463,13 @@ LABKEY.LeveyJenningsTrackingDataPanel = Ext.extend(Ext.grid.GridPanel, { } window.location = pdfHref + "&attachment=true"; } - } }, - failure: function (response) - { + failure: function (response) { Ext.get(plotDiv.getId()).update("Error: " + response.statusText); this.getEl().unmask(); }, scope: win }).render(); }, - - exportData: function (type) - { - // build up the JSON to pass to the export util - var exportJson = { - fileName: this.title + ".xls", - sheets: [ - { - name: 'data', - // add a header section to the export with the graph parameter information - data: [ - [this.controlType == 'Titration' ? 'Titration' : 'SinglePointControl', this.controlName], - ['Analyte:', this.analyte], - ['Isotype:', this.isotype], - ['Conjugate:', this.conjugate], - ['Export Date:', this.dateRenderer(new Date())], - [] - ] - } - ] - }; - - // get all of the columns that are currently being shown in the grid (except for the checkbox column) - var columns = this.getColumnModel().getColumnsBy(function (c) - { - return !c.hidden && c.dataIndex != ""; - }); - - // add the column header row to the export JSON object - var rowIndex = exportJson.sheets[0].data.length; - exportJson.sheets[0].data.push([]); - Ext.each(columns, function (col) - { - exportJson.sheets[0].data[rowIndex].push(col.header); - }); - - // loop through the grid store to put the data into the export JSON object - Ext.each(this.getStore().getRange(), function (row) - { - var rowIndex = exportJson.sheets[0].data.length; - exportJson.sheets[0].data[rowIndex] = []; - - // loop through the column list to get the data for each column - var colIndex = 0; - Ext.each(columns, function (col) - { - // some of the columns may not be defined in the assay design, so set to null - var value = null; - if (null != row.get(col.dataIndex)) - { - value = row.get(col.dataIndex); - } - - // render dates with the proper renderer - if (value instanceof Date) - { - value = this.dateRenderer(value); - } - // render numbers with the proper rounding and format - if (typeof(value) == 'number') - { - value = this.numberRenderer(value); - } - // render out of range values with an asterisk - var enabledStates = row.get(col.dataIndex + "QCFlagsEnabled"); - if (enabledStates != null && (enabledStates.indexOf('t') > -1 || enabledStates.indexOf('1') > -1)) - { - value = "*" + value; - } - - // render the flags in an excel friendly format - if (col.dataIndex == "QCFlags") - { - value = this.flagsExcelRenderer(value); - } - - // Issue 19019: specify that this value should be displayed as a string and not converted to a date - if (col.dataIndex == "RunName") - { - value = {value: value, forceString: true}; - } - - exportJson.sheets[0].data[rowIndex][colIndex] = value; - colIndex++; - }, this); - }, this); - - if (type == 'excel') - { - LABKEY.Utils.convertToExcel(exportJson); - } - else - { - LABKEY.Utils.convertToTable({ - fileNamePrefix: this.title, - delim: 'TAB', - rows: exportJson.sheets[0].data - }); - } - }, - - outOfRangeRenderer: function (enabledDataIndex) - { - return function (val, metaData, record) - { - if (null == val) - { - return null; - } - - // if the record has an enabled QC flag, highlight it in red - var enabledStates = record.get(enabledDataIndex); - if (enabledStates != null && (enabledStates.indexOf('t') > -1 || enabledStates.indexOf('1') > -1)) - { - metaData.attr = "style='color:red'"; - } - - // if this is a very small number, display more decimal places - var precision = this.getPrecision(val); - return Ext.util.Format.number(Ext.util.Format.round(val, precision), (precision == 6 ? '0.000000' : '0.00')); - } - }, - - getPrecision: function (val) - { - return (null != val && val > 0 && val < 1) ? 6 : 2; - }, - - formatGuideSetMembers: function (val, metaData, record) - { - if (record.get("IncludeInGuideSetCalculation")) - { - metaData.attr = "style='font-weight:bold'"; - } - return this.dateRenderer(val); - }, - - loadQCFlags: function (store, records, options) - { - // query the server for the QC Flags that match the selected Titration and Analyte and update the grid store accordingly - this.getEl().mask("loading QC Flags...", "x-mask-loading"); - var prefix = this.controlType == 'Titration' ? 'Titration' : 'SinglePointControl'; - LABKEY.Query.executeSql({ - schemaName: "assay.Luminex." + LABKEY.QueryKey.encodePart(this.assayName), - sql: 'SELECT DISTINCT x.Run, x.FlagType, x.Enabled, FROM Analyte' + prefix + 'QCFlags AS x ' - + 'WHERE x.Analyte.Name=\'' + this.analyte.replace(/'/g, "''") + '\' AND x.' + prefix + '.Name=\'' + this.controlName.replace(/'/g, "''") + '\' ' - + (this.isotype == '' ? ' AND x.' + prefix + '.Run.Isotype IS NULL ' : ' AND x.' + prefix + '.Run.Isotype=\'' + this.isotype.replace(/'/g, "''") + '\' ') - + (this.conjugate == '' ? ' AND x.' + prefix + '.Run.Conjugate IS NULL ' : ' AND x.' + prefix + '.Run.Conjugate=\'' + this.conjugate.replace(/'/g, "''") + '\' ') - + 'ORDER BY x.Run, x.FlagType, x.Enabled LIMIT 10000 ', - sort: "Run,FlagType,Enabled", - containerFilter: LABKEY.Query.containerFilter.allFolders, - success: function (data) - { - // put together the flag display for each runId - var runFlagList = {}; - for (var i = 0; i < data.rows.length; i++) - { - var row = data.rows[i]; - if (runFlagList[row.Run] == undefined) - { - runFlagList[row.Run] = {id: row.Run, count: 0, value: ""}; - } - - // add a comma separator - if (runFlagList[row.Run].count > 0) - { - runFlagList[row.Run].value += ", "; - } - - // add strike-thru for disabled flags - if (row.Enabled) - { - runFlagList[row.Run].value += row.FlagType; - } - else - { - runFlagList[row.Run].value += '' + row.FlagType + ''; - } - - runFlagList[row.Run].count++; - } - - // update the store records with the QC Flag values - store.each(function (record) - { - var runFlag = runFlagList[record.get("RunRowId")]; - if (runFlag) - { - record.set("QCFlags", "" + runFlag.value + ""); - } - }, this); - - // add cellclick event to the grid to trigger the QCFlagToggleWindow - this.on('cellclick', this.showQCFlagToggleWindow, this); - - if (this.getEl().isMasked()) - { - this.getEl().unmask(); - } - }, - failure: function (info, response, options) - { - if (this.getEl().isMasked()) - { - this.getEl().unmask(); - } - - LABKEY.Utils.displayAjaxErrorResponse(response, options); - }, - scope: this - }) - }, - - showQCFlagToggleWindow: function (grid, rowIndex, colIndex, evnt) - { - var record = grid.getStore().getAt(rowIndex); - var fieldName = grid.getColumnModel().getDataIndex(colIndex); - var value = record.get(fieldName); - var prefix = this.controlType == 'Titration' ? 'Titration' : 'SinglePointControl'; - - if (fieldName == "QCFlags" && value != null) - { - var win = new LABKEY.QCFlagToggleWindow({ - schemaName: "assay.Luminex." + LABKEY.QueryKey.encodePart(this.assayName), - queryName: "Analyte" + prefix + "QCFlags", - runId: record.get("RunRowId"), - analyte: this.analyte, - controlName: this.controlName, - controlType: this.controlType, - editable: true, - listeners: { - scope: this, - 'saveSuccess': function () - { - grid.getStore().reload(); - win.close(); - } - } - }); - win.show(); - } - }, - - dateRenderer: function (val) - { - return val ? new Date(val).format("Y-m-d") : null; - }, - - numberRenderer: function (val) - { - // if this is a very small number, display more decimal places - if (null == val) - { - return null; - } - else - { - if (val > 0 && val < 1) - { - return Ext.util.Format.number(Ext.util.Format.round(val, 6), '0.000000'); - } - else - { - return Ext.util.Format.number(Ext.util.Format.round(val, 2), '0.00'); - } - } - }, - - assayIdHrefRenderer: function (val, p, record) - { - var msg = Ext.util.Format.htmlEncode(val); - var url = LABKEY.ActionURL.buildURL('assay', 'assayDetailRedirect', LABKEY.container.path, {runId: record.get('RunRowId')}); - return "" + msg + ""; - }, - - encodingRenderer: function (value, p, record) - { - return $h(value); - }, - - flagsExcelRenderer: function (value) - { - if (value != null) - { - value = value.replace(//gi, "").replace(/<\/a>/gi, ""); - value = value.replace(//gi, "-").replace(/<\/span>/gi, "-"); - } - return value; - } }); diff --git a/luminex/webapp/luminex/LeveyJenningsTrendPlotPanel.js b/luminex/webapp/luminex/LeveyJenningsTrendPlotPanel.js index 8c7c81a2d2..70b519b9cd 100644 --- a/luminex/webapp/luminex/LeveyJenningsTrendPlotPanel.js +++ b/luminex/webapp/luminex/LeveyJenningsTrendPlotPanel.js @@ -14,7 +14,7 @@ Ext.namespace('LABKEY'); /** * Class to create a tab panel for displaying the R plot for the trending of EC50, AUC, and High MFI values for the selected graph parameters. * - * @params titration + * @params titration or single point control * @params assayName */ LABKEY.LeveyJenningsTrendPlotPanel = Ext.extend(Ext.FormPanel, { @@ -29,7 +29,6 @@ LABKEY.LeveyJenningsTrendPlotPanel = Ext.extend(Ext.FormPanel, { Ext.apply(config, { items: [], header: false, - bodyStyle: 'background-color:#EEEEEE', labelAlign: 'left', width: 865, border: false, @@ -38,26 +37,24 @@ LABKEY.LeveyJenningsTrendPlotPanel = Ext.extend(Ext.FormPanel, { yAxisScale: 'linear' }); - this.addEvents('reportFilterApplied', 'togglePdfBtn'); + this.addEvents('togglePdfBtn'); LABKEY.LeveyJenningsTrendPlotPanel.superclass.constructor.call(this, config); }, initComponent : function() { - var labelStyle = 'font-size: 13px; padding-top: 4px;'; - this.ANY_FIELD = '[ANY]'; // constant value used for turning filtering off - - this.startDate = null; - this.endDate = null; - this.network = null; - this.networkAny = true; // false - turns on the filter in R and in Data Panel - this.protocol = null; - this.protocolAny = true; // false - turns on the filter in R and in Data Panel + this.guideSetRangeStore = LABKEY.LeveyJenningsPlotHelper.getGuideSetRangesStore({ + assayName: this.assayName, + scope: this, + loadListener: function() { + this.guideSetRangeStoreLoadComplete = true; + this.updateTrendPlot(); + }, + }); // initialize the y-axis scale combo for the top toolbar this.scaleLabel = new Ext.form.Label({ text: 'Y-Axis Scale:', - style: labelStyle }); this.scaleCombo = new Ext.form.ComboBox({ id: 'scale-combo-box', @@ -82,190 +79,14 @@ LABKEY.LeveyJenningsTrendPlotPanel = Ext.extend(Ext.FormPanel, { } }); - // initialize the date range selection fields for the top toolbar - this.startDateLabel = new Ext.form.Label({ - text: 'Start Date:', - style: labelStyle - }); - this.startDateField = new Ext.form.DateField({ - id: 'start-date-field', - format: 'Y-m-d', // TODO use LABKEY.extDefaultDateFormat? - listeners: { - scope: this, - 'valid': function (df) { - if (df.getValue() != '') - this.applyFilterButton.enable(); - }, - 'invalid': function (df, msg) { - this.applyFilterButton.disable(); - } - } - }); - this.endDateLabel = new Ext.form.Label({ - text: 'End Date:', - style: labelStyle - }); - this.endDateField = new Ext.form.DateField({ - id: 'end-date-field', - format: 'Y-m-d', // TODO use LABKEY.extDefaultDateFormat? - listeners: { - scope: this, - 'valid': function (df) { - if (df.getValue() != '') - this.applyFilterButton.enable(); - }, - 'invalid': function (df, msg) { - this.applyFilterButton.disable(); - } - } - }); - - // Only create the network store and combobox if the Network column exists - if (this.networkExists) { - // Add Network field for filtering - this.networkLabel = new Ext.form.Label({ - text: 'Network:', - style: labelStyle - }); - this.networkCombobox = new Ext.form.ComboBox({ - id: 'network-combo-box', - width: 75, - listWidth: 180, - store: new Ext.data.ArrayStore({fields: ['value', 'display']}), - editable: false, - triggerAction: 'all', - mode: 'local', - valueField: 'value', - displayField: 'display', - tpl: '
{display:htmlEncode}
', - listeners: { - scope: this, - 'select': function(combo, record, index) { - if (combo.getValue() == this.ANY_FIELD) { - this.networkAny = true; - this.network = null; - } else { - this.networkAny = false; - this.network = combo.getValue(); - } - this.applyFilterButton.enable(); - } - } - }); - - this.networkCombobox.getStore().on('load', function(store, records, options) { - if (this.network != undefined && store.findExact('value', this.network) > -1) - { - this.networkCombobox.setValue(this.network); - this.networkCombobox.fireEvent('select', this.networkCombobox); - this.networkCombobox.enable(); - } - else - { - this.network = undefined; - } - }, this); - } - - // Only create the protocol if the CustomProtocol column exists - if (this.protocolExists) { - // Add Protocol field for filtering - this.protocolLabel = new Ext.form.Label({ - text: 'Protocol:', - style: labelStyle - }); - this.protocolCombobox = new Ext.form.ComboBox({ - id: 'protocol-combo-box', - width: 75, - listWidth: 180, - store: new Ext.data.ArrayStore({fields: ['value', 'display']}), - editable: false, - triggerAction: 'all', - mode: 'local', - valueField: 'value', - displayField: 'display', - tpl: '
{display:htmlEncode}
', - listeners: { - scope: this, - 'select': function(combo, record, index) { - this.protocol = combo.getValue(); - this.applyFilterButton.enable(); - - if (combo.getValue() == this.ANY_FIELD) { - this.protocolAny = true; - this.protocol = null; - } else { - this.protocolAny = false; - this.protocol = combo.getValue(); - } - this.applyFilterButton.enable(); - } - } - }); - - this.protocolCombobox.getStore().on('load', function(store, records, options) { - if (this.protocol != undefined && store.findExact('value', this.protocol) > -1) - { - this.protocolCombobox.setValue(this.protocol); - this.protocolCombobox.fireEvent('select', this.protocolCombobox); - this.protocolCombobox.enable(); - } - else - { - this.protocol = undefined; - } - }, this); - } - - // initialize the refesh graph button - this.applyFilterButton = new Ext.Button({ - disabled: true, - text: 'Apply', - handler: this.applyGraphFilter, - scope: this - }); - - // initialize the clear graph button - this.clearFilterButton = new Ext.Button({ - disabled: true, - text: 'Clear', - handler: function() { - this.clearGraphFilter(false); - }, - scope: this - }); - - var tbspacer = {xtype: 'tbspacer', width: 5}; - - var items = [ - this.scaleLabel, tbspacer, - this.scaleCombo, tbspacer, - '->', - this.startDateLabel, tbspacer, - this.startDateField, tbspacer, - this.endDateLabel, tbspacer, - this.endDateField, tbspacer - ]; - if (this.networkExists) { - items.push(this.networkLabel); - items.push(tbspacer); - items.push(this.networkCombobox); - items.push(tbspacer); - - } - if (this.protocolExists) { - items.push(this.protocolLabel); - items.push(tbspacer); - items.push(this.protocolCombobox); - items.push(tbspacer); - } - items.push(this.applyFilterButton); - items.push(tbspacer); - items.push(this.clearFilterButton); - this.tbar = new Ext.Toolbar({ - style: 'border: solid 1px #d0d0d0; padding: 5px 10px;', - items: items + style: 'background-color: #ffffff; padding: 5px 10px; border-color: #d0d0d0; border-width: 1px 1px 0 1px;', + items: [ + '->', + this.scaleLabel, + {xtype: 'tbspacer', width: 5}, + this.scaleCombo, + ] }); // initialize the tab panel that will show the trend plots @@ -321,6 +142,7 @@ LABKEY.LeveyJenningsTrendPlotPanel = Ext.extend(Ext.FormPanel, { }); this.trendTabPanel = new Ext.TabPanel({ + style: 'border-style: solid; border-width: 0 1px; border-color: #d0d0d0;', autoScroll: true, activeTab: 0, defaults: { @@ -332,7 +154,13 @@ LABKEY.LeveyJenningsTrendPlotPanel = Ext.extend(Ext.FormPanel, { }); this.items.push(this.trendTabPanel); + this.fbar = [ + {xtype: 'label', text: 'The default plot is showing the most recent ' + this.defaultRowSize + ' data points.'}, + ]; + LABKEY.LeveyJenningsTrendPlotPanel.superclass.initComponent.call(this); + + this.fbar.hide(); }, // function called by the JSP when the graph params are selected and the "Apply" button is clicked @@ -342,52 +170,58 @@ LABKEY.LeveyJenningsTrendPlotPanel = Ext.extend(Ext.FormPanel, { this.isotype = isotype; this.conjugate = conjugate; - // remove any previously entered values from the start date, end date, network, etc. fileds - this.clearGraphFilter(true); - - // show the trending tab panel and date range selection toolbar this.enable(); this.setTrendPlotLoading(); }, - trackingDataLoaded: function(store) + plotDataLoading: function(store, hasGuideSetUpdates) { - if (this.networkExists) - { - this.networkCombobox.getStore().loadData(this.getArrayStoreData(store.collect("Network", true), this.networkCombobox.getValue())); + if (hasGuideSetUpdates) { + this.guideSetRangeStoreLoadComplete = false; + this.guideSetRangeStore.load(); } - if (this.protocolExists) - { - this.protocolCombobox.getStore().loadData(this.getArrayStoreData(store.collect("CustomProtocol", true), this.protocolCombobox.getValue())); + this.setTrendPlotLoading(); + }, + + plotDataLoaded: function(store, hasReportFilter) + { + // stash the store's records so that they can be re-used on tab change + if (store) { + this.trendDataStore = store; } - this.updateTrendPlot(store); + this.plotDataLoadComplete = true; + this.fbar.setVisible(!hasReportFilter && this.trendDataStore.getTotalCount() >= this.defaultRowSize); + this.updateTrendPlot(); }, setTrendPlotLoading: function() { + this.plotDataLoadComplete = false; var plotType = this.trendTabPanel.getActiveTab().itemId; var trendDiv = plotType + 'TrendPlotDiv'; Ext.get(trendDiv).update('Loading plot...'); this.togglePDFExportBtn(false); }, - updateTrendPlot: function(store) + updateTrendPlot: function() { - this.togglePDFExportBtn(false); - - // stash the store's records so that they can be re-used on tab change - if (store) { - this.trendDataStore = store; + // if we are still loading either the guide set range data or the plot data, return without rendering + if (!this.guideSetRangeStoreLoadComplete || !this.plotDataLoadComplete) { + return; } + this.togglePDFExportBtn(false); + var plotType = this.trendTabPanel.getActiveTab().itemId; var trendDiv = plotType + 'TrendPlotDiv'; var plotConfig = { - store: this.trendDataStore, - plotType: plotType, renderDiv: trendDiv, + dataStore: this.trendDataStore, + guideSetRangeStore: this.guideSetRangeStore, + controlType: this.controlType, + plotType: plotType, assayName: this.assayName, controlName: this.controlName, analyte: this.analyte, @@ -422,96 +256,15 @@ LABKEY.LeveyJenningsTrendPlotPanel = Ext.extend(Ext.FormPanel, { } }, - applyGraphFilter: function() { - // make sure that at least one filter field is not null - if (this.startDateField.getRawValue() == '' && this.endDateField.getRawValue() == '' && this.networkCombobox.getRawValue() == '' && this.protocolCombobox.getRawValue() == '') - { - Ext.Msg.show({ - title:'ERROR', - msg: 'Please enter a value for filtering.', - buttons: Ext.Msg.OK, - icon: Ext.MessageBox.ERROR - }); - } - // verify that the start date is not after the end date - else if (this.startDateField.getValue() > this.endDateField.getValue() && this.endDateField.getValue() != '') - { - Ext.Msg.show({ - title:'ERROR', - msg: 'Please enter an end date that does not occur before the start date.', - buttons: Ext.Msg.OK, - icon: Ext.MessageBox.ERROR - }); - } - else - { - // get date values without the time zone info - this.startDate = this.startDateField.getRawValue(); - this.endDate = this.endDateField.getRawValue(); - this.clearFilterButton.enable(); - - this.fireEvent('reportFilterApplied', this.startDate, this.endDate, this.network, this.networkAny, this.protocol, this.protocolAny); - } - }, - - clearGraphFilter: function(clearOnly) { - this.startDate = null; - this.startDateField.reset(); - this.endDate = null; - this.endDateField.reset(); - this.applyFilterButton.disable(); - this.clearFilterButton.disable(); - this.network = null; - if (this.networkCombobox) { - this.networkCombobox.reset(); - this.networkCombobox.setValue(this.ANY_FIELD); - this.networkAny = true; - this.network = null; - } - this.protocol = null; - if (this.protocolCombobox) { - this.protocolCombobox.reset(); - this.protocolCombobox.setValue(this.ANY_FIELD); - this.protocolAny = true; - this.protocol = null; - } - - if (clearOnly) - return; - - this.fireEvent('reportFilterApplied', this.startDate, this.endDate, this.network, this.networkAny, this.protocol, this.protocolAny); - }, - - getStartDate: function() { - return this.startDate ? this.startDate : null; - }, - - getEndDate: function() { - return this.endDate ? this.endDate : null; - }, - - getArrayStoreData: function(arr, prevVal) { - // if there is a previously selected combo value, make sure it exists in the array data - if (prevVal && prevVal != '[ANY]' && arr.indexOf(prevVal) == -1){ - arr.push(prevVal); - } - - var storeData = [ [this.ANY_FIELD, this.ANY_FIELD] ]; - Ext.each(arr.sort(), function(value){ - storeData.push([value, value == null ? '[Blank]' : value]); - }); - return storeData; - }, - getTitrationSinglePointControlItems: function() { - if (this.controlType == "Titration") { + if (this.controlType === "Titration") { var panels = []; if (this.has4PLCurveFit) panels.push(this.ec504plPanel); if (this.has5PLCurveFit) panels.push(this.ec505plPanel); panels.push(this.aucPanel); panels.push(this.mfiPanel); return(panels); - } else if (this.controlType = "SinglePoint") { + } else if (this.controlType === "SinglePoint") { return([this.singlePointControlPanel]); } return null;