diff --git a/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java b/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java index 7f9345afddb6e20a6be146b935dad0debd5f9beb..3fdd4502a74557afc22b4d9b40b608d5cf31dbe0 100644 --- a/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java +++ b/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java @@ -292,6 +292,30 @@ public class MetaDataRepository { return result; } + + public SearchResultsDTO searchByEmail(String email, int pageSize, Optional<User> user) throws IOException { + + SearchRequest searchRequest = new SearchRequest(SearchRepositoryConstants.INDEX_METADATA); + + BoolQueryBuilder emailQuery = QueryBuilders.boolQuery(); + + emailQuery + .must(QueryBuilders.prefixQuery(SearchRepositoryConstants.METADATA_CREATOR_EMAIL, email)); + + //addAuthorizationQuery(user, emailQuery); + + + SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); + sourceBuilder.query(emailQuery).size(pageSize) + .from(0); + + searchRequest.source(sourceBuilder); + + SearchResponse searchResponse = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT); + + return parseSearchResponse(searchResponse); + } + /** * returns the hits from the meta data search index for the input query parameters. * @param searchInputDTO the query parameters. diff --git a/src/main/java/at/ac/uibk/gitsearch/repository/search/SearchRepositoryConstants.java b/src/main/java/at/ac/uibk/gitsearch/repository/search/SearchRepositoryConstants.java index e06ff8e904f244c8d4b70d89f423ace58f9495aa..d2cee8b3a12f37d5c0584a9978b9fb56ffd31d45 100644 --- a/src/main/java/at/ac/uibk/gitsearch/repository/search/SearchRepositoryConstants.java +++ b/src/main/java/at/ac/uibk/gitsearch/repository/search/SearchRepositoryConstants.java @@ -22,6 +22,7 @@ public final class SearchRepositoryConstants { public static final String METADATA_PROGRAMMING_LANGUAGES = "metadata.programmingLanguage"; public static final String METADATA_PROJECT_ID = "project.project_id"; public static final String METADATA_CREATOR = "metadata.creator.name"; + public static final String METADATA_CREATOR_EMAIL = "metadata.creator.email"; public static final String METADATA_CONTRIBUTOR = "metadata.contributor.name"; public static final String METADATA_PUBLISHER = "metadata.publisher.name"; public static final String METADATA_LICENSE = "metadata.license"; diff --git a/src/main/java/at/ac/uibk/gitsearch/service/GitlabService.java b/src/main/java/at/ac/uibk/gitsearch/service/GitlabService.java index 7833a917bdec8d3174e9dde95654397d4720f392..451f0a5a986048b818a84728c1c33ad52d9ea80c 100644 --- a/src/main/java/at/ac/uibk/gitsearch/service/GitlabService.java +++ b/src/main/java/at/ac/uibk/gitsearch/service/GitlabService.java @@ -6,6 +6,8 @@ import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; @@ -13,7 +15,12 @@ import java.util.zip.ZipOutputStream; import org.gitlab4j.api.GitLabApi; import org.gitlab4j.api.GitLabApiException; import org.gitlab4j.api.ProjectApi; +import org.gitlab4j.api.Constants.ProjectOrderBy; +import org.gitlab4j.api.Constants.SortOrder; +import org.gitlab4j.api.models.Project; import org.gitlab4j.api.models.RepositoryFile; +import org.gitlab4j.api.models.Visibility; +import org.gitlab4j.api.models.Note.OrderBy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -53,6 +60,19 @@ public class GitlabService { return false; } } + + public void getAllProjectIdsForUser() { + try{ + final GitLabApi gitLabApi = gitLabRepository.getGitLabApi(tokenProvider.getGitLabAccessInfo()); + final ProjectApi gitLabProjectApi = gitLabApi.getProjectApi(); + final List<Project> projects = gitLabProjectApi.getProjects(false, null, ProjectOrderBy.UPDATED_AT, SortOrder.ASC, "", true, true, true, false, true); + + log.info("Got all projects {}" , projects.size());} + catch(GitLabApiException e){ + log.error("Failed to load projects\n" + e.getMessage(), e); + } + } + public InputStream getRepositoryZip(String exerciseID) throws GitLabApiException, IOException { diff --git a/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java b/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java index d2969c64eb19a4f34b5941c80870654715c1e671..63d19d3f506d75cf1053bbacfbeb4d63a75423ea 100644 --- a/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java +++ b/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java @@ -133,6 +133,23 @@ public class SearchService { // return readTestResults(searchInput.getFulltextQuery(), first, length); } + public SearchResultsDTO searchResultForAuthorEmail(String searchInput, int length) throws IOException { + log.debug("Searchrequest for {} ", searchInput); + + final SearchResultsDTO pageDetails = metaDataRepository.searchByEmail(searchInput, length, + tokenProvider.getCurrentPrincipal()); + + pageDetails.getSearchResult().stream() + .forEach(hit -> pluginManagementService.getRegisteredPluginConfigs().stream() + .forEach(config -> config.getActions().values().stream() + .filter(action -> action.isApplicable(hit)) + .forEach(action -> hit.getSupportedActions().add(action.getPluginActionInfo())))); + pageDetails.getSearchResult().stream().forEach(this::fixImageURL); + + return pageDetails; + // return readTestResults(searchInput.getFulltextQuery(), first, length); + } + private void fixImageURL(SearchResultDTO metaData) { String image = metaData.getMetadata().getImage(); diff --git a/src/main/java/at/ac/uibk/gitsearch/service/dto/SearchInputDTO.java b/src/main/java/at/ac/uibk/gitsearch/service/dto/SearchInputDTO.java index 86d4ff266a9e93b42631255860390f33872a919f..dec65c4f7dbec930d3c5fd5c030d63ab6b81c59b 100644 --- a/src/main/java/at/ac/uibk/gitsearch/service/dto/SearchInputDTO.java +++ b/src/main/java/at/ac/uibk/gitsearch/service/dto/SearchInputDTO.java @@ -38,6 +38,10 @@ public class SearchInputDTO { return metadata; } + public void setMetadata(SearchInputMetadataDTO metadata){ + this.metadata = metadata; + } + public List<String> getSelectedRepository() { return selectedRepository; } diff --git a/src/main/java/at/ac/uibk/gitsearch/web/rest/SearchResource.java b/src/main/java/at/ac/uibk/gitsearch/web/rest/SearchResource.java index a7870da4fb9ee996e09ebc35b1e59d4db4c146e3..ae57e2c0e6753cb7d4983c5938004d0af144742b 100644 --- a/src/main/java/at/ac/uibk/gitsearch/web/rest/SearchResource.java +++ b/src/main/java/at/ac/uibk/gitsearch/web/rest/SearchResource.java @@ -14,6 +14,7 @@ import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -28,10 +29,14 @@ import at.ac.uibk.gitsearch.service.SearchService; import at.ac.uibk.gitsearch.service.StatisticsService; import at.ac.uibk.gitsearch.service.dto.AutoCompleteEntry; import at.ac.uibk.gitsearch.service.dto.SearchInputDTO; +import at.ac.uibk.gitsearch.service.dto.SearchInputMetadataDTO; +import at.ac.uibk.gitsearch.service.dto.SearchResultDTO; import at.ac.uibk.gitsearch.service.dto.SearchResultsDTO; import at.ac.uibk.gitsearch.service.dto.StatisticsDTO; import at.ac.uibk.gitsearch.web.util.HeaderUtil; +import org.springframework.http.HttpStatus; + /** * REST controller for managing {@link DocumentInfo}. */ @@ -45,7 +50,6 @@ public class SearchResource { private static final String ENTITY_NAME = "SearchResource"; - private final Logger log = LoggerFactory.getLogger(SearchResource.class); private final SearchService searchService; @@ -58,17 +62,42 @@ public class SearchResource { } /** - * {@code SEARCH /search/page-details} : search for the searchResults corresponding - * to the query. + * {@code SEARCH /search/page-details} : search for the searchResults + * corresponding to the query. * * @param query the query of the searchResult search. * @return the result of the search. */ @PostMapping("/search/page-details") - public SearchResultsDTO - searchPageDetails(@RequestBody SearchInputDTO query) throws IOException { + public SearchResultsDTO searchPageDetails(@RequestBody SearchInputDTO query) throws IOException { log.debug("REST request to search {}", query); - return searchService.searchResultPage(query, (long) SearchInputDTO.PAGE_SIZE * (long) query.getPage(), SearchInputDTO.PAGE_SIZE); + return searchService.searchResultPage(query, (long) SearchInputDTO.PAGE_SIZE * (long) query.getPage(), + SearchInputDTO.PAGE_SIZE); + } + + @GetMapping("/user/{login}/statistics/views") + @PreAuthorize("hasAnyRole('USER')") + public ResponseEntity<StatisticsDTO> searchUserStatistics(@PathVariable String login) throws IOException { + log.debug("REST request to search statistics for user {}", login); + SearchInputDTO search = new SearchInputDTO(); + SearchInputMetadataDTO metadata = new SearchInputMetadataDTO(); + metadata.setAuthor(login); + search.setMetadata(metadata); + SearchResultsDTO results = searchService.searchResultForAuthorEmail("Michael.Breu@uibk.ac.at", 1000); + int totalAmountOfViews = 0; + int totalAmountOfDownloads = 0; + for (SearchResultDTO result : results.getSearchResult()) { + Optional<StatisticsDTO> statisticsDTO = statisticsService.findOneByExerciseID((Long.parseLong(result.getExerciseId()))); + if (statisticsDTO.isPresent()) { + StatisticsDTO newStats = statisticsDTO.get(); + totalAmountOfViews += newStats.getViews(); + totalAmountOfDownloads += newStats.getDownloads(); + } + } + StatisticsDTO toReturn = new StatisticsDTO(); + toReturn.setViews(totalAmountOfViews); + toReturn.setDownloads(totalAmountOfDownloads); + return new ResponseEntity<StatisticsDTO>(toReturn, HttpStatus.OK); } /** @@ -103,21 +132,22 @@ public class SearchResource { * @throws IOException */ @GetMapping("/search/contributorAutoComplete") - public List<AutoCompleteEntry> getContributorAutoComplete(@RequestParam String contributorPrefix) throws IOException { + public List<AutoCompleteEntry> getContributorAutoComplete(@RequestParam String contributorPrefix) + throws IOException { return searchService.getContributorAutoComplete(contributorPrefix); } - /** - * returns all contributor and author autocompletes - * - * @param contributorPrefix - * @return - * @throws IOException - */ + /** + * returns all contributor and author autocompletes + * + * @param contributorPrefix + * @return + * @throws IOException + */ @GetMapping("/search/contributorCreatorAutoComplete") - public List<AutoCompleteEntry> getContributorCreatorAutoComplete(String contributorPrefix) throws IOException { - return searchService.getContributorCreatorAutoComplete(contributorPrefix); - } + public List<AutoCompleteEntry> getContributorCreatorAutoComplete(String contributorPrefix) throws IOException { + return searchService.getContributorCreatorAutoComplete(contributorPrefix); + } /** * returns all programmingLanguage autocompletes for keyWord @@ -127,31 +157,34 @@ public class SearchResource { * @throws IOException */ @GetMapping("/search/programmingLanguageAutoComplete") - public List<AutoCompleteEntry> getProgrammingLanguageAutoComplete(@RequestParam String programmingLanguagePrefix) throws IOException { + public List<AutoCompleteEntry> getProgrammingLanguageAutoComplete(@RequestParam String programmingLanguagePrefix) + throws IOException { return searchService.getProgrammingLanguageAutoComplete(programmingLanguagePrefix); } - /** - * POST /programming-exercises/:exerciseId/export-programmingExercise : sends all repositories and details of the programming exercise as zip - * TODO projectId and ExerciseId are mixed up here. Please clarify! + * POST /programming-exercises/:exerciseId/export-programmingExercise : sends + * all repositories and details of the programming exercise as zip TODO + * projectId and ExerciseId are mixed up here. Please clarify! * - * @param projectId the id of the exercise to get the repos from - * ResponseEntity with status + * @param projectId the id of the exercise to get the repos from ResponseEntity + * with status * @throws IOException if something during the zip process went wrong */ @PostMapping("/programming-exercises/{projectId}/export-programming-exercise") public ResponseEntity<Resource> exportProgrammingExercise(@PathVariable long projectId) throws IOException { File zipFile = searchService.exportExercise(projectId); - + if (zipFile == null) { - return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(applicationName, true, ENTITY_NAME, "internalServerError", - "There was an error on the server and the zip file could not be created.")).body(null); + return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(applicationName, true, ENTITY_NAME, + "internalServerError", "There was an error on the server and the zip file could not be created.")) + .body(null); } /* - * Customized FileInputStream to delete and therefore clean up the returned files + * Customized FileInputStream to delete and therefore clean up the returned + * files */ class NewFileInputStream extends FileInputStream { @@ -172,13 +205,13 @@ public class SearchResource { log.debug("REST request to get Statistics for ExerciseID : {}", projectId); Optional<StatisticsDTO> statisticsDTO = statisticsService.findOneByExerciseID(projectId); - if(statisticsDTO.isPresent()){ + if (statisticsDTO.isPresent()) { StatisticsDTO newStats = statisticsDTO.get(); newStats.setDownloads(newStats.getDownloads() + 1); statisticsService.save(newStats); log.debug("REST increased number of downloads for ExerciseID : {}", projectId); } - if(!statisticsDTO.isPresent()){ + if (!statisticsDTO.isPresent()) { StatisticsDTO newStats = new StatisticsDTO(); newStats.setDownloads(1); newStats.setViews(1); @@ -187,8 +220,8 @@ public class SearchResource { log.debug("Created new statistics entry for exerciseID: {}", projectId); } - return ResponseEntity.ok().contentLength(zipFile.length()).contentType(MediaType.APPLICATION_OCTET_STREAM).header("filename", zipFile.getName()).body(resource); + return ResponseEntity.ok().contentLength(zipFile.length()).contentType(MediaType.APPLICATION_OCTET_STREAM) + .header("filename", zipFile.getName()).body(resource); } - } diff --git a/src/main/java/at/ac/uibk/gitsearch/web/rest/StatisticsResource.java b/src/main/java/at/ac/uibk/gitsearch/web/rest/StatisticsResource.java index c1013a9f41d1a425eea32232d68635a7bd11b584..a6f3dbf65378758c2e5528110283623bcd9c079d 100644 --- a/src/main/java/at/ac/uibk/gitsearch/web/rest/StatisticsResource.java +++ b/src/main/java/at/ac/uibk/gitsearch/web/rest/StatisticsResource.java @@ -27,7 +27,9 @@ import org.springframework.security.access.prepost.PreAuthorize; import javax.validation.Valid; import java.net.URI; import java.net.URISyntaxException; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.StreamSupport; diff --git a/src/main/webapp/app/account/achievements.component.html b/src/main/webapp/app/account/achievements.component.html index 952248d98cbb027ac6094474bdfcc5965e9af2df..eda41b4d7e098ca96527acd7fce73481cd6c0862 100644 --- a/src/main/webapp/app/account/achievements.component.html +++ b/src/main/webapp/app/account/achievements.component.html @@ -4,29 +4,35 @@ <ul class="list-group"> <li class="list-group-item d-flex justify-content-between align-items-center"> More than 500 views - <span class="badge badge-primary badge-pill">14</span> + <span *ngIf="statistics.views <= 500" class="badge badge-primary badge-pill">{{statistics.views}}</span> + <fa-icon icon="check" *ngIf="statistics.views > 500" class="greeniconcolor"></fa-icon> </li> <li class="list-group-item d-flex justify-content-between align-items-center"> More than 100 views - <span class="badge badge-primary badge-pill">2</span> + <span *ngIf="statistics.views <= 100" class="badge badge-primary badge-pill">{{statistics.views}}</span> + <fa-icon icon="check" *ngIf="statistics.views > 100" class="greeniconcolor"></fa-icon> </li> <li class="list-group-item d-flex justify-content-between align-items-center"> More than 10 views - <span class="badge badge-primary badge-pill">1</span> + <span *ngIf="statistics.views <= 10" class="badge badge-primary badge-pill">{{statistics.views}}</span> + <fa-icon icon="check" *ngIf="statistics.views > 10" class="greeniconcolor"></fa-icon> </li> <li class="list-group-item d-flex justify-content-between align-items-center"> More than 500 downloads - <span class="badge badge-primary badge-pill">14</span> + <span *ngIf="statistics.downloads <= 500" class="badge badge-primary badge-pill">{{statistics.downloads}}</span> + <fa-icon icon="check" *ngIf="statistics.downloads > 500" class="greeniconcolor"></fa-icon> </li> <li class="list-group-item d-flex justify-content-between align-items-center"> More than 100 downloads - <fa-icon icon="check" class="greeniconcolor"></fa-icon> + <span *ngIf="statistics.downloads <= 100" class="badge badge-primary badge-pill">{{statistics.downloads}}</span> + <fa-icon icon="check" *ngIf="statistics.downloads > 100" class="greeniconcolor"></fa-icon> </li> <li class="list-group-item d-flex justify-content-between align-items-center"> More than 10 downloads - <fa-icon icon="times-circle" class="rediconcolor"></fa-icon> + <span *ngIf="statistics.downloads <= 10" class="badge badge-primary badge-pill">{{statistics.downloads}}</span> + <fa-icon icon="check" *ngIf="statistics.downloads > 10" class="greeniconcolor"></fa-icon> </li> </ul> - <button (click) = "getTotalNumberOfViews()" class="btn btn-primary"> Get number of views </button> + <!-- <button (click) = "getTotalNumberOfViews()" class="btn btn-primary"> Get number of views </button> --> diff --git a/src/main/webapp/app/account/achievements.component.ts b/src/main/webapp/app/account/achievements.component.ts index 6a4f9e568ee9fee5e94756b8b5039e46741c149f..8996dad85d6a3df314ef047706cde58730b13b2d 100644 --- a/src/main/webapp/app/account/achievements.component.ts +++ b/src/main/webapp/app/account/achievements.component.ts @@ -4,6 +4,7 @@ import { SearchService } from 'app/search/service/search-service'; import { AccountService } from 'app/core/auth/account.service'; import { Account } from 'app/core/user/account.model'; +import { Statistics } from 'app/shared/model/statistics.model'; @Component({ selector: 'jhi-achievements', @@ -13,16 +14,27 @@ import { Account } from 'app/core/user/account.model'; @Injectable({ providedIn: 'root' }) export class AchievementsComponent implements OnInit { account!: Account; + statistics!: Statistics; constructor(private accountService: AccountService, protected searchService: SearchService) {} public getTotalNumberOfViews(): void { // eslint-disable-next-line no-console console.log('I have been called'); - this.searchService.getStatisticsForUser(this.account.login).subscribe( - (data: number) => { + this.searchService.getStatisticsForUser(this.account.firstName + ' ' + this.account.lastName).subscribe( + (data: Statistics) => { + this.statistics = data; // eslint-disable-next-line no-console - console.log('Data: ' + data + ' for account ' + this.account.login); + console.log( + 'Data: Number of Downloads: ' + + data.downloads + + ' Number of views: ' + + data.views + + ' for account ' + + this.account.firstName + + ' ' + + this.account.lastName + ); }, () => alert('Could not load statistics for User') ); @@ -34,5 +46,6 @@ export class AchievementsComponent implements OnInit { this.account = account; } }); + this.getTotalNumberOfViews(); } } diff --git a/src/main/webapp/app/core/icons/font-awesome-icons.ts b/src/main/webapp/app/core/icons/font-awesome-icons.ts index 46617c114e871ef590f14cd64b545c1efe49d70e..f8b7c0afdf5cdc035ba248a95b70c143ff25716f 100644 --- a/src/main/webapp/app/core/icons/font-awesome-icons.ts +++ b/src/main/webapp/app/core/icons/font-awesome-icons.ts @@ -35,6 +35,8 @@ import { faHome, faLanguage, faDownload, + faCheck, + faTimesCircle, // jhipster-needle-add-icon-import } from '@fortawesome/free-solid-svg-icons'; @@ -76,5 +78,7 @@ export const fontAwesomeIcons = [ faLanguage, faBook, faDownload, + faCheck, + faTimesCircle, // jhipster-needle-add-icon-import ]; diff --git a/src/main/webapp/app/search/service/search-service.ts b/src/main/webapp/app/search/service/search-service.ts index 094c73a4872597d198037d7b6ceed1006ee2cc26..6579bd09aa6063881e91d373be341f6cae56611a 100644 --- a/src/main/webapp/app/search/service/search-service.ts +++ b/src/main/webapp/app/search/service/search-service.ts @@ -28,6 +28,10 @@ export class SearchService { return this.http.get<Statistics>(this.resourceSearchUrlStatisticsForExercise + exerciseID); } + getStatisticsForUser(login: string): Observable<Statistics> { + return this.http.get<Statistics>('api/user/' + login + '/statistics/views'); + } + /** * Used to export an exercise as a compressed zip file * The file will contain all the three repositories as well as the exercise text and further metadata