/* * Copyright 2012 JBoss Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.overlord.rtgov.ui.client.local.pages; import static org.overlord.rtgov.ui.client.model.ResolutionState.IN_PROGRESS; import static org.overlord.rtgov.ui.client.model.ResolutionState.RESOLVED; import javax.annotation.PostConstruct; import javax.enterprise.context.Dependent; import javax.inject.Inject; import org.jboss.errai.databinding.client.api.DataBinder; import org.jboss.errai.databinding.client.api.InitialState; import org.jboss.errai.ui.nav.client.local.Page; import org.jboss.errai.ui.nav.client.local.PageState; import org.jboss.errai.ui.nav.client.local.TransitionAnchor; import org.jboss.errai.ui.nav.client.local.TransitionTo; import org.jboss.errai.ui.shared.api.annotations.AutoBound; import org.jboss.errai.ui.shared.api.annotations.Bound; import org.jboss.errai.ui.shared.api.annotations.DataField; import org.jboss.errai.ui.shared.api.annotations.EventHandler; import org.jboss.errai.ui.shared.api.annotations.Templated; import org.overlord.commons.gwt.client.local.widgets.HtmlSnippet; import org.overlord.commons.gwt.client.local.widgets.Pager; import org.overlord.rtgov.ui.client.model.ResolutionState; import org.overlord.rtgov.ui.client.local.ClientMessages; import org.overlord.rtgov.ui.client.local.events.TableSortEvent; import org.overlord.rtgov.ui.client.local.pages.situations.CallTraceDetails; import org.overlord.rtgov.ui.client.local.pages.situations.CallTraceWidget; import org.overlord.rtgov.ui.client.local.pages.situations.SituationContextTable; import org.overlord.rtgov.ui.client.local.pages.situations.SituationPropertiesTable; import org.overlord.rtgov.ui.client.local.pages.situations.SituationTable; import org.overlord.rtgov.ui.client.local.services.NotificationService; import org.overlord.rtgov.ui.client.local.services.SituationsServiceCaller; import org.overlord.rtgov.ui.client.local.services.rpc.IRpcServiceInvocationHandler; import org.overlord.rtgov.ui.client.local.services.rpc.IRpcServiceInvocationHandler.RpcServiceInvocationHandlerAdapter; import org.overlord.rtgov.ui.client.local.util.DOMUtil; import org.overlord.rtgov.ui.client.local.util.DataBindingDateTimeConverter; import org.overlord.rtgov.ui.client.local.widgets.common.SortableTemplatedWidgetTable.SortColumn; import org.overlord.rtgov.ui.client.local.widgets.common.SourceEditor; import org.overlord.rtgov.ui.client.model.NotificationBean; import org.overlord.rtgov.ui.client.model.SituationBean; import org.overlord.rtgov.ui.client.model.SituationResultSetBean; import org.overlord.rtgov.ui.client.model.SituationSummaryBean; import org.overlord.rtgov.ui.client.model.TraceNodeBean; import com.google.common.collect.HashMultimap; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.SpanElement; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.InlineLabel; /** * The Situation Details page. * * @author eric.wittmann@redhat.com */ @Templated("/org/overlord/rtgov/ui/client/local/site/situationDetails.html#page") @Page(path="situationDetails") @Dependent public class SituationDetailsPage extends AbstractPage { @Inject protected ClientMessages i18n; @Inject protected SituationsServiceCaller situationsService; @Inject protected NotificationService notificationService; @Inject protected IRpcServiceInvocationHandler.VoidInvocationHandler voidInvocationHandler; @PageState private String id; @Inject @AutoBound protected DataBinder<SituationBean> situation; // Breadcrumbs @Inject @DataField("back-to-dashboard") private TransitionAnchor<DashboardPage> toDashboardPage; @Inject @DataField("to-situations") private TransitionAnchor<SituationsPage> toSituationsPage; @Inject @DataField("to-services") private TransitionAnchor<ServicesPage> toServicesPage; // Header @Inject @DataField @Bound(property="type") InlineLabel situationName; @Inject @DataField FlowPanel severity; // Properties @Inject @DataField @Bound(property="subject") InlineLabel subject; @Inject @DataField @Bound(property="timestamp", converter=DataBindingDateTimeConverter.class) InlineLabel timestamp; @Inject @DataField @Bound(property="resolutionState") InlineLabel resolutionState; @Inject @DataField @Bound(property="description") InlineLabel description; @Inject @DataField("btn-resubmitted-situation") Button resubmittedSituationButton; @Inject TransitionTo<SituationDetailsPage> resubmittedSituationLink; @Inject @DataField("properties-table") @Bound(property="properties") SituationPropertiesTable propertiesTable; @Inject @DataField("context-table") @Bound(property="context") SituationContextTable contextTable; @Inject @DataField("call-trace") @Bound(property="callTrace") CallTraceWidget callTrace; @Inject @DataField("call-trace-detail") CallTraceDetails callTraceDetail; @Inject @DataField("messageTab") Anchor messageTabAnchor; @Inject @DataField("message-editor") SourceEditor messageEditor; @Inject @DataField("btn-resubmit") Button resubmitButton; @Inject @DataField InlineLabel resubmitDetails; @Inject @DataField("btn-assign") Button assignButton; @Inject @DataField("btn-start") Button startButton; @Inject @DataField("btn-resolve") Button resolveButton; @Inject @DataField("resubmitFailuresTab") Anchor resubmitFailuresTabAnchor; @Inject @DataField("btn-refresh") protected Button refreshButton; @Inject @DataField("resubmit-failures-none") protected HtmlSnippet noDataMessage; @Inject @DataField("resubmit-failures-searching") protected HtmlSnippet searchInProgressMessage; @Inject @DataField("resubmit-failures-table") protected SituationTable resubmitFailuresTable; @Inject @DataField("resubmit-failures-pager") protected Pager pager; @DataField("resubmit-failures-range") protected SpanElement rangeSpan = Document.get().createSpanElement(); @DataField("resubmit-failures-total") protected SpanElement totalSpan = Document.get().createSpanElement(); private int currentPage = 1; @Inject @DataField("situation-details-loading-spinner") protected HtmlSnippet loading; protected Element pageContent; String resubmittedId=null; /** * Constructor. */ public SituationDetailsPage() { } /** * Called after the widget is constructed. */ @PostConstruct protected void onPostConstruct() { pageContent = DOMUtil.findElementById(getElement(), "situation-details-content-wrapper"); //$NON-NLS-1$ pageContent.addClassName("hide"); //$NON-NLS-1$ callTraceDetail.setVisible(false); callTrace.addSelectionHandler(new SelectionHandler<TraceNodeBean>() { @Override public void onSelection(SelectionEvent<TraceNodeBean> event) { onCallTraceNodeSelected(event.getSelectedItem()); } }); pager.addValueChangeHandler(new ValueChangeHandler<Integer>() { @Override public void onValueChange(ValueChangeEvent<Integer> event) { doRetrieveResubmitFailures(event.getValue()); } }); resubmitFailuresTable.addTableSortHandler(new TableSortEvent.Handler() { @Override public void onTableSort(TableSortEvent event) { doRetrieveResubmitFailures(currentPage); } }); // Hide column 1 when in mobile mode. resubmitFailuresTable.setColumnClasses(1, "desktop-only"); //$NON-NLS-1$ this.rangeSpan.setInnerText("?"); //$NON-NLS-1$ this.totalSpan.setInnerText("?"); //$NON-NLS-1$ } /** * Event handler that fires when the user clicks the refresh button. * @param event */ @EventHandler("btn-refresh") public void onRefreshClick(ClickEvent event) { doRetrieveResubmitFailures(currentPage); } /** * @see org.overlord.sramp.ui.client.local.pages.AbstractPage#onPageShowing() */ @Override protected void onPageShowing() { pageContent.addClassName("hide"); //$NON-NLS-1$ loading.getElement().removeClassName("hide"); //$NON-NLS-1$ loadSituationAndUpdatePageData(); } private void loadSituationAndUpdatePageData() { situationsService.get(id, new IRpcServiceInvocationHandler<SituationBean>() { @Override public void onReturn(SituationBean data) { updateMetaData(data); if (data.getResubmissionFailureTotalCount() > 0) { doRetrieveResubmitFailures(); } } @Override public void onError(Throwable error) { notificationService.sendErrorNotification(i18n.format("situation-details.error-getting-detail-info"), error); //$NON-NLS-1$ } }); } /** * Called when the situation is loaded. * @param situation */ protected void updateMetaData(final SituationBean situation) { this.situation.setModel(situation, InitialState.FROM_MODEL); severity.setStyleName("icon"); //$NON-NLS-1$ severity.addStyleName("details-icon"); //$NON-NLS-1$ severity.addStyleName("icon-severity-" + situation.getSeverity()); //$NON-NLS-1$ loading.getElement().addClassName("hide"); //$NON-NLS-1$ pageContent.removeClassName("hide"); //$NON-NLS-1$ this.messageTabAnchor.setVisible(situation.hasMessage()); if (situation.hasMessage()) { messageEditor.setValue(situation.getMessage().getContent()); } else { messageEditor.setValue(""); //$NON-NLS-1$ } resubmitButton.setEnabled(situation.isResubmitPossible() && !situation.getSituationId().equals(resubmittedId)); messageEditor.setReadOnly(!situation.isResubmitPossible() && !situation.getSituationId().equals(resubmittedId)); if (situation.resubmitBy() != null) { resubmitDetails.setText(i18n.format("situation-details.resubmit-details", situation.resubmitBy(), situation.resubmitAt(), situation.resubmitResult())); resubmitDetails.setTitle(situation.resubmitErrorMessage()); } else { resubmitDetails.setTitle(""); resubmitDetails.setText(""); } if (situation.resubmitError()) { resubmitDetails.getElement().addClassName("text-error"); //$NON-NLS-1$ } else { resubmitDetails.getElement().removeClassName("text-error"); //$NON-NLS-1$ } if (situation.isAssignedToCurrentUser()) { assignButton.getElement().addClassName("hide"); } else { assignButton.getElement().removeClassName("hide"); } ResolutionState resolutionState = ResolutionState.valueOf(situation.getResolutionState()); if (situation.isManualResolutionPossible() && ResolutionState.UNRESOLVED == resolutionState) { startButton.getElement().removeClassName("hide"); } else { startButton.getElement().addClassName("hide"); } if (situation.isManualResolutionPossible() && ResolutionState.IN_PROGRESS == resolutionState) { resolveButton.getElement().removeClassName("hide"); } else { resolveButton.getElement().addClassName("hide"); } if (situation.getResubmittedSituationId() == null) { resubmittedSituationButton.getElement().addClassName("hide"); } this.resubmitFailuresTable.clear(); if (situation.getResubmissionFailureTotalCount() > 0) { this.resubmitFailuresTabAnchor.setVisible(true); } else { this.resubmitFailuresTabAnchor.setVisible(false); } } /** * Event handler called when the user clicks an item in the call trace widget. * @param event */ public void onCallTraceNodeSelected(TraceNodeBean node) { callTraceDetail.setValue(node); callTraceDetail.setVisible(true); } /** * Called when the user clicks the Resubmit button. * @param event */ @EventHandler("btn-resubmitted-situation") protected void onResubmittedSituationClick(ClickEvent event) { HashMultimap<String,String> map=HashMultimap.<String,String>create(); map.put("id", situation.getModel().getResubmittedSituationId()); resubmittedSituationLink.go(map); } /** * Called when the user clicks the Resubmit button. * @param event */ @EventHandler("btn-resubmit") protected void onResubmitClick(ClickEvent event) { final NotificationBean notificationBean = notificationService.startProgressNotification( i18n.format("situation-details.resubmit-message-title"), //$NON-NLS-1$ i18n.format("situation-details.resubmit-message-msg", this.situation.getModel().getSubject())); //$NON-NLS-1$ situationsService.resubmit(situation.getModel().getSituationId(), this.messageEditor.getValue(), new RpcServiceInvocationHandlerAdapter<Void>() { @Override public void doOnReturn(Void data) { notificationService.completeProgressNotification(notificationBean.getUuid(), i18n.format("situation-details.message-resubmitted"), //$NON-NLS-1$ i18n.format("situation-details.resubmit-success-msg", situation.getModel().getSubject())); //$NON-NLS-1$ } @Override public void doOnError(Throwable error) { notificationService.completeProgressNotification(notificationBean.getUuid(), i18n.format("situation-details.resubmit-error"), //$NON-NLS-1$ error); } @Override public void doOnComplete(RpcResult<Void> result) { resubmittedId = situation.getModel().getSituationId(); loadSituationAndUpdatePageData(); } }); } @EventHandler("btn-assign") protected void onAssignButtonClick(ClickEvent event) { situationsService.assign(id, new IRpcServiceInvocationHandler<Void>() { @Override public void onReturn(Void data) { loadSituationAndUpdatePageData(); } @Override public void onError(Throwable error) { } }); } @EventHandler("btn-start") protected void onStartButtonClick(ClickEvent event) { situationsService.updateResolutionState(id, IN_PROGRESS.name(), new IRpcServiceInvocationHandler<Void>() { @Override public void onReturn(Void data) { loadSituationAndUpdatePageData(); } @Override public void onError(Throwable error) { } }); } @EventHandler("btn-resolve") protected void onResolveButtonClick(ClickEvent event) { situationsService.updateResolutionState(id, RESOLVED.name(), new IRpcServiceInvocationHandler<Void>() { @Override public void onReturn(Void data) { loadSituationAndUpdatePageData(); } @Override public void onError(Throwable error) { } }); } /** * Retrieve resubmit failures. */ protected void doRetrieveResubmitFailures() { doRetrieveResubmitFailures(1); } /** * Retrieve resubmit failures for specified page. * @param page */ protected void doRetrieveResubmitFailures(int page) { onRetrieveResubmitFailuresStarting(); currentPage = page; SortColumn currentSortColumn = this.resubmitFailuresTable.getCurrentSortColumn(); situationsService.getResubmitFailures(id, page, currentSortColumn.columnId, currentSortColumn.ascending, new IRpcServiceInvocationHandler<SituationResultSetBean>() { @Override public void onReturn(SituationResultSetBean data) { updateTable(data); updatePager(data); } @Override public void onError(Throwable error) { notificationService.sendErrorNotification(i18n.format("situations.error-loading"), error); //$NON-NLS-1$ noDataMessage.setVisible(true); searchInProgressMessage.setVisible(false); } }); } /** * Called when a new retrieval is kicked off. */ protected void onRetrieveResubmitFailuresStarting() { this.pager.setVisible(false); this.searchInProgressMessage.setVisible(true); this.resubmitFailuresTable.setVisible(false); this.noDataMessage.setVisible(false); this.rangeSpan.setInnerText("?"); //$NON-NLS-1$ this.totalSpan.setInnerText("?"); //$NON-NLS-1$ } /** * Updates the table of resubmit failures with the given data. * @param data */ protected void updateTable(SituationResultSetBean data) { this.resubmitFailuresTable.clear(); this.searchInProgressMessage.setVisible(false); if (data.getSituations().size() > 0) { for (SituationSummaryBean summaryBean : data.getSituations()) { this.resubmitFailuresTable.addRow(summaryBean); } this.resubmitFailuresTable.setVisible(true); } else { this.noDataMessage.setVisible(true); } } /** * Updates the pager with the given data. * @param data */ protected void updatePager(SituationResultSetBean data) { int numPages = ((int) (data.getTotalResults() / data.getItemsPerPage())) + (data.getTotalResults() % data.getItemsPerPage() == 0 ? 0 : 1); int thisPage = (data.getStartIndex() / data.getItemsPerPage()) + 1; this.pager.setNumPages(numPages); this.pager.setPage(thisPage); if (numPages > 1) this.pager.setVisible(true); int startIndex = data.getStartIndex() + 1; int endIndex = startIndex + data.getSituations().size() - 1; String rangeText = "" + startIndex + "-" + endIndex; //$NON-NLS-1$ //$NON-NLS-2$ String totalText = String.valueOf(data.getTotalResults()); this.rangeSpan.setInnerText(rangeText); this.totalSpan.setInnerText(totalText); } }