Skip to content

Commit 31b1ebf

Browse files
committed
files added
1 parent f22172e commit 31b1ebf

File tree

9 files changed

+330
-0
lines changed

9 files changed

+330
-0
lines changed

pom.xml

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>org.example</groupId>
8+
<artifactId>GustoTracker</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
<build>
11+
<plugins>
12+
<plugin>
13+
<groupId>org.apache.maven.plugins</groupId>
14+
<artifactId>maven-compiler-plugin</artifactId>
15+
<version>3.8.1</version>
16+
<configuration>
17+
<source>8</source>
18+
<target>8</target>
19+
</configuration>
20+
</plugin>
21+
</plugins>
22+
</build>
23+
<dependencies>
24+
<dependency>
25+
<groupId>org.seleniumhq.selenium</groupId>
26+
<artifactId>selenium-java</artifactId>
27+
<version>3.141.59</version>
28+
</dependency>
29+
<dependency>
30+
<groupId>org.apache.poi</groupId>
31+
<artifactId>poi</artifactId>
32+
<version>4.0.0</version>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.apache.poi</groupId>
36+
<artifactId>poi-ooxml</artifactId>
37+
<version>4.0.0</version>
38+
</dependency>
39+
<dependency>
40+
<groupId>org.projectlombok</groupId>
41+
<artifactId>lombok</artifactId>
42+
<version>1.18.10</version>
43+
<scope>provided</scope>
44+
</dependency>
45+
</dependencies>
46+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.essheva;
2+
3+
4+
import lombok.AccessLevel;
5+
import lombok.Getter;
6+
7+
import java.io.FileNotFoundException;
8+
import java.io.FileReader;
9+
import java.io.IOException;
10+
import java.nio.file.Path;
11+
import java.nio.file.Paths;
12+
import java.util.Properties;
13+
14+
public class Configuration {
15+
16+
private static final String RESOURCE_DIR_PATH = "src/main/resources";
17+
private static final String BI_WEEKLY_STATUS = "BiWeeklyStatus.xlsx";
18+
private static final String USER_LOGIN = "secret.properties";
19+
20+
@Getter
21+
final Path driverPath;
22+
23+
@Getter(AccessLevel.PACKAGE)
24+
final Path statusFilePath;
25+
26+
@Getter(AccessLevel.PACKAGE)
27+
final String userEmail;
28+
29+
@Getter(AccessLevel.PACKAGE)
30+
final String userPassword;
31+
32+
Configuration() throws IOException {
33+
this.driverPath = getWebDriverFolderPathByOS();
34+
this.statusFilePath = Paths.get(RESOURCE_DIR_PATH, BI_WEEKLY_STATUS);
35+
36+
Properties userSecret = loadPropertiesFromFile();
37+
userEmail = getValue(userSecret, "user_email");
38+
userPassword = getValue(userSecret, "user_password");
39+
}
40+
41+
private static Path getWebDriverFolderPathByOS() {
42+
String os = System.getProperty("os.name").toLowerCase();
43+
final String dirName;
44+
if (os.contains("win")) {
45+
dirName = "win32";
46+
} else if (os.contains("mac")) {
47+
dirName = "mac64";
48+
} else if (os.contains("nux")) {
49+
dirName = "linux64";
50+
} else {
51+
throw new UnsupportedOperationException(os + " is not supported");
52+
}
53+
return Paths.get(RESOURCE_DIR_PATH, "chomedriver", dirName, "chromedriver");
54+
}
55+
56+
private String getValue(Properties props, String s) {
57+
final String value = props.getProperty(s);
58+
if (value == null) {
59+
throw new IllegalArgumentException("Property not set " + s);
60+
}
61+
return value;
62+
}
63+
64+
private static FileReader getReader() throws FileNotFoundException {
65+
return new FileReader(Paths.get(RESOURCE_DIR_PATH, Configuration.USER_LOGIN).toFile());
66+
}
67+
68+
private static Properties loadPropertiesFromFile() throws IOException {
69+
Properties props = new Properties();
70+
props.load(getReader());
71+
return props;
72+
}
73+
}
+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package com.essheva;
2+
3+
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
4+
import org.openqa.selenium.*;
5+
import org.openqa.selenium.chrome.ChromeDriver;
6+
import org.openqa.selenium.interactions.Actions;
7+
import org.openqa.selenium.support.ui.WebDriverWait;
8+
9+
import java.io.IOException;
10+
import java.time.format.DateTimeFormatter;
11+
import java.util.List;
12+
13+
14+
public class GustoTracker {
15+
16+
GustoTracker() throws IOException, InvalidFormatException {
17+
Configuration config = new Configuration();
18+
TimeTrackerExcelReader reader = new TimeTrackerExcelReader(config.getStatusFilePath());
19+
List<DayRecord> records = reader.getRecords();
20+
21+
System.setProperty("webdriver.chrome.driver", config.getDriverPath().toAbsolutePath().toString());
22+
WebDriver driver = new ChromeDriver();
23+
24+
driver.get("http://gusto.com/login");
25+
26+
driver.findElement(By.id("user_email")).sendKeys(config.getUserEmail());
27+
driver.findElement(By.id("user_password")).sendKeys(config.getUserPassword());
28+
driver.findElement(By.id("btn-login")).submit();
29+
30+
driver.get("https://app.gusto.com/time_tracking");
31+
32+
logTime(records, driver);
33+
}
34+
35+
private void logTime(List<DayRecord> records, WebDriver driver) {
36+
JavascriptExecutor js = (JavascriptExecutor) driver;
37+
int offSet = 0;
38+
for (DayRecord record : records) {
39+
40+
System.out.println("Processing record: " + record);
41+
42+
int day = record.getDate().getDayOfMonth();
43+
String month = DateTimeFormatter.ofPattern("MMM").format(record.getDate());
44+
45+
WebElement tr = null;
46+
while (tr == null) {
47+
js.executeScript("window.scrollBy(0,100)");
48+
tr = new WebDriverWait(driver, 5).until(
49+
driver1 -> driver1.findElement(
50+
By.xpath("//tr[td//span/text()='" + day + "' and td//span/text()='" + month + "']")));
51+
}
52+
53+
54+
try {
55+
tr.findElement(By.xpath(".//td[@data-title='Breakdown']//span[@class='breakdown__hours']"));
56+
offSet += 600;
57+
continue;
58+
} catch (NoSuchElementException ignore) {}
59+
60+
js.executeScript("window.scrollTo(0, arguments[0])", offSet);
61+
62+
Actions actions = new Actions(driver);
63+
WebElement expand = tr.findElement(By.xpath(".//td[@data-title='Hours']"));
64+
actions.moveToElement(expand).perform();
65+
66+
WebElement expandButton = tr.findElement(By.xpath(".//div[@class='time-log__result']//i"));
67+
while (!expandButton.isDisplayed()) {
68+
js.executeScript("window.scrollBy(0,300)");
69+
}
70+
actions.moveToElement(expandButton).click().perform();
71+
72+
logData(js, record, tr);
73+
74+
js.executeScript("window.scrollBy(0, 750)");
75+
76+
WebElement submitButton = tr.findElement(By.xpath(".//div[@class='flex']//button"));
77+
submitButton.submit();
78+
79+
js.executeScript("window.scrollBy(0, -800)");
80+
offSet += 600;
81+
}
82+
}
83+
84+
private void logData(JavascriptExecutor js, DayRecord record, WebElement tr) {
85+
DateTimeFormatter gustoTimeFormatter = DateTimeFormatter.ofPattern("hh:mma");
86+
87+
String startTime = gustoTimeFormatter.format(record.getStart());
88+
String endTime = gustoTimeFormatter.format(record.getEnd());
89+
String breakTime = gustoTimeFormatter.format(record.getBreakStarted());
90+
91+
WebElement start = tr.findElement(By.xpath("(.//div[@class='time-log__shift']//input[@type='time'])[1]"));
92+
WebElement end = tr.findElement(By.xpath("(.//div[@class='time-log__shift']//input[@type='time'])[2]"));
93+
94+
start.sendKeys(startTime);
95+
putData(endTime, end);
96+
WebElement breakRadioYes = tr.findElement(By.xpath(
97+
"(.//div[@class='break-input']//input[@type='radio'])[1]"));
98+
WebElement breakRadioNo = tr.findElement(By.xpath(
99+
"(.//div[@class='break-input']//input[@type='radio'])[2]"));
100+
js.executeScript("arguments[0].click();", breakRadioYes);
101+
102+
WebElement breakStarted = tr.findElement(
103+
By.xpath(".//div[@class='break-input-secondary-group']//input[@name='break_start']"));
104+
breakStarted.sendKeys(breakTime);
105+
WebElement breakDuration = tr.findElement(
106+
By.xpath(".//div[@class='break-input-secondary-group']//input[@name='break_duration']"));
107+
breakDuration.clear();
108+
breakDuration.sendKeys(String.valueOf(record.getBreakDuration()));
109+
WebElement notes = tr.findElement(By.tagName("textarea"));
110+
notes.sendKeys(record.getDescription());
111+
}
112+
113+
private void putData(String endTime, WebElement end) {
114+
end.sendKeys(endTime);
115+
}
116+
117+
public static void main(String[] args) throws Exception {
118+
new GustoTracker();
119+
}
120+
}
121+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.essheva;
2+
3+
import lombok.Getter;
4+
import lombok.Setter;
5+
import lombok.ToString;
6+
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
7+
import org.apache.poi.ss.usermodel.Cell;
8+
import org.apache.poi.ss.usermodel.Row;
9+
import org.apache.poi.ss.usermodel.Sheet;
10+
import org.apache.poi.ss.usermodel.Workbook;
11+
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
12+
13+
import java.io.IOException;
14+
import java.nio.file.Path;
15+
import java.time.Instant;
16+
import java.time.LocalDate;
17+
import java.time.LocalTime;
18+
import java.time.ZoneId;
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
public class TimeTrackerExcelReader {
23+
24+
final private Sheet sheet;
25+
26+
TimeTrackerExcelReader(Path file) throws IOException, InvalidFormatException {
27+
Workbook workbook = new XSSFWorkbook(file.toFile());
28+
this.sheet = getLastSheet(workbook);
29+
}
30+
31+
private Sheet getLastSheet(Workbook workbook) {
32+
return workbook.getSheetAt(workbook.getNumberOfSheets() - 1);
33+
}
34+
35+
List<DayRecord> getRecords() {
36+
List<DayRecord> records = new ArrayList<>();
37+
for (int i = 1; i < sheet.getPhysicalNumberOfRows(); i++) {
38+
Row row = sheet.getRow(i);
39+
if (row == null) continue;
40+
DayRecord record = new DayRecord();
41+
int j = 0;
42+
for (Cell cell: row) {
43+
switch (cell.getCellType()) {
44+
case STRING:
45+
record.setDescription(cell.getStringCellValue());
46+
break;
47+
case NUMERIC:
48+
String data_format = cell.getCellStyle().getDataFormatString();
49+
if (data_format.equals("m/d/yy")) {
50+
LocalDate date = cell.getDateCellValue().toInstant()
51+
.atZone(ZoneId.systemDefault())
52+
.toLocalDate();
53+
record.setDate(date);
54+
} else if (data_format.contains("h:mm:ss")) {
55+
LocalTime time = Instant.ofEpochMilli(cell.getDateCellValue().getTime())
56+
.atZone(ZoneId.systemDefault())
57+
.toLocalTime();
58+
Row header = sheet.getRow(0);
59+
String cellValue = header.getCell(j).getStringCellValue();
60+
switch (cellValue) {
61+
case "Start Work": record.setStart(time); break;
62+
case "End Work": record.setEnd(time); break;
63+
case "Start Break": record.setBreakStarted(time); break;
64+
}
65+
} else {
66+
record.setBreakDuration((int)(cell.getNumericCellValue()));
67+
}
68+
break;
69+
default:
70+
throw new IllegalStateException("Unexpected value: " + cell.getCellType());
71+
}
72+
j++;
73+
}
74+
System.out.println(record);
75+
records.add(record);
76+
}
77+
return records;
78+
}
79+
}
80+
81+
@ToString
82+
class DayRecord {
83+
@Setter @Getter private LocalDate date;
84+
@Setter @Getter private LocalTime start;
85+
@Setter @Getter private LocalTime end;
86+
@Setter @Getter private LocalTime breakStarted;
87+
@Setter @Getter private int breakDuration;
88+
@Setter @Getter private String description;
89+
}
9.1 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
----------ChromeDriver v2.38 (2018-04-17)----------
Binary file not shown.

0 commit comments

Comments
 (0)