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
Show file tree
Hide file tree
Showing 19 changed files with 990 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

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

private final SearchManager searchManager;
private final SearchExportManager searchExportManager;

@RequestMapping(value = "/search", method = RequestMethod.POST)
@ResponseBody
Expand Down Expand Up @@ -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
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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",
Expand Down
Loading

0 comments on commit 18ff88c

Please sign in to comment.