Skip to content

Commit

Permalink
Issue #3737 [API] Add support to export faceted search result with te…
Browse files Browse the repository at this point in the history
…mplate file (#3739)
  • Loading branch information
ekazachkova authored Oct 10, 2024
1 parent 65a1587 commit 18ff88c
Showing 19 changed files with 990 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -838,6 +838,13 @@ public final class MessageConstants {
"error.search.scrolling.parameter.doc.id.missing";
public static final String ERROR_SEARCH_SCROLLING_PARAMETER_DOC_SORT_FIELDS_MISSING =
"error.search.scrolling.parameter.doc.sort.fields.missing";
public static final String ERROR_SEARCH_TEMPLATE_EXPORT_NOT_FOUND = "error.search.template.export.not.found";
public static final String ERROR_SEARCH_TEMPLATE_EXPORT_FILE_NOT_FOUND =
"error.search.template.export.file.not.found";
public static final String ERROR_SEARCH_TEMPLATE_EXPORT_PATH_TO_SAVE_EMPTY =
"error.search.template.export.path.to.save.empty";
public static final String ERROR_SEARCH_TEMPLATE_EXPORT_PATH_TO_SAVE_WRONG_SCHEMA =
"error.search.template.export.path.to.save.wrong.schema";

// Quota
public static final String ERROR_QUOTA_GROUP_EMPTY = "error.quota.group.empty";
Original file line number Diff line number Diff line change
@@ -23,6 +23,8 @@
import com.epam.pipeline.controller.vo.search.FacetedSearchRequest;
import com.epam.pipeline.entity.search.FacetedSearchResult;
import com.epam.pipeline.entity.search.SearchResult;
import com.epam.pipeline.entity.search.SearchTemplateExportInfo;
import com.epam.pipeline.manager.search.SearchExportManager;
import com.epam.pipeline.manager.search.SearchManager;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
@@ -34,6 +36,7 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@@ -46,6 +49,7 @@
public class SearchController extends AbstractRestController {

private final SearchManager searchManager;
private final SearchExportManager searchExportManager;

@RequestMapping(value = "/search", method = RequestMethod.POST)
@ResponseBody
@@ -85,6 +89,35 @@ public void export(@RequestBody final FacetedSearchExportRequest searchExportReq
final String reportFileName = StringUtils.isNotBlank(searchExportRequest.getCsvFileName())
? searchExportRequest.getCsvFileName()
: String.format("facet_report_%s.csv", LocalDateTime.now());
writeFileToResponse(response, searchManager.export(searchExportRequest), reportFileName);
writeFileToResponse(response, searchExportManager.export(searchExportRequest), reportFileName);
}

@PostMapping("/search/facet/export/templates")
@ResponseBody
@ApiOperation(
value = "Export faceted search result in a predefined format.",
notes = "Export faceted search result in a predefined format.",
produces = MediaType.APPLICATION_JSON_VALUE)
@ApiResponses(value = {@ApiResponse(code = HTTP_STATUS_OK, message = API_STATUS_DESCRIPTION)})
public void templateExport(@RequestBody final FacetedSearchRequest searchRequest,
@RequestParam final String templateId,
@RequestParam(required = false) final String fileName,
final HttpServletResponse response) throws IOException {
final String reportFileName = StringUtils.isNotBlank(fileName)
? fileName
: String.format("%s-%s.xls", templateId, LocalDateTime.now());
writeFileToResponse(response, searchExportManager.templateExport(searchRequest, templateId), reportFileName);
}

@PostMapping("/search/facet/export/templates/save")
@ResponseBody
@ApiOperation(
value = "Persists export faceted search result in a predefined format by specified cloud path.",
notes = "Persists export faceted search result in a predefined format by specified cloud path.",
produces = MediaType.APPLICATION_JSON_VALUE)
@ApiResponses(value = {@ApiResponse(code = HTTP_STATUS_OK, message = API_STATUS_DESCRIPTION)})
public Result<SearchTemplateExportInfo> saveTemplateExport(@RequestBody final FacetedSearchRequest searchRequest,
@RequestParam final String templateId) {
return Result.success(searchExportManager.saveTemplateExport(searchRequest, templateId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2024 EPAM Systems, Inc. (https://www.epam.com/)
*
* Licensed 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 com.epam.pipeline.entity.search;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

@Data
@AllArgsConstructor
@Builder
public class SearchTemplateExportColumnData {
/**
* Literal column index: 'A' -> 0 'Z' -> 25 'AA' -> 26
*/
private String columnStringIndex;
/**
* 0-based row start
*/
private int rowStartIndex;
/**
* Resolved column values
*/
private String[] columnValues;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2024 EPAM Systems, Inc. (https://www.epam.com/)
*
* Licensed 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 com.epam.pipeline.entity.search;

import com.epam.pipeline.entity.user.SidImpl;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import java.util.List;
import java.util.Map;

@Data
public class SearchTemplateExportConfig {
@JsonProperty("friendly_name")
private String friendlyName;
@JsonProperty("template_path")
private String templatePath;
private Map<String, List<SearchTemplateExportSheetMapping>> mapping;
private List<SidImpl> permissions;
@JsonProperty("save_to")
private String saveTo;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2024 EPAM Systems, Inc. (https://www.epam.com/)
*
* Licensed 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 com.epam.pipeline.entity.search;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

@Data
@AllArgsConstructor
@Builder
public class SearchTemplateExportInfo {
private String fullPath;
private String storagePath;
private Long storageId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2024 EPAM Systems, Inc. (https://www.epam.com/)
*
* Licensed 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 com.epam.pipeline.entity.search;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

@Data
@AllArgsConstructor
@Builder
public class SearchTemplateExportSheetMapping {
private String column;
/**
* 1-based row start
*/
@JsonProperty("start_row")
private Integer startRow;
/**
* String with placeholders
*/
private String value;
private boolean unique;
/**
* Some items may not contain values for requested placeholders.
* If so and this flag enabled placeholders shall not be resolved.
* CASE: keep_unresolved=true
* {Placeholder1}/{Placeholder2} -> ResolvedValue/{Placeholder2}
* CASE: keep_unresolved=false
* {Placeholder1}/{Placeholder2} -> ResolvedValue/
*/
@JsonProperty("keep_unresolved")
private boolean keepUnresolved;
}
Original file line number Diff line number Diff line change
@@ -30,15 +30,19 @@
import com.epam.pipeline.entity.monitoring.LongPausedRunAction;
import com.epam.pipeline.entity.monitoring.NetworkConsumingRunAction;
import com.epam.pipeline.entity.preference.Preference;
import com.epam.pipeline.entity.search.SearchTemplateExportConfig;
import com.epam.pipeline.entity.search.SearchTemplateExportSheetMapping;
import com.epam.pipeline.security.ExternalServiceEndpoint;
import com.epam.pipeline.utils.PipelineStringUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;

import java.io.IOException;
import java.net.MalformedURLException;
@@ -47,9 +51,11 @@
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiPredicate;

@@ -320,6 +326,30 @@ public static BiPredicate<String, Map<String, Preference>> isNullOrValidEnum(fin
return true;
};

public static final BiPredicate<String, Map<String, Preference>> isValidSearchExportTemplateConfig =
isNullOrValidJson(new TypeReference<Map<String, SearchTemplateExportConfig>>() {})
.and((pref, dependencies) -> {
final Map<String, SearchTemplateExportConfig> configurations = JsonMapper.parseData(pref,
new TypeReference<Map<String, SearchTemplateExportConfig>>() {});
configurations.forEach(PreferenceValidators::validateSearchExportTemplate);
return true;
});

private static void validateSearchExportTemplate(final String templateId,
final SearchTemplateExportConfig configuration) {
final String messagePattern = "Failed to persist template '%s': %s.";
Assert.state(StringUtils.isNotBlank(configuration.getTemplatePath()),
String.format(messagePattern, templateId, "'template_path' field is required."));
Assert.state(MapUtils.isNotEmpty(configuration.getMapping()),
String.format(messagePattern, templateId, "No mappings provided."));
Assert.state(configuration.getMapping().values().stream()
.flatMap(Collection::stream)
.map(SearchTemplateExportSheetMapping::getStartRow)
.filter(Objects::nonNull)
.noneMatch(value -> value < 1),
String.format(messagePattern, templateId, "Row start value shall be greater that 0."));
}

private PreferenceValidators() {
// No-op
}
Original file line number Diff line number Diff line change
@@ -75,6 +75,7 @@
import com.epam.pipeline.manager.preference.AbstractSystemPreference.ObjectPreference;
import com.epam.pipeline.manager.preference.AbstractSystemPreference.StringPreference;
import com.epam.pipeline.manager.preference.AbstractSystemPreference.EnumPreference;
import com.epam.pipeline.entity.search.SearchTemplateExportConfig;
import com.epam.pipeline.security.ExternalServiceEndpoint;
import com.epam.pipeline.utils.CommonUtils;
import com.fasterxml.jackson.core.type.TypeReference;
@@ -112,6 +113,7 @@
import static com.epam.pipeline.manager.preference.PreferenceValidators.isValidEnum;
import static com.epam.pipeline.manager.preference.PreferenceValidators.isValidInstanceTags;
import static com.epam.pipeline.manager.preference.PreferenceValidators.isValidMapOfLaunchCommands;
import static com.epam.pipeline.manager.preference.PreferenceValidators.isValidSearchExportTemplateConfig;
import static com.epam.pipeline.manager.preference.PreferenceValidators.pass;

/**
@@ -1324,6 +1326,10 @@ public class SystemPreferences {
"search.elastic.hide.deleted", true, SEARCH_GROUP, pass);
public static final IntPreference SEARCH_EXPORT_PAGE_SIZE = new IntPreference(
"search.export.page.size", 5000, SEARCH_GROUP, isGreaterThan(0));
public static final ObjectPreference<Map<String, SearchTemplateExportConfig>> SEARCH_EXPORT_TEMPLATE_MAPPING =
new ObjectPreference<>("search.export.template.mapping", Collections.emptyMap(),
new TypeReference<Map<String, SearchTemplateExportConfig>>() {}, SEARCH_GROUP,
isValidSearchExportTemplateConfig);

public static final ObjectPreference<List<StorageFileSearchMask>> FILE_SEARCH_MASK_RULES = new ObjectPreference<>(
"search.storage.elements.settings",
Loading

0 comments on commit 18ff88c

Please sign in to comment.