Skip to content

Commit 4490847

Browse files
committed
Initial implementation of survey history
1 parent 1e80ca3 commit 4490847

14 files changed

Lines changed: 362 additions & 148 deletions

File tree

frontend/generated/vaadin.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
import './vaadin-featureflags.ts';
22

33
import './index';
4-
5-
import '@vaadin/flow-frontend/vaadin-dev-tools.js';

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@
230230
"@vaadin/vertical-layout": "23.2.11",
231231
"@vaadin/virtual-list": "23.2.11",
232232
"@webcomponents/webcomponentsjs": "^2.2.10",
233-
"apexcharts": "3.30.0",
233+
"apexcharts": "3.35.0",
234234
"construct-style-sheets-polyfill": "3.1.0",
235235
"date-fns": "2.29.3",
236236
"lit": "2.3.1",
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/******************************************************************************
2+
*
3+
* Copyright 2020-, UT-Battelle, LLC. All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are met:
7+
*
8+
* o Redistributions of source code must retain the above copyright notice, this
9+
* list of conditions and the following disclaimer.
10+
*
11+
* o Redistributions in binary form must reproduce the above copyright notice,
12+
* this list of conditions and the following disclaimer in the documentation
13+
* and/or other materials provided with the distribution.
14+
*
15+
* o Neither the name of the copyright holder nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*
30+
*******************************************************************************/package io.bssw.psip.backend.model;
31+
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
35+
/*
36+
* Keep a history of survey scores. The scores are timestamped, but
37+
* are maintained in the order they were created even if the timestamps
38+
* are out of order.
39+
*
40+
* TODO: should we be able to remove scores?
41+
*/
42+
public class SurveyHistory {
43+
private List<SurveyScore> scores = new ArrayList();
44+
45+
public List<SurveyScore> getScores() {
46+
return scores;
47+
}
48+
49+
public void setScores(List<SurveyScore> scores) {
50+
this.scores = scores;
51+
}
52+
53+
public void addScore(SurveyScore score) {
54+
scores.add(score);
55+
}
56+
}

src/main/java/io/bssw/psip/backend/service/AbstractRepositoryProvider.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
import java.util.List;
66
import java.util.Map;
77

8+
import org.springframework.context.ApplicationListener;
9+
810
import com.vaadin.flow.component.html.Image;
911

1012
import io.bssw.psip.backend.model.ProviderConfiguration;
13+
import io.bssw.psip.backend.service.events.AuthChangeEvent;
14+
import io.bssw.psip.backend.service.events.AuthChangeEvent.Type;
1115

12-
public abstract class AbstractRepositoryProvider implements RepositoryProvider {
16+
public abstract class AbstractRepositoryProvider implements RepositoryProvider, ApplicationListener<AuthChangeEvent> {
1317
private ProviderConfiguration configuration;
1418
private RepositoryProviderManager repositoryManager;
1519

@@ -94,4 +98,14 @@ protected RepositoryProviderManager getRepositoryManager() {
9498
protected void setRepositoryManager(RepositoryProviderManager repositoryManager) {
9599
this.repositoryManager = repositoryManager;
96100
}
101+
102+
@Override
103+
public void onApplicationEvent(AuthChangeEvent event) {
104+
if (event.getType() == Type.LOGIN) {
105+
login();
106+
} else {
107+
logout();
108+
}
109+
110+
}
97111
}

src/main/java/io/bssw/psip/backend/service/GitHubRepositoryProvider.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import org.kohsuke.github.GHBranch;
99
import org.kohsuke.github.GHContent;
10+
import org.kohsuke.github.GHFileNotFoundException;
1011
import org.kohsuke.github.GHRepository;
1112
import org.kohsuke.github.GitHub;
1213
import org.kohsuke.github.PagedIterator;
@@ -76,9 +77,23 @@ public InputStream readFile(String path) throws IOException {
7677
@Override
7778
public void writeFile(String path, String contents) throws IOException {
7879
if (ghRepo != null && ghBranch != null) {
79-
ghRepo.createContent().path(path).content(contents).branch(ghBranch.getName()).commit();
80+
try {
81+
GHContent file = ghRepo.getFileContent(path, ghBranch.getName());
82+
file.update(contents, "User action: update history");
83+
} catch (IOException e) {
84+
if (!(e instanceof GHFileNotFoundException)) {
85+
throw e;
86+
}
87+
ghRepo.createContent()
88+
.path(path)
89+
.content(contents)
90+
.branch(ghBranch.getName())
91+
.message("User action: new history")
92+
.commit();
93+
}
94+
} else {
95+
throw new IOException("Not connected");
8096
}
81-
throw new IOException("Not connected");
8297
}
8398

8499
@Override

src/main/java/io/bssw/psip/backend/service/RepositoryProviderManager.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.util.List;
44

55
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.context.ApplicationEventPublisher;
7+
import org.springframework.context.ApplicationEventPublisherAware;
68
import org.springframework.security.core.Authentication;
79
import org.springframework.security.core.context.SecurityContextHolder;
810
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
@@ -17,16 +19,19 @@
1719
import com.vaadin.flow.server.VaadinServletRequest;
1820

1921
import io.bssw.psip.backend.model.ProviderConfiguration;
22+
import io.bssw.psip.backend.service.events.AuthChangeEvent;
23+
import io.bssw.psip.backend.service.events.AuthChangeEvent.Type;
2024

2125
@Service
22-
public class RepositoryProviderManager {
26+
public class RepositoryProviderManager implements ApplicationEventPublisherAware {
2327
@Autowired
2428
private List<RepositoryProvider> repositoryProviders;
2529
@Autowired
2630
private ProviderService providerService;
2731
@Autowired
2832
private OAuth2AuthorizedClientService clientService;
2933

34+
private ApplicationEventPublisher publisher;
3035
private ProviderConfiguration currentConfiguration;
3136
private RepositoryProvider currentProvider;
3237
private String redirectUrl;
@@ -133,6 +138,7 @@ public boolean logout() {
133138
if (currentProvider != null) {
134139
currentProvider.logout();
135140
}
141+
publisher.publishEvent(new AuthChangeEvent(this, Type.LOGOUT));
136142
setProviderConfiguration(null);
137143
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
138144
logoutHandler.logout(
@@ -147,6 +153,7 @@ public String loginSuccessful() {
147153
if (currentProvider != null) {
148154
currentProvider.login();
149155
}
156+
publisher.publishEvent(new AuthChangeEvent(this, Type.LOGIN));
150157
String url = getRedirectUrl();
151158
setRedirectUrl(null);
152159
return url;
@@ -174,4 +181,9 @@ protected OAuth2AccessToken getAccessToken() {
174181
}
175182
return null;
176183
}
184+
185+
@Override
186+
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
187+
this.publisher = publisher;
188+
}
177189
}

src/main/java/io/bssw/psip/backend/service/SurveyService.java

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,41 +32,59 @@
3232

3333
import java.io.IOException;
3434
import java.io.InputStream;
35+
import java.time.Instant;
3536
import java.util.HashMap;
37+
import java.util.Iterator;
3638
import java.util.Map;
3739

3840
import org.springframework.beans.factory.annotation.Autowired;
41+
import org.springframework.context.ApplicationListener;
3942
import org.springframework.stereotype.Service;
4043
import org.yaml.snakeyaml.Yaml;
4144
import org.yaml.snakeyaml.constructor.Constructor;
4245
import org.yaml.snakeyaml.error.YAMLException;
4346

47+
import com.fasterxml.jackson.databind.ObjectMapper;
48+
49+
import io.bssw.psip.backend.model.Category;
50+
import io.bssw.psip.backend.model.CategoryScore;
4451
import io.bssw.psip.backend.model.Item;
52+
import io.bssw.psip.backend.model.ItemScore;
4553
import io.bssw.psip.backend.model.Survey;
4654
import io.bssw.psip.backend.model.SurveyContent;
55+
import io.bssw.psip.backend.model.SurveyScore;
56+
import io.bssw.psip.backend.service.events.AuthChangeEvent;
57+
import io.bssw.psip.backend.model.SurveyHistory;
4758

4859
// Must be session scope to ensure only one service (and resulting entities) per session
4960
// @VaadinSessionScope
5061
@Service
51-
public class SurveyService {
62+
public class SurveyService implements ApplicationListener<AuthChangeEvent> {
5263
private static final String SURVEY_FILE = ".psip/survey.yml";
64+
private static final String HISTORY_FILE = ".psip/history.json";
5365

5466
@Autowired
5567
private RepositoryProviderManager repositoryManager;
5668

5769
private Survey survey;
70+
private SurveyHistory history;
5871

5972
private final Map<String, Item> items = new HashMap<String, Item>();
6073
private final Map<String, Item> prevItems = new HashMap<String, Item>();
6174
private final Map<String, Item> nextItems = new HashMap<String, Item>();
6275

6376
public Survey getSurvey() {
6477
if (survey == null) {
65-
return loadSurvey();
78+
loadSurvey();
79+
loadSurveyHistory();
6680
}
6781
return survey;
6882
}
6983

84+
public void reset() {
85+
survey = null;
86+
}
87+
7088
public Item getItem(String path) {
7189
return items.get(path);
7290
}
@@ -98,11 +116,10 @@ public Survey load(InputStream inputStream) throws YAMLException {
98116
}
99117

100118
/*
101-
* (Re)load a new survey. Make sure we get a new survey if
102-
* the repository provider changes.
119+
* (Re)load a new survey from a repository. Assumes that the repository
120+
* provider is connected. If not it will load the default survey.
103121
*/
104-
public Survey loadSurvey() throws YAMLException {
105-
survey = null;
122+
private void loadSurvey() throws YAMLException {
106123
items.clear();
107124
prevItems.clear();
108125
nextItems.clear();
@@ -119,7 +136,69 @@ public Survey loadSurvey() throws YAMLException {
119136
stream = getClass().getResourceAsStream("/assessment.yml");
120137
}
121138
survey = load(stream);
122-
return survey;
123139
}
124140

141+
private void loadSurveyHistory() {
142+
history = new SurveyHistory();
143+
InputStream stream = null;
144+
if (repositoryManager.isLoggedIn()) {
145+
RepositoryProvider provider = repositoryManager.getRepositoryProvider();
146+
try {
147+
stream = provider.readFile(HISTORY_FILE);
148+
} catch (IOException e) {
149+
// Just use default file
150+
}
151+
}
152+
if (stream != null) {
153+
ObjectMapper mapper = new ObjectMapper();
154+
try {
155+
history = mapper.readValue(stream, SurveyHistory.class);
156+
} catch (IOException e) {
157+
}
158+
}
159+
}
160+
161+
public SurveyScore generateScore(Survey survey) {
162+
SurveyScore surveyScore = new SurveyScore();
163+
surveyScore.setVersion(survey.getVersion());
164+
surveyScore.setTimestamp(Instant.now().toString());
165+
Iterator<Category> categoryIter = survey.getCategories().iterator();
166+
while (categoryIter.hasNext()) {
167+
Category category = categoryIter.next();
168+
CategoryScore catScore = new CategoryScore();
169+
catScore.setPath(category.getPath());
170+
Iterator<Item> itemIter = category.getItems().iterator();
171+
while (itemIter.hasNext()) {
172+
Item item = itemIter.next();
173+
ItemScore score = new ItemScore();
174+
score.setPath(item.getPath());
175+
score.setValue(item.getScore().orElse(0).toString());
176+
catScore.getItemScores().add(score);
177+
}
178+
surveyScore.getCategoryScores().add(catScore);
179+
}
180+
return surveyScore;
181+
}
182+
183+
/*
184+
* Save the current survey values into the history and
185+
* write out to the repository
186+
*/
187+
public void saveSurveyHistory(Survey survey) throws Exception {
188+
SurveyScore score = generateScore(survey);
189+
history.addScore(score);
190+
ObjectMapper mapper = new ObjectMapper();
191+
String value = mapper.writeValueAsString(history);
192+
if (repositoryManager.isLoggedIn()) {
193+
RepositoryProvider provider = repositoryManager.getRepositoryProvider();
194+
provider.writeFile(HISTORY_FILE, value);
195+
} else {
196+
throw new IOException("Not logged in");
197+
}
198+
}
199+
200+
@Override
201+
public void onApplicationEvent(AuthChangeEvent event) {
202+
reset();
203+
}
125204
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.bssw.psip.backend.service.events;
2+
3+
import org.springframework.context.ApplicationEvent;
4+
5+
public class AuthChangeEvent extends ApplicationEvent {
6+
public enum Type {
7+
LOGIN,
8+
LOGOUT
9+
}
10+
11+
private final Type type;
12+
13+
public AuthChangeEvent(Object source, Type type) {
14+
super(source);
15+
this.type = type;
16+
}
17+
18+
public Type getType() {
19+
return type;
20+
}
21+
}

src/main/java/io/bssw/psip/ui/MainLayout.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public <C extends Component & HasUrlParameter<String>> void initNaviItems() {
196196

197197
private <C extends Component & HasUrlParameter<String>> void loadSurvey(Activity activity, Class<? extends Component> route, NaviItem actNav) {
198198
NaviMenu menu = getNaviDrawer().getMenu();
199-
Survey survey = surveyService.loadSurvey();
199+
Survey survey = surveyService.getSurvey();
200200
List<Category> categories = survey.getCategories();
201201
ListIterator<Category> categoryIter = categories.listIterator();
202202
Item prev = null;

src/main/java/io/bssw/psip/ui/components/navigation/bar/AppBar.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,10 @@ private void initAvatar() {
184184
Notification.Position.BOTTOM_CENTER));
185185
contextMenu.addItem("Log Out",
186186
e -> {
187-
UI.getCurrent().getPage().setLocation("/");
188187
if (repositoryManager.isLoggedIn()) {
189188
repositoryManager.logout();
190189
}
190+
UI.getCurrent().getPage().setLocation("/");
191191
});
192192
}
193193
}

0 commit comments

Comments
 (0)