/* * RHQ Management Platform * Copyright (C) 2005-2013 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.coregui.client.dashboard.portlets.inventory.resource.graph; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.rpc.AsyncCallback; import com.smartgwt.client.data.Criteria; import com.smartgwt.client.types.Overflow; import com.smartgwt.client.widgets.Canvas; import com.smartgwt.client.widgets.HTMLFlow; import com.smartgwt.client.widgets.Label; import com.smartgwt.client.widgets.events.ResizedEvent; import com.smartgwt.client.widgets.events.ResizedHandler; import com.smartgwt.client.widgets.form.DynamicForm; import com.smartgwt.client.widgets.form.events.SubmitValuesEvent; import com.smartgwt.client.widgets.form.events.SubmitValuesHandler; import com.smartgwt.client.widgets.form.fields.SelectItem; import com.smartgwt.client.widgets.form.fields.TextItem; import com.smartgwt.client.widgets.form.fields.events.ChangedEvent; import org.rhq.core.domain.common.EntityContext; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.criteria.ResourceCriteria; import org.rhq.core.domain.dashboard.DashboardPortlet; import org.rhq.core.domain.measurement.MeasurementDefinition; import org.rhq.core.domain.measurement.composite.MeasurementDataNumericHighLowComposite; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.util.PageList; import org.rhq.coregui.client.CoreGUI; import org.rhq.coregui.client.LinkManager; import org.rhq.coregui.client.components.lookup.ResourceLookupComboBoxItem; import org.rhq.coregui.client.dashboard.AutoRefreshPortlet; import org.rhq.coregui.client.dashboard.AutoRefreshUtil; import org.rhq.coregui.client.dashboard.CustomSettingsPortlet; import org.rhq.coregui.client.dashboard.Portlet; import org.rhq.coregui.client.dashboard.PortletViewFactory; import org.rhq.coregui.client.dashboard.PortletWindow; import org.rhq.coregui.client.gwt.GWTServiceLookup; import org.rhq.coregui.client.gwt.ResourceGWTServiceAsync; import org.rhq.coregui.client.inventory.common.graph.MetricGraphData; import org.rhq.coregui.client.inventory.common.graph.graphtype.StackedBarMetricGraphImpl; import org.rhq.coregui.client.inventory.resource.AncestryUtil; import org.rhq.coregui.client.inventory.resource.detail.monitoring.MetricD3Graph; import org.rhq.coregui.client.inventory.resource.detail.monitoring.ResourceScheduledMetricDatasource; import org.rhq.coregui.client.inventory.resource.type.ResourceTypeRepository; import org.rhq.coregui.client.util.BrowserUtility; import org.rhq.coregui.client.util.Log; import org.rhq.enterprise.server.measurement.util.MeasurementUtils; /** * @author Greg Hinkle * @author Jay Shaughnessy * @author Mike Thompson */ public class ResourceD3GraphPortlet extends MetricD3Graph implements AutoRefreshPortlet, CustomSettingsPortlet { // A non-displayed, persisted identifier for the portlet public static final String KEY = "ResourceMetricD3"; // A default displayed, persisted name for the portlet public static final String NAME = MSG.view_portlet_defaultName_resourceMetric(); public static final String CFG_RESOURCE_ID = "resourceId"; public static final String CFG_DEFINITION_ID = "definitionId"; public static final String CFG_CUSTOM_TITLE = "customTitle"; // set on initial configuration, the window for this portlet view. private PortletWindow portletWindow; public ResourceD3GraphPortlet() { super(); setOverflow(Overflow.CLIP_H); addResizedHandler(new ResizedHandler() { public void onResized(ResizedEvent event) { refresh(); } }); } @Override public void configure(PortletWindow portletWindow, DashboardPortlet storedPortlet) { Log.debug("ResourceD3GraphPortlet Configure"); if (null == this.portletWindow && null != portletWindow) { this.portletWindow = portletWindow; } destroyMembers(); graph = GWT.create(StackedBarMetricGraphImpl.class); graph.setMetricGraphData(MetricGraphData.createForDashboard(portletWindow.getStoredPortlet().getId())); if ((null == storedPortlet) || (null == storedPortlet.getConfiguration())) { return; } if (storedPortlet.getConfiguration().getSimple(CFG_RESOURCE_ID) != null) { refreshFromConfiguration(storedPortlet); } } private void refreshFromConfiguration(DashboardPortlet storedPortlet) { PropertySimple resourceIdProperty = storedPortlet.getConfiguration().getSimple(CFG_RESOURCE_ID); PropertySimple measurementDefIdProperty = storedPortlet.getConfiguration().getSimple(CFG_DEFINITION_ID); String customTitle = storedPortlet.getConfiguration().getSimpleValue(CFG_CUSTOM_TITLE, ""); if (resourceIdProperty != null && measurementDefIdProperty != null) { final Integer entityId = resourceIdProperty.getIntegerValue(); final Integer measurementDefId = measurementDefIdProperty.getIntegerValue(); if (entityId != null && measurementDefId != null) { queryResource(entityId, measurementDefId, customTitle); } } } private void queryResource(Integer entityId, final Integer measurementDefId, String customTitle) { ResourceGWTServiceAsync resourceService = GWTServiceLookup.getResourceService(); final String portletTitle = customTitle == null || customTitle.isEmpty() ? NAME : customTitle; ResourceCriteria resourceCriteria = new ResourceCriteria(); resourceCriteria.addFilterId(entityId); resourceService.findResourcesByCriteria(resourceCriteria, new AsyncCallback<PageList<Resource>>() { @Override public void onFailure(Throwable caught) { CoreGUI.getErrorHandler().handleError(MSG.view_resource_monitor_graphs_lookupFailed(), caught); } @Override public void onSuccess(PageList<Resource> result) { if (result.isEmpty()) { return; } // only concerned with first resource since this is a query by id final Resource resource = result.get(0); HashSet<Integer> typesSet = new HashSet<Integer>(); typesSet.add(resource.getResourceType().getId()); HashSet<String> ancestries = new HashSet<String>(); ancestries.add(resource.getAncestry()); // In addition to the types of the result resources, get the types of their ancestry typesSet.addAll(AncestryUtil.getAncestryTypeIds(ancestries)); ResourceTypeRepository.Cache.getInstance().getResourceTypes( typesSet.toArray(new Integer[typesSet.size()]), EnumSet.of(ResourceTypeRepository.MetadataType.measurements), new ResourceTypeRepository.TypesLoadedCallback() { @Override public void onTypesLoaded(final Map<Integer, ResourceType> types) { ResourceType type = types.get(resource.getResourceType().getId()); for (final MeasurementDefinition def : type.getMetricDefinitions()) { if (def.getId() == measurementDefId) { Log.debug("Found portlet measurement definition !" + def); // Adding the resource link in the portlet pushed the chart down too far, so // I'm adding it to the title. TODO: In the future (RHQ Metrics) the link // back should be done better and it should provide ancestry in a nice way. portletWindow.setTitle(portletTitle + " - " + LinkManager.getHref(LinkManager.getResourceLink(resource.getId()), resource.getName())); // I couldn't figure a good way to show ancestry. There is no way to set // a tooltip on only the window title or header and having it on the whole // window is annoying. //portletWindow.setTooltip(AncestryUtil.getAncestryHoverHTMLForResource(resource, // types, 0)); graph.setEntityId(resource.getId()); graph.setEntityName(resource.getName()); graph.setDefinition(def); final long startTime = System.currentTimeMillis(); GWTServiceLookup.getMeasurementDataService().findDataForResourceForLast( resource.getId(), new int[] { def.getId() }, 8, MeasurementUtils.UNIT_HOURS, 60, new AsyncCallback<List<List<MeasurementDataNumericHighLowComposite>>>() { @Override public void onFailure(Throwable caught) { CoreGUI.getErrorHandler().handleError( MSG.view_resource_monitor_graphs_loadFailed(), caught); } @Override public void onSuccess( final List<List<MeasurementDataNumericHighLowComposite>> measurementData) { Log.debug("Dashboard Metric data in: " + (System.currentTimeMillis() - startTime) + " ms."); graph.getMetricGraphData().setMetricData(measurementData.get(0)); if (!BrowserUtility.isBrowserPreIE9()) { drawGraph(); } } }); break; } } } }); } }); } @Override protected void setupGraphDiv(HTMLFlow graphDiv) { graphDiv.setHeight100(); graphDiv.setWidth("750px"); } @Override public Canvas getHelpCanvas() { return new HTMLFlow(MSG.view_portlet_help_graph()); } @Override protected void onDraw() { Log.debug("ResourceD3GraphPortlet.onDraw()"); DashboardPortlet storedPortlet = portletWindow.getStoredPortlet(); if (BrowserUtility.isBrowserPreIE9()) { removeMembers(getMembers()); addMember(new Label("<i>" + MSG.chart_ie_not_supported() + "</i>")); } else { PropertySimple simple = storedPortlet.getConfiguration().getSimple(CFG_RESOURCE_ID); if (simple == null || simple.getIntegerValue() == null) { removeMembers(getMembers()); addMember(new Label("<i>" + MSG.view_portlet_configure_needed() + "</i>")); } else { super.onDraw(); } } } @Override public DynamicForm getCustomSettingsForm() { final DynamicForm form = new DynamicForm(); final ResourceLookupComboBoxItem resourceLookupComboBoxItem = new ResourceLookupComboBoxItem(CFG_RESOURCE_ID, MSG.common_title_resource()); resourceLookupComboBoxItem.setWidth(300); final SelectItem metric = new SelectItem(CFG_DEFINITION_ID, MSG.common_title_metric()) { @Override protected Criteria getPickListFilterCriteria() { Criteria criteria = new Criteria(); if (resourceLookupComboBoxItem.getValue() != null) { int resourceId = (Integer) resourceLookupComboBoxItem.getValue(); criteria.addCriteria(CFG_RESOURCE_ID, resourceId); } return criteria; } }; final TextItem customTitle = new TextItem(CFG_CUSTOM_TITLE, MSG.view_portlet_configure_customTitle()); customTitle.setWidth(300); final DashboardPortlet storedPortlet = portletWindow.getStoredPortlet(); metric.setWidth(300); metric.setValueField("id"); metric.setDisplayField("displayName"); metric.setOptionDataSource(new ResourceScheduledMetricDatasource()); resourceLookupComboBoxItem .addChangedHandler(new com.smartgwt.client.widgets.form.fields.events.ChangedHandler() { public void onChanged(ChangedEvent event) { if (form.getValue(CFG_RESOURCE_ID) instanceof Integer) { metric.fetchData(); form.clearValue(CFG_DEFINITION_ID); } } }); if (storedPortlet.getConfiguration().getSimple(CFG_RESOURCE_ID) != null) { Integer integerValue = storedPortlet.getConfiguration().getSimple(CFG_RESOURCE_ID).getIntegerValue(); if (integerValue != null) { form.setValue(CFG_RESOURCE_ID, integerValue); } PropertySimple propertySimple = storedPortlet.getConfiguration().getSimple(CFG_DEFINITION_ID); if (propertySimple != null && propertySimple.getIntegerValue() != null) { form.setValue(CFG_DEFINITION_ID, propertySimple.getIntegerValue()); } form.setValue(CFG_CUSTOM_TITLE, storedPortlet.getConfiguration().getSimpleValue(CFG_CUSTOM_TITLE)); } form.setFields(resourceLookupComboBoxItem, metric, customTitle); form.addSubmitValuesHandler(new SubmitValuesHandler() { public void onSubmitValues(SubmitValuesEvent submitValuesEvent) { storedPortlet.getConfiguration().put( new PropertySimple(CFG_RESOURCE_ID, form.getValue(CFG_RESOURCE_ID))); storedPortlet.getConfiguration().put( new PropertySimple(CFG_DEFINITION_ID, form.getValue(CFG_DEFINITION_ID))); storedPortlet.getConfiguration().put( new PropertySimple(CFG_CUSTOM_TITLE, form.getValueAsString(CFG_CUSTOM_TITLE))); // this will cause the graph to draw configure(portletWindow, storedPortlet); } }); return form; } @Override public void redraw() { DashboardPortlet storedPortlet = portletWindow.getStoredPortlet(); PropertySimple simple = storedPortlet.getConfiguration().getSimple(CFG_RESOURCE_ID); if (simple == null || simple.getIntegerValue() == null) { removeMembers(getMembers()); addMember(new Label("<i>" + MSG.view_portlet_configure_needed() + "</i>")); } else { graph.getMetricGraphData().setEntityId(simple.getIntegerValue()); PropertySimple simpleDefId = storedPortlet.getConfiguration().getSimple(CFG_DEFINITION_ID); graph.getMetricGraphData().setDefinitionId(simpleDefId.getIntegerValue()); Log.debug("Redraw Portlet for entityId: " + simple.getIntegerValue() + "-" + simpleDefId.getIntegerValue()); if (!BrowserUtility.isBrowserPreIE9()) { drawGraph(); } } super.redraw(); } @Override /** * Portlet Charts are defined by an additional portletId to enable a particular resourceId/measurementId * combination to be valid in multiple dashboards. */ public String getFullChartId() { if (portletWindow != null && graph != null && graph.getMetricGraphData() != null) { return "rChart-" + graph.getMetricGraphData().getChartId() + "-" + portletWindow.getStoredPortlet().getId(); } else { // handle the case where the portlet has not been configured yet return ""; } } public static final class Factory implements PortletViewFactory { public static final PortletViewFactory INSTANCE = new Factory(); @Override public final Portlet getInstance(EntityContext context) { return new ResourceD3GraphPortlet(); } } public void startRefreshCycle() { refreshTimer = AutoRefreshUtil.startRefreshCycleWithPageRefreshInterval(this, this, refreshTimer); } @Override protected void onDestroy() { AutoRefreshUtil.onDestroy(refreshTimer); super.onDestroy(); } public boolean isRefreshing() { return false; } //Custom refresh operation as we are not directly extending Table @Override public void refresh() { if (isVisible() && !isRefreshing()) { refreshFromConfiguration(portletWindow.getStoredPortlet()); } } }