/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.falcon.regression.ui.pages;
import org.apache.falcon.entity.v0.EntityType;
import org.apache.falcon.entity.v0.process.Process;
import org.apache.falcon.regression.core.helpers.ColoHelper;
import org.apache.falcon.regression.core.util.TimeUtil;
import org.apache.log4j.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Page of a process entity.
*/
public class ProcessPage extends EntityPage<Process> {
private static final Logger LOGGER = Logger.getLogger(ProcessPage.class);
private boolean isLineageOpened = false;
private static final String INSTANCES_PANEL = "//div[@id='panel-instance']//span";
private static final String INSTANCE_STATUS_TEMPLATE = INSTANCES_PANEL + "[contains(..,'%s')]";
private static final String LINEAGE_LINK_TEMPLATE =
"//a[@class='lineage-href' and @data-instance-name='%s']";
//Lineage information xpaths
private static final String CLOSE_LINEAGE_LINK_TEMPLATE =
"//body[@class='modal-open']//button[contains(., 'Close')]";
private static final String LINEAGE_MODAL = "//div[@id='lineage-modal']";
private static final String SVG_ELEMENT = "//*[name() = 'svg']/*[name()='g']/*[name()='g']";
private static final String VERTICES_BLOCKS = SVG_ELEMENT + "[not(@class='lineage-link')]";
private static final String VERTICES_TEXT = VERTICES_BLOCKS
+ "//div[@class='lineage-node-text']";
private static final String EDGE = SVG_ELEMENT + "[@class='lineage-link']//*[name()='path']";
private static final String CIRCLE = "//*[name() = 'circle']";
private static final String VERTICES = VERTICES_BLOCKS + CIRCLE;
private static final String VERTEX_BLOCK_TEMPLATE = VERTICES_BLOCKS + "[contains(., '%s')]";
private static final String VERTEX_TEMPLATE = VERTEX_BLOCK_TEMPLATE + CIRCLE;
private static final String LINEAGE_INFO_PANEL_LIST = "//div[@id='lineage-info-panel']"
+ "//div[@class='col-md-3']";
private static final String LINEAGE_TITLE = LINEAGE_MODAL + "//div[@class='modal-header']/h4";
private static final String LINEAGE_LEGENDS_BLOCK = LINEAGE_MODAL
+ "//div[@class='modal-body']/div[ul[@class='lineage-legend']]";
private static final String LINEAGE_LEGENDS_TITLE = LINEAGE_LEGENDS_BLOCK + "/h4";
private static final String LINEAGE_LEGENDS_ELEMENTS = LINEAGE_LEGENDS_BLOCK + "/ul/li";
public ProcessPage(WebDriver driver, ColoHelper helper, String entityName) {
super(driver, helper, EntityType.PROCESS, Process.class, entityName);
}
/**
* @param nominalTime particular instance of process, defined by it's start time
*/
public void openLineage(String nominalTime) {
waitForElement(String.format(LINEAGE_LINK_TEMPLATE, nominalTime), DEFAULT_TIMEOUT,
"Lineage button didn't appear");
LOGGER.info("Working with instance: " + nominalTime);
WebElement lineage =
driver.findElement(By.xpath(String.format(LINEAGE_LINK_TEMPLATE, nominalTime)));
LOGGER.info("Opening lineage...");
lineage.click();
waitForElement(VERTICES, DEFAULT_TIMEOUT, "Circles not found");
waitForDisplayed(LINEAGE_TITLE, DEFAULT_TIMEOUT, "Lineage title not found");
isLineageOpened = true;
}
public void closeLineage() {
LOGGER.info("Closing lineage...");
if (isLineageOpened) {
WebElement close = driver.findElement(By.xpath(CLOSE_LINEAGE_LINK_TEMPLATE));
close.click();
isLineageOpened = false;
waitForDisappear(CLOSE_LINEAGE_LINK_TEMPLATE, DEFAULT_TIMEOUT,
"Lineage didn't disappear");
}
}
@Override
public void refresh() {
super.refresh();
isLineageOpened = false;
}
/**
* @return map with instances names and their nominal start time
*/
public HashMap<String, List<String>> getAllVertices() {
LOGGER.info("Getting all vertices from lineage graph...");
HashMap<String, List<String>> map = null;
if (isLineageOpened) {
waitForElement(VERTICES_TEXT, DEFAULT_TIMEOUT,
"Vertices blocks with names not found");
List<WebElement> blocks = driver.findElements(By.xpath(VERTICES_TEXT));
LOGGER.info(blocks.size() + " elements found");
map = new HashMap<>();
for (WebElement block : blocks) {
waitForElement(block, ".[contains(.,'/')]", DEFAULT_TIMEOUT,
"Expecting text to contain '/' :" + block.getText());
String text = block.getText();
LOGGER.info("Vertex: " + text);
String[] separate = text.split("/");
String name = separate[0];
String nominalTime = separate[1];
if (map.containsKey(name)) {
map.get(name).add(nominalTime);
} else {
List<String> instances = new ArrayList<>();
instances.add(nominalTime);
map.put(name, instances);
}
}
}
return map;
}
/**
* @return list of all vertices names
*/
public List<String> getAllVerticesNames() {
LOGGER.info("Getting all vertices names from lineage graph...");
List<String> list = new ArrayList<>();
if (isLineageOpened) {
waitForElement(CLOSE_LINEAGE_LINK_TEMPLATE, DEFAULT_TIMEOUT,
"Close Lineage button not found");
waitForElement(VERTICES_BLOCKS, DEFAULT_TIMEOUT,
"Vertices not found");
List<WebElement> blocks = driver.findElements(By.xpath(VERTICES_BLOCKS));
LOGGER.info(blocks.size() + " elements found");
for (WebElement block : blocks) {
list.add(block.getText());
}
}
LOGGER.info("Vertices: " + list);
return list;
}
/**
* Vertex is defined by it's entity name and particular time of it's creation.
*/
public void clickOnVertex(String entityName, String nominalTime) {
LOGGER.info("Clicking on vertex " + entityName + '/' + nominalTime);
if (isLineageOpened) {
WebElement circle = driver.findElement(By.xpath(String.format(VERTEX_TEMPLATE,
entityName + '/' + nominalTime)));
Actions builder = new Actions(driver);
builder.click(circle).build().perform();
TimeUtil.sleepSeconds(0.5);
}
}
/**
* @return map of parameters from info panel and their values
*/
public HashMap<String, String> getPanelInfo() {
LOGGER.info("Getting info panel values...");
HashMap<String, String> map = null;
if (isLineageOpened) {
//check if vertex was clicked
waitForElement(LINEAGE_INFO_PANEL_LIST, DEFAULT_TIMEOUT, "Info panel not found");
List<WebElement> infoBlocks = driver.findElements(By.xpath(LINEAGE_INFO_PANEL_LIST));
LOGGER.info(infoBlocks.size() + " values found");
map = new HashMap<>();
for (WebElement infoBlock : infoBlocks) {
String text = infoBlock.getText();
String[] values = text.split("\n");
map.put(values[0], values[1]);
}
}
LOGGER.info("Values: " + map);
return map;
}
/**
* @return map of legends as key and their names on UI as values
*/
public HashMap<String, String> getLegends() {
HashMap<String, String> map = null;
if (isLineageOpened) {
map = new HashMap<>();
List<WebElement> legends = driver.findElements(By.xpath(LINEAGE_LEGENDS_ELEMENTS));
for (WebElement legend : legends) {
String value = legend.getText();
String elementClass = legend.getAttribute("class");
map.put(elementClass, value);
}
}
return map;
}
/**
* @return the main title of Lineage UI
*/
public String getLineageTitle() {
LOGGER.info("Getting Lineage title...");
if (isLineageOpened) {
return driver.findElement(By.xpath(LINEAGE_TITLE)).getText();
} else {
return null;
}
}
/**
* @return the name of legends block
*/
public String getLegendsTitle() {
LOGGER.info("Getting Legends title...");
if (isLineageOpened) {
return driver.findElement(By.xpath(LINEAGE_LEGENDS_TITLE)).getText();
} else {
return null;
}
}
/**
* @return list of edges present on UI. Each edge presented as two 2d points - beginning and
* the end of the edge.
*/
public List<Point[]> getEdgesFromGraph() {
List<Point[]> pathsEndpoints = null;
LOGGER.info("Getting edges from lineage graph...");
if (isLineageOpened) {
pathsEndpoints = new ArrayList<>();
List<WebElement> paths = driver.findElements(By.xpath(EDGE));
LOGGER.info(paths.size() + " edges found");
for (WebElement path : paths) {
String[] coordinates = path.getAttribute("d").split("[MLC,]");
int x = 0, y, i = 0;
while (i < coordinates.length) {
if (!coordinates[i].isEmpty()) {
x = (int) Double.parseDouble(coordinates[i]);
break;
} else {
i++;
}
}
y = (int) Double.parseDouble(coordinates[i + 1]);
Point startPoint = new Point(x, y);
x = (int) Math.round(Double.parseDouble(coordinates[coordinates.length - 2]));
y = (int) Math.round(Double.parseDouble(coordinates[coordinates.length - 1]));
Point endPoint = new Point(x, y);
LOGGER.info("Edge " + startPoint + '→' + endPoint);
pathsEndpoints.add(new Point[]{startPoint, endPoint});
}
}
return pathsEndpoints;
}
/**
* @return common value for radius of every vertex (circle) on the graph
*/
public int getCircleRadius() {
LOGGER.info("Getting value of vertex radius...");
WebElement circle = driver.findElements(By.xpath(VERTICES)).get(0);
return Integer.parseInt(circle.getAttribute("r"));
}
/**
* Finds vertex on the graph by its name and evaluates its coordinates as 2d point.
* @param vertex the name of vertex which point is needed
* @return Point(x,y) object
*/
public Point getVertexEndpoint(String vertex) {
/** get circle of start vertex */
LOGGER.info("Getting vertex coordinates...");
WebElement block = driver.findElement(By.xpath(String.format(VERTEX_BLOCK_TEMPLATE, vertex)));
String attribute = block.getAttribute("transform");
attribute = attribute.replaceAll("[a-zA-Z]", "");
String[] numbers = attribute.replaceAll("[()]", "").split(",");
return new Point(Integer.parseInt(numbers[0]), Integer.parseInt(numbers[1]));
}
/**
* Returns status of instance from instances panel.
* @param instanceDate date stamp of instance
* @return status of instance from instances panel
*/
public String getInstanceStatus(String instanceDate) {
waitForInstancesPanel();
LOGGER.info("Getting status of " + instanceDate + " instance");
List<WebElement> status =
driver.findElements(By.xpath(String.format(INSTANCE_STATUS_TEMPLATE, instanceDate)));
if (status.isEmpty()) {
return null;
} else {
return status.get(0).getAttribute("class").replace("instance-icons instance-link-", "");
}
}
/**
* Checks if 'Lineage' link is present on instances panel.
* @param instanceDate date stamp of instance
* @return true if link is present
*/
public boolean isLineageLinkPresent(String instanceDate) {
waitForInstancesPanel();
LOGGER.info("Checking if 'Lineage' link is present for " + instanceDate);
List<WebElement> lineage =
driver.findElements(By.xpath(String.format(LINEAGE_LINK_TEMPLATE, instanceDate)));
return !lineage.isEmpty();
}
private void waitForInstancesPanel() {
waitForElement(INSTANCES_PANEL, DEFAULT_TIMEOUT, "Instances panel didn't appear");
}
/**
* Checks whether vertex is terminal or not.
* @param vertexName name of vertex
* @return whether it is terminal or not
*/
public boolean isTerminal(String vertexName) {
LOGGER.info("Checking if " + vertexName + " is 'terminal' instance");
waitForElement(String.format(VERTEX_TEMPLATE, vertexName), DEFAULT_TIMEOUT,
"Vertex not found");
WebElement vertex = driver.findElement(By.xpath(String.format(VERTEX_TEMPLATE, vertexName)));
String vertexClass = vertex.getAttribute("class");
return vertexClass.contains("lineage-node-terminal");
}
}