package rocks.inspectit.ui.rcp.ci.wizard.page;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.FormText;
import rocks.inspectit.shared.cs.ci.AlertingDefinition;
import rocks.inspectit.shared.cs.cmr.service.IInfluxDBService;
import rocks.inspectit.ui.rcp.InspectIT;
import rocks.inspectit.ui.rcp.InspectITImages;
/**
* Wizard Page for the definition of the data source for alerting processing.
*
* @author Alexander Wert
*
*/
public class AlertSourceDefinitionWizardPage extends WizardPage {
/**
* Title of the wizard page.
*/
private static final String TITLE = "Alert Definition Source";
/**
* Default message of the wizard page.
*/
private static final String DEFAULT_MESSAGE = "Define the name and source for the new alert definition.";
/**
* Description text for the name input field.
*/
private static final String NAME_INFO_TEXT = "Specify the name of the alerting rule.\nThis name must be unique.";
/**
* Description text for the metric input fields.
*/
private static final String METRIC_INFO_TEXT = "Specify the target metric on which this alerting rule shall be applied.\n"
+ "A metric is determined by the name of the measurement in the corresponding influxDB and the field name.";
/**
* Description text for the tags input fields.
*/
private static final String TAGS_INFO_TEXT = "Specify the scope of the target metric\n" + "by narrowing it down with additional tag specifications.\n"
+ "Tags are specified by a tag key and a tag value.";
/**
* Number of layout columns in the main composite of this page.
*/
private static final int NUM_LAYOUT_COLUMNS = 5;
/**
* Initial name of the alerting definition (used for editing mode).
*/
private String initialName;
/**
* Initial measurement name (used for editing mode).
*/
private String initialMeasurement;
/**
* Initial field name (used for editing mode).
*/
private String initialField;
/**
* Initial key-value pairs for the tags (used for editing mode).
*/
private Map<String, String> initialTags;
/**
* Name box.
*/
private Text nameBox;
/**
* Measurement box.
*/
private Combo measurementBox;
/**
* Field box.
*/
private Combo fieldBox;
/**
* List of existing items defining which names are already taken.
*/
private final Collection<String> existingItems;
/**
* A list of tag UI components currently created.
*/
private final List<TagKeyValueUIComponent> tagComponents = new ArrayList<>();
/**
* Listener that checks whether page can be completed.
*/
private Listener pageCompletionListener;
/**
* {@link IInfluxDBService} instance used to retrieve values for different source fields
* (measurement, field, tag keys and tag values).
*/
private IInfluxDBService influxService;
/**
* Constructor.
*
* @param influxService
* {@link IInfluxDBService} instance used to retrieve values for different source
* fields (measurement, field, tag keys and tag values).
* @param existingNames
* List of existing items defining which names are already taken.
*/
public AlertSourceDefinitionWizardPage(IInfluxDBService influxService, Collection<String> existingNames) {
this(influxService, existingNames, null, null, null, null);
}
/**
* Constructor.
*
* @param influxService
* {@link IInfluxDBService} instance used to retrieve values for different source
* fields (measurement, field, tag keys and tag values).
* @param existingNames
* List of existing items defining which names are already taken.
* @param name
* Initial name of the alerting definition (used for editing mode).
* @param measurement
* Initial measurement name (used for editing mode).
* @param field
* Initial field name (used for editing mode).
* @param tags
* Initial key-value pairs for the tags (used for editing mode).
*/
public AlertSourceDefinitionWizardPage(IInfluxDBService influxService, Collection<String> existingNames, String name, String measurement, String field, Map<String, String> tags) {
super(TITLE);
setTitle(TITLE);
setMessage(DEFAULT_MESSAGE);
this.influxService = influxService;
if (null != existingNames) {
existingItems = existingNames;
} else {
existingItems = Collections.emptyList();
}
this.initialName = name;
this.initialMeasurement = measurement;
this.initialField = field;
this.initialTags = tags;
}
/**
* {@inheritDoc}
*/
@Override
public void createControl(Composite parent) {
// create main composite
final ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL);
scrolledComposite.setExpandHorizontal(true);
scrolledComposite.setExpandVertical(true);
final Composite main = new Composite(scrolledComposite, SWT.NONE);
main.setLayout(new GridLayout(NUM_LAYOUT_COLUMNS, false));
// create name controls
Label nameLabel = new Label(main, SWT.LEFT);
nameLabel.setText("Name:");
nameLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
nameBox = new Text(main, SWT.BORDER);
nameBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, NUM_LAYOUT_COLUMNS - 2, 1));
Label infoLabelName = new Label(main, SWT.RIGHT);
infoLabelName.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
infoLabelName.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_INFORMATION));
infoLabelName.setToolTipText(NAME_INFO_TEXT);
// create measurement controls
Label measurementLabel = new Label(main, SWT.LEFT);
measurementLabel.setText("Measurement:");
measurementLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
measurementBox = new Combo(main, SWT.BORDER | SWT.DROP_DOWN);
measurementBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
List<String> measurements = influxService.getMeasurements();
if (null != measurements) {
measurementBox.setItems(measurements.toArray(new String[measurements.size()]));
}
// create field controls
Label fieldLabel = new Label(main, SWT.LEFT);
fieldLabel.setText("Field:");
fieldLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
fieldBox = new Combo(main, SWT.BORDER | SWT.DROP_DOWN);
fieldBox.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, false, NUM_LAYOUT_COLUMNS - 4, 1));
Label infoLabelMetric = new Label(main, SWT.RIGHT);
infoLabelMetric.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
infoLabelMetric.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_INFORMATION));
infoLabelMetric.setToolTipText(METRIC_INFO_TEXT);
// create heading for tags section
FormText headingText = new FormText(main, SWT.NONE);
headingText.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, NUM_LAYOUT_COLUMNS - 1, 1));
headingText.setFont("header", JFaceResources.getBannerFont());
headingText.setText("<form><p><span color=\"header\" font=\"header\">Tag Specifications</span></p></form>", true, false);
Label infoLabelTags = new Label(main, SWT.RIGHT);
infoLabelTags.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
infoLabelTags.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_INFORMATION));
infoLabelTags.setToolTipText(TAGS_INFO_TEXT);
setupListeners();
initContents(main);
// create add button for new tag rows
final FormText addText = new FormText(main, SWT.NONE);
addText.setLayoutData(new GridData(SWT.RIGHT, SWT.FILL, false, false, NUM_LAYOUT_COLUMNS, 1));
addText.setText("<form><p>Add tag specification ... <a href=\"delete\"><img href=\"addImg\" /></a></p></form>", true, false);
addText.setImage("addImg", InspectIT.getDefault().getImage(InspectITImages.IMG_ADD));
addText.addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent e) {
TagKeyValueUIComponent tagComponent = new TagKeyValueUIComponent(main);
addText.moveBelow(tagComponent.deleteText);
tagComponents.add(tagComponent);
main.layout(true, true);
scrolledComposite.setMinSize(main.computeSize(SWT.DEFAULT, SWT.DEFAULT));
pageCompletionListener.handleEvent(null);
}
});
// redraw and finalize main composite setup
main.layout();
scrolledComposite.setMinSize(main.computeSize(SWT.DEFAULT, SWT.DEFAULT));
setControl(scrolledComposite);
scrolledComposite.setContent(main);
}
/**
* Sets the message based on the page content.
*/
protected void setPageMessage() {
if (nameBox.getText().isEmpty()) {
setMessage("No value for the name entered!", ERROR);
return;
}
if (alreadyExists(getAlertingDefinitionName())) {
setMessage("An alert definition with this name already exists!", ERROR);
return;
}
if (measurementBox.getText().isEmpty()) {
setMessage("Measurement must not be empty!", ERROR);
return;
}
if (fieldBox.getText().isEmpty()) {
setMessage("Field must not be empty!", ERROR);
return;
}
for (TagKeyValueUIComponent tagComponent : tagComponents) {
if (tagComponent.getTagKey().isEmpty()) {
setMessage("Tag keys must not be empty!", ERROR);
return;
}
if (tagComponent.getTagValue().isEmpty()) {
setMessage("Tag value for key '" + tagComponent.getTagKey() + "' must not be empty!", ERROR);
return;
}
}
setMessage(DEFAULT_MESSAGE);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isPageComplete() {
if (nameBox.getText().isEmpty() || alreadyExists(nameBox.getText())) {
return false;
}
if (measurementBox.getText().isEmpty()) {
return false;
}
if (fieldBox.getText().isEmpty()) {
return false;
}
for (TagKeyValueUIComponent tagComponent : tagComponents) {
if (tagComponent.getTagKey().isEmpty() || tagComponent.getTagValue().isEmpty()) {
return false;
}
}
return true;
}
/**
* Returns the specified name for the {@link AlertingDefinition}.
*
* @return Returns the specified name for the {@link AlertingDefinition}.
*/
public String getAlertingDefinitionName() {
return nameBox.getText();
}
/**
* Returns the name of the target influxDB measurement.
*
* @return Returns the name of the target influxDB measurement.
*/
public String getMeasurement() {
return measurementBox.getText();
}
/**
* Returns the name of the target influxDB field.
*
* @return Returns the name of the target influxDB field.
*/
public String getField() {
return fieldBox.getText();
}
/**
* Returns the key-value pairs for the specification of the target time series.
*
* @return Returns the key-value pairs for the specification of the target time series.
*/
public Map<String, String> getTags() {
Map<String, String> map = new HashMap<>();
for (TagKeyValueUIComponent tagComponent : tagComponents) {
map.put(tagComponent.getTagKey(), tagComponent.getTagValue());
}
return map;
}
/**
* Sets up control listeners.
*/
private void setupListeners() {
pageCompletionListener = new Listener() {
@Override
public void handleEvent(Event event) {
setPageComplete(isPageComplete());
setPageMessage();
}
};
Listener measurementChangedListener = new Listener() {
@Override
public void handleEvent(Event event) {
updateFieldOptions();
for (TagKeyValueUIComponent tagComponent : tagComponents) {
tagComponent.updateTagKeyOptions();
}
}
};
nameBox.addListener(SWT.Modify, pageCompletionListener);
measurementBox.addListener(SWT.Modify, pageCompletionListener);
fieldBox.addListener(SWT.Modify, pageCompletionListener);
measurementBox.addListener(SWT.Modify, measurementChangedListener);
}
/**
* Indicates whether an element with such a name already exists.
*
* @param name
* name to check
* @return true, if an element with the same name already exists.
*/
private boolean alreadyExists(String name) {
for (String item : existingItems) {
if (item.equals(name)) {
return true;
}
}
return false;
}
/**
* Updates the selection options of the Field drop-down box.
*/
private void updateFieldOptions() {
String currentText = fieldBox.getText();
List<String> fields = influxService.getFields(measurementBox.getText());
if (null != fields) {
fieldBox.setItems(fields.toArray(new String[fields.size()]));
}
fieldBox.setText(currentText);
}
/**
* Initializes the contents of all fields if there are initial values.
*
* @param main
* The main composite where to attach tag UI components to.
*/
private void initContents(Composite main) {
if (null != initialName) {
nameBox.setText(initialName);
}
if (null != initialMeasurement) {
measurementBox.setText(initialMeasurement);
}
if (null != initialField) {
fieldBox.setText(initialField);
}
if (null != initialTags) {
for (Entry<String, String> tag : initialTags.entrySet()) {
TagKeyValueUIComponent tagComponent = new TagKeyValueUIComponent(main, tag.getKey(), tag.getValue());
tagComponents.add(tagComponent);
}
}
}
/**
* This class encapsulates the UI elements required to specify a single tag.
*
* @author Alexander Wert
*
*/
private class TagKeyValueUIComponent {
/**
* Key box.
*/
private Combo keyBox;
/**
* Value box.
*/
private Combo valueBox;
/**
* Label for the key.
*/
private Label keyLabel;
/**
* Label for the value.
*/
private Label valueLabel;
/**
* FormText for the delete button.
*/
private FormText deleteText;
/**
* Constructor.
*
* To be used in creation mode.
*
* @param parent
* The parent composite.
*/
TagKeyValueUIComponent(Composite parent) {
this(parent, null, null);
}
/**
* Constructor.
*
* To be used in editing mode.
*
* @param parent
* The parent composite.
* @param initialKey
* The initial key of the tag to be edited.
* @param initialValue
* The initial value of the tag to be edited.
*/
TagKeyValueUIComponent(final Composite parent, String initialKey, String initialValue) {
createControls(parent, initialKey, initialValue);
}
/**
* Creates the controls for this UI component.
*
* @param parent
* The parent composite.
* @param initialKey
* The initial key of the tag to be edited.
* @param initialValue
* The initial value of the tag to be edited.
*/
private void createControls(final Composite parent, String initialKey, String initialValue) {
// create controls for tag key
keyLabel = new Label(parent, SWT.LEFT);
keyLabel.setText("Key:");
keyLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
keyBox = new Combo(parent, SWT.BORDER | SWT.DROP_DOWN);
keyBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
if (null != initialKey) {
keyBox.setText(initialKey);
}
// create controls for tag value
valueLabel = new Label(parent, SWT.LEFT);
valueLabel.setText("Value:");
valueLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
valueBox = new Combo(parent, SWT.BORDER | SWT.DROP_DOWN);
valueBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
if (null != initialValue) {
valueBox.setText(initialValue);
}
// create controls for the delete button
deleteText = new FormText(parent, SWT.NONE);
deleteText.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
deleteText.setText("<form><p><a href=\"delete\"><img href=\"deleteImg\" /></a></p></form>", true, false);
deleteText.setImage("deleteImg", InspectIT.getDefault().getImage(InspectITImages.IMG_DELETE));
deleteText.addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent e) {
keyLabel.dispose();
keyBox.dispose();
valueLabel.dispose();
valueBox.dispose();
deleteText.dispose();
parent.layout(true, true);
tagComponents.remove(TagKeyValueUIComponent.this);
pageCompletionListener.handleEvent(null);
}
});
setupListeners();
updateTagKeyOptions();
}
/**
* Sets up control listeners.
*/
private void setupListeners() {
Listener tagKeyChangedListener = new Listener() {
@Override
public void handleEvent(Event event) {
updateAvailableTagValues();
}
};
keyBox.addListener(SWT.Modify, pageCompletionListener);
keyBox.addListener(SWT.Modify, tagKeyChangedListener);
valueBox.addListener(SWT.Modify, pageCompletionListener);
}
/**
* Returns the specified key of the tag.
*
* @return Returns the specified key of the tag.
*/
public String getTagKey() {
return keyBox.getText();
}
/**
* Returns the specified value of the tag.
*
* @return Returns the specified value of the tag.
*/
public String getTagValue() {
return valueBox.getText();
}
/**
* Updates the selection options for the key drop-down box depending on the selection in the
* measurement box.
*/
public void updateTagKeyOptions() {
String currentText = keyBox.getText();
List<String> tagKeys = influxService.getTags(measurementBox.getText());
if (null != tagKeys) {
keyBox.setItems(tagKeys.toArray(new String[tagKeys.size()]));
}
keyBox.setText(currentText);
}
/**
* Updates the selection options for the value drop-down box depending on the selection in
* the measurement box and the tag key box.
*/
public void updateAvailableTagValues() {
String currentText = valueBox.getText();
List<String> tagValues = influxService.getTagValues(measurementBox.getText(), keyBox.getText());
if (null != tagValues) {
valueBox.setItems(tagValues.toArray(new String[tagValues.size()]));
}
valueBox.setText(currentText);
}
}
}