From 7b07cb6f7fe06d57048246cffcc1f30a8713e5eb Mon Sep 17 00:00:00 2001 From: Nathan Dickerson Date: Fri, 28 Sep 2018 15:00:17 -0500 Subject: [PATCH] feat(Associations): Enabled wildcard searching for associations (#269) --- dataloader.properties | 10 ++ .../bullhorn/dataloader/enums/Property.java | 3 +- .../com/bullhorn/dataloader/rest/RestApi.java | 7 +- .../dataloader/task/AbstractTask.java | 22 +++- .../bullhorn/dataloader/task/LoadTask.java | 5 +- .../dataloader/util/PropertyFileUtil.java | 7 ++ .../integration/IntegrationTest.java | 13 +- .../dataloader/task/LoadTaskTest.java | 112 ++++++++++++++++++ .../CandidateAssociationsOver500.csv | 2 +- .../integrationTest.properties | 1 + .../CandidateWildcardMatching.csv | 4 + .../wildcardMatching/NoteWildcardMatching.csv | 2 + 12 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 src/test/resources/integrationTest/wildcardMatching/CandidateWildcardMatching.csv create mode 100644 src/test/resources/integrationTest/wildcardMatching/NoteWildcardMatching.csv diff --git a/dataloader.properties b/dataloader.properties index e0f04658..80fa34a9 100755 --- a/dataloader.properties +++ b/dataloader.properties @@ -162,6 +162,15 @@ clientUserIDColumn=clientContact.id # processEmptyAssociations -- If set to true then all To-Many association cells that are empty will remove any # existing associations. Default value is false, which will ignore the empty cells. # +# wildcardMatching -- If set to true then wildcards (*) can be used within cell to search for multiple associations +# without having to list all of them explicitly. For example, "Java*" can be used to match +# skill entries like "Java" and "Javascript". +# For Search entities, the literal matching is removed, and the full lucene syntax is supported: +# See all available search options here: https://lucene.apache.org/core/2_9_4/queryparsersyntax.html +# For Query entities, (*) is is replaced with (%) and the full MSSQL 'like' syntax is supported. +# See all available query options here: +# https://docs.microsoft.com/en-us/sql/t-sql/language-elements/like-transact-sql?view=sql-server-2017#arguments +# # singleByteEncoding -- If set to true then CSV files will be read in using the ISO-8859-1 (single-byte) encoding. # If set to false then CVS files will be read in using the UTF-8 (multi-byte) encoding. # The single byte encoding covers only latin characters and some accented characters while @@ -171,6 +180,7 @@ clientUserIDColumn=clientContact.id listDelimiter=; dateFormat=MM/dd/yy HH:mm processEmptyAssociations=false +wildcardMatching=true singleByteEncoding=false # --------------------------------------------------------------------------------------------------------------------- diff --git a/src/main/java/com/bullhorn/dataloader/enums/Property.java b/src/main/java/com/bullhorn/dataloader/enums/Property.java index b0c3779d..58da26f8 100644 --- a/src/main/java/com/bullhorn/dataloader/enums/Property.java +++ b/src/main/java/com/bullhorn/dataloader/enums/Property.java @@ -23,7 +23,8 @@ public enum Property { TOKEN_URL("tokenUrl"), USERNAME("username"), VERBOSE("verbose"), - WAIT_SECONDS_BETWEEN_FILES_IN_DIRECTORY("waitSecondsBetweenFilesInDirectory"); + WAIT_SECONDS_BETWEEN_FILES_IN_DIRECTORY("waitSecondsBetweenFilesInDirectory"), + WILDCARD_MATCHING("wildcardMatching"); private final String propertyName; diff --git a/src/main/java/com/bullhorn/dataloader/rest/RestApi.java b/src/main/java/com/bullhorn/dataloader/rest/RestApi.java index 95c478d4..d588f4c0 100644 --- a/src/main/java/com/bullhorn/dataloader/rest/RestApi.java +++ b/src/main/java/com/bullhorn/dataloader/rest/RestApi.java @@ -109,6 +109,7 @@ public List queryForList(Class type, return list; } + // TODO: Remove this now that the regular queryForList is recursive List queryForAllRecordsList(Class type, String where, Set fieldSet, @@ -162,7 +163,8 @@ public List getAllAss public C associateWithEntity( Class type, Integer entityId, AssociationField associationName, Set associationIds) { - printUtil.log(Level.DEBUG, "Associate(" + type.getSimpleName() + "): #" + entityId + " - " + associationName.getAssociationFieldName()); + printUtil.log(Level.DEBUG, "Associate(" + type.getSimpleName() + "): #" + entityId + " - " + associationName.getAssociationFieldName() + + " (" + associationIds.size() + " associations)"); C crudResponse = bullhornData.associateWithEntity(type, entityId, associationName, associationIds); restApiExtension.checkForRestSdkErrorMessages(crudResponse); return crudResponse; @@ -171,7 +173,8 @@ public C associateWithEnti public C disassociateWithEntity( Class type, Integer entityId, AssociationField associationName, Set associationIds) { - printUtil.log(Level.DEBUG, "Disassociate(" + type.getSimpleName() + "): #" + entityId + " - " + associationName.getAssociationFieldName()); + printUtil.log(Level.DEBUG, "Disassociate(" + type.getSimpleName() + "): #" + entityId + " - " + associationName.getAssociationFieldName() + + " (" + associationIds.size() + " associations)"); C crudResponse = bullhornData.disassociateWithEntity(type, entityId, associationName, associationIds); restApiExtension.checkForRestSdkErrorMessages(crudResponse); return crudResponse; diff --git a/src/main/java/com/bullhorn/dataloader/task/AbstractTask.java b/src/main/java/com/bullhorn/dataloader/task/AbstractTask.java index 94c5836a..b01f7be9 100644 --- a/src/main/java/com/bullhorn/dataloader/task/AbstractTask.java +++ b/src/main/java/com/bullhorn/dataloader/task/AbstractTask.java @@ -162,28 +162,46 @@ private List queryForEntity(List entityExistFi Sets.newHashSet("id"), ParamFactory.queryParams()); } + // TODO: Move to utility String getQueryStatement(String field, String value, Class fieldType, EntityInfo fieldEntityInfo) { + // Fix for the Note entity doing it's own thing when it comes to the 'id' field if (fieldEntityInfo == EntityInfo.NOTE && field.equals(StringConsts.ID)) { field = StringConsts.NOTE_ID; } if (Integer.class.equals(fieldType) || BigDecimal.class.equals(fieldType) || Boolean.class.equals(fieldType)) { return field + ":" + value; - } else if (DateTime.class.equals(fieldType) || String.class.equals(fieldType)) { + } else if (DateTime.class.equals(fieldType)) { return field + ":\"" + value + "\""; + } else if (String.class.equals(fieldType)) { + if (propertyFileUtil.getWildcardMatching()) { + return field + ": " + value; // Flexible match - non quoted string (falls back to whatever text exists in the cell) + } else { + return field + ":\"" + value + "\""; // Literal match - equals quoted string + } } else { throw new RestApiException("Failed to create lucene search string for: '" + field + "' with unsupported field type: " + fieldType); } } + // TODO: Move to utility String getWhereStatement(String field, String value, Class fieldType) { if (Integer.class.equals(fieldType) || BigDecimal.class.equals(fieldType) || Double.class.equals(fieldType)) { return field + "=" + value; } else if (Boolean.class.equals(fieldType)) { return field + "=" + getBooleanWhereStatement(value); } else if (String.class.equals(fieldType)) { - return field + "='" + value + "'"; + if (propertyFileUtil.getWildcardMatching()) { + // Not all string fields in query entities support the like syntax. Only use it if there is a non-escaped asterisk. + if (value.matches(".*[^\\\\][*].*")) { + return field + " like '" + value.replaceAll("[*]", "%") + "'"; // Flexible match - using like syntax + } else { + return field + "='" + value.replaceAll("[\\\\][*]", "*") + "'"; // Literal match after unescaping asterisks + } + } else { + return field + "='" + value + "'"; // Literal match - equals quoted string + } } else if (DateTime.class.equals(fieldType)) { // TODO: This needs to be of the format: `dateOfBirth:[20170808 TO 20170808235959]` for dates // Format: [yyyyMMdd TO yyyyMMddHHmmss] - a date range of one day diff --git a/src/main/java/com/bullhorn/dataloader/task/LoadTask.java b/src/main/java/com/bullhorn/dataloader/task/LoadTask.java index 90ba5479..c9d1afa9 100644 --- a/src/main/java/com/bullhorn/dataloader/task/LoadTask.java +++ b/src/main/java/com/bullhorn/dataloader/task/LoadTask.java @@ -259,7 +259,7 @@ private List findAssociations(Field field) throws InvocationTargetException, if (!field.getStringValue().isEmpty()) { Set values = Sets.newHashSet(field.getStringValue().split(propertyFileUtil.getListDelimiter())); associations = doFindAssociations(field); - if (associations.size() != values.size()) { + if (!propertyFileUtil.getWildcardMatching() && associations.size() != values.size()) { Set existingAssociationValues = getFieldValueSet(field, associations); if (associations.size() > values.size()) { String duplicates = existingAssociationValues.stream().map(n -> "\t" + n) @@ -280,8 +280,7 @@ private List findAssociations(Field field) throws InvocationTargetException, /** * Makes the lookup call to check that all associated values are present, and there are no duplicates. This will - * work with up to 500 associated records, such as candidates or businessSectors. It will perform the lookup using - * the field given after the period, like: 'businessSector.name' or 'candidate.id' + * perform the lookup using the field given after the period, like 'name' from 'businessSector.name'. * * @param field the To-Many association field to lookup records for */ diff --git a/src/main/java/com/bullhorn/dataloader/util/PropertyFileUtil.java b/src/main/java/com/bullhorn/dataloader/util/PropertyFileUtil.java index 1bb4b789..7f8700cb 100644 --- a/src/main/java/com/bullhorn/dataloader/util/PropertyFileUtil.java +++ b/src/main/java/com/bullhorn/dataloader/util/PropertyFileUtil.java @@ -45,6 +45,7 @@ public class PropertyFileUtil { private Integer numThreads; private Integer waitSecondsBetweenFilesInDirectory; private Boolean processEmptyAssociations; + private Boolean wildcardMatching; private Boolean singleByteEncoding; private Boolean resultsFileEnabled; private String resultsFilePath; @@ -191,6 +192,10 @@ public Boolean getProcessEmptyAssociations() { return processEmptyAssociations; } + public Boolean getWildcardMatching() { + return wildcardMatching; + } + public Boolean getSingleByteEncoding() { return singleByteEncoding; } @@ -342,6 +347,8 @@ private void processProperties(Properties properties, PropertyValidationUtil pro Property.WAIT_SECONDS_BETWEEN_FILES_IN_DIRECTORY.getName())); processEmptyAssociations = propertyValidationUtil.validateBooleanProperty( Boolean.valueOf(properties.getProperty(Property.PROCESS_EMPTY_ASSOCIATIONS.getName()))); + wildcardMatching = propertyValidationUtil.validateBooleanProperty( + Boolean.valueOf(properties.getProperty(Property.WILDCARD_MATCHING.getName()))); singleByteEncoding = propertyValidationUtil.validateBooleanProperty( Boolean.valueOf(properties.getProperty(Property.SINGLE_BYTE_ENCODING.getName()))); resultsFileEnabled = propertyValidationUtil.validateBooleanProperty( diff --git a/src/test/java/com/bullhorn/dataloader/integration/IntegrationTest.java b/src/test/java/com/bullhorn/dataloader/integration/IntegrationTest.java index f0981079..a1f7ce12 100644 --- a/src/test/java/com/bullhorn/dataloader/integration/IntegrationTest.java +++ b/src/test/java/com/bullhorn/dataloader/integration/IntegrationTest.java @@ -64,10 +64,10 @@ public void testIntegration() throws IOException { // Test using more than 100,000 characters in a field insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("longFields"), false); - // Test using more than 500 associations in a To-Many field - // TODO: This is broken right now - can't use 1000 OR statements in where clause - // TODO: This will be fixed when wildcard searching for multiple records is enabled - //insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("associationsOver500"), false); + // Test using more than 500 associations in a To-Many field - requires that wildcard matching is enabled + System.setProperty("wildcardMatching", "true"); + insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("associationsOver500"), false); + System.setProperty("wildcardMatching", "false"); // Test for ignoring soft deleted entities insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("softDeletes"), true); @@ -78,6 +78,11 @@ public void testIntegration() throws IOException { // Test that the byte order mark is ignored when it's present in the input file as the first (hidden) character insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("byteOrderMark"), false); + // Test for wildcard associations for candidates in a note + System.setProperty("wildcardMatching", "true"); + insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("wildcardMatching"), false); + System.setProperty("wildcardMatching", "false"); + // Run a test for processing empty association fields (with the setting turned on) System.setProperty("processEmptyAssociations", "true"); insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("processEmptyFields"), false); diff --git a/src/test/java/com/bullhorn/dataloader/task/LoadTaskTest.java b/src/test/java/com/bullhorn/dataloader/task/LoadTaskTest.java index 6ccddbc5..38c268d7 100644 --- a/src/test/java/com/bullhorn/dataloader/task/LoadTaskTest.java +++ b/src/test/java/com/bullhorn/dataloader/task/LoadTaskTest.java @@ -1128,6 +1128,25 @@ public void testRunLuceneSearchStatementAllFieldTypes() throws Exception { TestUtils.verifyActionTotals(actionTotalsMock, Result.Action.INSERT, 1); } + @Test + public void testRunLuceneSearchStatementWildcardMatching() throws Exception { + String[] headerArray = new String[]{"action", "candidates.companyName", "comments"}; + String[] valueArray = new String[]{"Email", "Boeing*", "Candidates generated from the companyName field"}; + Row row = TestUtils.createRow(headerArray, valueArray); + when(propertyFileUtilMock.getWildcardMatching()).thenReturn(true); + when(restApiMock.insertEntity(any())).thenReturn(TestUtils.getResponse(ChangeType.INSERT, 90)); + + LoadTask task = new LoadTask(EntityInfo.NOTE, row, csvFileWriterMock, + propertyFileUtilMock, restApiMock, printUtilMock, actionTotalsMock, completeUtilMock); + task.run(); + + String expectedQuery = "(companyName: Boeing*) AND isDeleted:0"; + verify(restApiMock, times(1)).searchForList(eq(Candidate.class), eq(expectedQuery), any(), any()); + Result expectedResult = new Result(Result.Status.SUCCESS, Result.Action.INSERT, 90, ""); + verify(csvFileWriterMock, times(1)).writeRow(any(), eq(expectedResult)); + TestUtils.verifyActionTotals(actionTotalsMock, Result.Action.INSERT, 1); + } + @Test public void testRunLuceneSearchStatementNullFieldDefaults() throws Exception { String[] headerArray = new String[]{"dayRate", "isLockedOut", "customInt1", "customFloat1", "customDate1"}; @@ -1228,6 +1247,99 @@ public void testRunDatabaseQueryWhereStatementNullFieldDefaults() throws Excepti TestUtils.verifyActionTotals(actionTotalsMock, Result.Action.INSERT, 1); } + @Test + public void testRunDatabaseQueryWhereStatementToManyWildcardMatching() throws Exception { + String[] headerArray = new String[]{"firstName", "lastName", "primarySkills.name"}; + String[] valueArray = new String[]{"Stephanie", "Scribbles", "Sales*;Market*;IT"}; + Row row = TestUtils.createRow(headerArray, valueArray); + when(propertyFileUtilMock.getWildcardMatching()).thenReturn(true); + when(restApiMock.queryForList(eq(Skill.class), any(), any(), any())).thenReturn(TestUtils.getList + (Skill.class, 1, 2, 3, 4, 5, 6)); + when(restApiMock.getAllAssociationsList(eq(Candidate.class), any(), + eq(CandidateAssociations.getInstance().primarySkills()), any(), any())) + .thenReturn(TestUtils.getList(Skill.class, 1, 2)); + when(restApiMock.insertEntity(any())).thenReturn(TestUtils.getResponse(ChangeType.INSERT, 100)); + + LoadTask task = new LoadTask(EntityInfo.CANDIDATE, row, csvFileWriterMock, + propertyFileUtilMock, restApiMock, printUtilMock, actionTotalsMock, completeUtilMock); + task.run(); + + String expectedQuery = "name like 'Sales%' OR name like 'Market%' OR name='IT'"; + verify(restApiMock, times(1)).queryForList(eq(Skill.class), eq(expectedQuery), + any(), any()); + verify(restApiMock, times(1)).associateWithEntity(eq(Candidate.class), + eq(100), eq(CandidateAssociations.getInstance().primarySkills()), eq(Sets.newHashSet(3, 4, 5, 6))); + Result expectedResult = new Result(Result.Status.SUCCESS, Result.Action.INSERT, 100, ""); + verify(csvFileWriterMock, times(1)).writeRow(any(), eq(expectedResult)); + TestUtils.verifyActionTotals(actionTotalsMock, Result.Action.INSERT, 1); + } + + @Test + public void testRunDatabaseQueryWhereStatementToOneWildcardMatching() throws Exception { + String[] headerArray = new String[]{"firstName", "lastName", "category.name"}; + String[] valueArray = new String[]{"Stephanie", "Scribbles", "Sales*"}; + Row row = TestUtils.createRow(headerArray, valueArray); + when(propertyFileUtilMock.getWildcardMatching()).thenReturn(true); + when(restApiMock.insertEntity(any())).thenReturn(TestUtils.getResponse(ChangeType.INSERT, 100)); + when(restApiMock.queryForList(eq(Category.class), any(), any(), any())).thenReturn(TestUtils.getList + (Category.class, 1)); + + LoadTask task = new LoadTask(EntityInfo.CANDIDATE, row, csvFileWriterMock, + propertyFileUtilMock, restApiMock, printUtilMock, actionTotalsMock, completeUtilMock); + task.run(); + + String expectedQuery = "name like 'Sales%'"; + verify(restApiMock, times(1)).queryForList(eq(Category.class), eq(expectedQuery), + any(), any()); + Result expectedResult = new Result(Result.Status.SUCCESS, Result.Action.INSERT, 100, ""); + verify(csvFileWriterMock, times(1)).writeRow(any(), eq(expectedResult)); + TestUtils.verifyActionTotals(actionTotalsMock, Result.Action.INSERT, 1); + } + + @Test + public void testRunDatabaseQueryWhereStatementLimitWildcardMatching() throws Exception { + String[] headerArray = new String[]{"firstName", "lastName", "owner.name"}; + String[] valueArray = new String[]{"Stephanie", "Scribbles", "Bob Smiley"}; + Row row = TestUtils.createRow(headerArray, valueArray); + when(propertyFileUtilMock.getWildcardMatching()).thenReturn(true); + when(restApiMock.insertEntity(any())).thenReturn(TestUtils.getResponse(ChangeType.INSERT, 80)); + when(restApiMock.queryForList(eq(CorporateUser.class), any(), any(), any())).thenReturn(TestUtils.getList + (CorporateUser.class, 1)); + + LoadTask task = new LoadTask(EntityInfo.CANDIDATE, row, csvFileWriterMock, + propertyFileUtilMock, restApiMock, printUtilMock, actionTotalsMock, completeUtilMock); + task.run(); + + String expectedQuery = "name='Bob Smiley'"; + verify(restApiMock, times(1)).queryForList(eq(CorporateUser.class), eq(expectedQuery), + any(), any()); + Result expectedResult = new Result(Result.Status.SUCCESS, Result.Action.INSERT, 80, ""); + verify(csvFileWriterMock, times(1)).writeRow(any(), eq(expectedResult)); + TestUtils.verifyActionTotals(actionTotalsMock, Result.Action.INSERT, 1); + } + + @Test + public void testRunDatabaseQueryWhereStatementLimitWildcardMatchingWhenEscaped() throws Exception { + String[] headerArray = new String[]{"firstName", "lastName", "owner.name"}; + String[] valueArray = new String[]{"Stephanie", "Scribbles", "\\*Bob Smiley\\*"}; + Row row = TestUtils.createRow(headerArray, valueArray); + when(propertyFileUtilMock.getWildcardMatching()).thenReturn(true); + when(restApiMock.insertEntity(any())).thenReturn(TestUtils.getResponse(ChangeType.INSERT, 80)); + when(restApiMock.queryForList(eq(CorporateUser.class), any(), any(), any())).thenReturn(TestUtils.getList + (CorporateUser.class, 1)); + + LoadTask task = new LoadTask(EntityInfo.CANDIDATE, row, csvFileWriterMock, + propertyFileUtilMock, restApiMock, printUtilMock, actionTotalsMock, completeUtilMock); + task.run(); + + String expectedQuery = "name='*Bob Smiley*'"; + verify(restApiMock, times(1)).queryForList(eq(CorporateUser.class), eq(expectedQuery), + any(), any()); + Result expectedResult = new Result(Result.Status.SUCCESS, Result.Action.INSERT, 80, ""); + verify(csvFileWriterMock, times(1)).writeRow(any(), eq(expectedResult)); + TestUtils.verifyActionTotals(actionTotalsMock, Result.Action.INSERT, 1); + } + @Test public void testRunDatabaseQueryWhereStatementInvalidValues() throws Exception { Row row = TestUtils.createRow("salary1,isLastJob,customInt1,companyName,startDate", diff --git a/src/test/resources/integrationTest/associationsOver500/CandidateAssociationsOver500.csv b/src/test/resources/integrationTest/associationsOver500/CandidateAssociationsOver500.csv index d0c9b487..b1d76e56 100644 --- a/src/test/resources/integrationTest/associationsOver500/CandidateAssociationsOver500.csv +++ b/src/test/resources/integrationTest/associationsOver500/CandidateAssociationsOver500.csv @@ -1,2 +1,2 @@ externalID ,firstName ,lastName ,name ,primarySkills.name -candidateAssociations-ext-1,DataLoader,AssociationsOver500Test,DataLoader AssociationsOver500Test,Skill_1;Skill_2;Skill_3;Skill_4;Skill_5;Skill_6;Skill_7;Skill_8;Skill_9;Skill_10;Skill_11;Skill_12;Skill_13;Skill_14;Skill_15;Skill_16;Skill_17;Skill_18;Skill_19;Skill_20;Skill_21;Skill_22;Skill_23;Skill_24;Skill_25;Skill_26;Skill_27;Skill_28;Skill_29;Skill_30;Skill_31;Skill_32;Skill_33;Skill_34;Skill_35;Skill_36;Skill_37;Skill_38;Skill_39;Skill_40;Skill_41;Skill_42;Skill_43;Skill_44;Skill_45;Skill_46;Skill_47;Skill_48;Skill_49;Skill_50;Skill_51;Skill_52;Skill_53;Skill_54;Skill_55;Skill_56;Skill_57;Skill_58;Skill_59;Skill_60;Skill_61;Skill_62;Skill_63;Skill_64;Skill_65;Skill_66;Skill_67;Skill_68;Skill_69;Skill_70;Skill_71;Skill_72;Skill_73;Skill_74;Skill_75;Skill_76;Skill_77;Skill_78;Skill_79;Skill_80;Skill_81;Skill_82;Skill_83;Skill_84;Skill_85;Skill_86;Skill_87;Skill_88;Skill_89;Skill_90;Skill_91;Skill_92;Skill_93;Skill_94;Skill_95;Skill_96;Skill_97;Skill_98;Skill_99;Skill_100;Skill_101;Skill_102;Skill_103;Skill_104;Skill_105;Skill_106;Skill_107;Skill_108;Skill_109;Skill_110;Skill_111;Skill_112;Skill_113;Skill_114;Skill_115;Skill_116;Skill_117;Skill_118;Skill_119;Skill_120;Skill_121;Skill_122;Skill_123;Skill_124;Skill_125;Skill_126;Skill_127;Skill_128;Skill_129;Skill_130;Skill_131;Skill_132;Skill_133;Skill_134;Skill_135;Skill_136;Skill_137;Skill_138;Skill_139;Skill_140;Skill_141;Skill_142;Skill_143;Skill_144;Skill_145;Skill_146;Skill_147;Skill_148;Skill_149;Skill_150;Skill_151;Skill_152;Skill_153;Skill_154;Skill_155;Skill_156;Skill_157;Skill_158;Skill_159;Skill_160;Skill_161;Skill_162;Skill_163;Skill_164;Skill_165;Skill_166;Skill_167;Skill_168;Skill_169;Skill_170;Skill_171;Skill_172;Skill_173;Skill_174;Skill_175;Skill_176;Skill_177;Skill_178;Skill_179;Skill_180;Skill_181;Skill_182;Skill_183;Skill_184;Skill_185;Skill_186;Skill_187;Skill_188;Skill_189;Skill_190;Skill_191;Skill_192;Skill_193;Skill_194;Skill_195;Skill_196;Skill_197;Skill_198;Skill_199;Skill_200;Skill_201;Skill_202;Skill_203;Skill_204;Skill_205;Skill_206;Skill_207;Skill_208;Skill_209;Skill_210;Skill_211;Skill_212;Skill_213;Skill_214;Skill_215;Skill_216;Skill_217;Skill_218;Skill_219;Skill_220;Skill_221;Skill_222;Skill_223;Skill_224;Skill_225;Skill_226;Skill_227;Skill_228;Skill_229;Skill_230;Skill_231;Skill_232;Skill_233;Skill_234;Skill_235;Skill_236;Skill_237;Skill_238;Skill_239;Skill_240;Skill_241;Skill_242;Skill_243;Skill_244;Skill_245;Skill_246;Skill_247;Skill_248;Skill_249;Skill_250;Skill_251;Skill_252;Skill_253;Skill_254;Skill_255;Skill_256;Skill_257;Skill_258;Skill_259;Skill_260;Skill_261;Skill_262;Skill_263;Skill_264;Skill_265;Skill_266;Skill_267;Skill_268;Skill_269;Skill_270;Skill_271;Skill_272;Skill_273;Skill_274;Skill_275;Skill_276;Skill_277;Skill_278;Skill_279;Skill_280;Skill_281;Skill_282;Skill_283;Skill_284;Skill_285;Skill_286;Skill_287;Skill_288;Skill_289;Skill_290;Skill_291;Skill_292;Skill_293;Skill_294;Skill_295;Skill_296;Skill_297;Skill_298;Skill_299;Skill_300;Skill_301;Skill_302;Skill_303;Skill_304;Skill_305;Skill_306;Skill_307;Skill_308;Skill_309;Skill_310;Skill_311;Skill_312;Skill_313;Skill_314;Skill_315;Skill_316;Skill_317;Skill_318;Skill_319;Skill_320;Skill_321;Skill_322;Skill_323;Skill_324;Skill_325;Skill_326;Skill_327;Skill_328;Skill_329;Skill_330;Skill_331;Skill_332;Skill_333;Skill_334;Skill_335;Skill_336;Skill_337;Skill_338;Skill_339;Skill_340;Skill_341;Skill_342;Skill_343;Skill_344;Skill_345;Skill_346;Skill_347;Skill_348;Skill_349;Skill_350;Skill_351;Skill_352;Skill_353;Skill_354;Skill_355;Skill_356;Skill_357;Skill_358;Skill_359;Skill_360;Skill_361;Skill_362;Skill_363;Skill_364;Skill_365;Skill_366;Skill_367;Skill_368;Skill_369;Skill_370;Skill_371;Skill_372;Skill_373;Skill_374;Skill_375;Skill_376;Skill_377;Skill_378;Skill_379;Skill_380;Skill_381;Skill_382;Skill_383;Skill_384;Skill_385;Skill_386;Skill_387;Skill_388;Skill_389;Skill_390;Skill_391;Skill_392;Skill_393;Skill_394;Skill_395;Skill_396;Skill_397;Skill_398;Skill_399;Skill_400;Skill_401;Skill_402;Skill_403;Skill_404;Skill_405;Skill_406;Skill_407;Skill_408;Skill_409;Skill_410;Skill_411;Skill_412;Skill_413;Skill_414;Skill_415;Skill_416;Skill_417;Skill_418;Skill_419;Skill_420;Skill_421;Skill_422;Skill_423;Skill_424;Skill_425;Skill_426;Skill_427;Skill_428;Skill_429;Skill_430;Skill_431;Skill_432;Skill_433;Skill_434;Skill_435;Skill_436;Skill_437;Skill_438;Skill_439;Skill_440;Skill_441;Skill_442;Skill_443;Skill_444;Skill_445;Skill_446;Skill_447;Skill_448;Skill_449;Skill_450;Skill_451;Skill_452;Skill_453;Skill_454;Skill_455;Skill_456;Skill_457;Skill_458;Skill_459;Skill_460;Skill_461;Skill_462;Skill_463;Skill_464;Skill_465;Skill_466;Skill_467;Skill_468;Skill_469;Skill_470;Skill_471;Skill_472;Skill_473;Skill_474;Skill_475;Skill_476;Skill_477;Skill_478;Skill_479;Skill_480;Skill_481;Skill_482;Skill_483;Skill_484;Skill_485;Skill_486;Skill_487;Skill_488;Skill_489;Skill_490;Skill_491;Skill_492;Skill_493;Skill_494;Skill_495;Skill_496;Skill_497;Skill_498;Skill_499;Skill_500;Skill_501;Skill_502;Skill_503;Skill_504;Skill_505;Skill_506;Skill_507;Skill_508;Skill_509;Skill_510;Skill_511;Skill_512;Skill_513;Skill_514;Skill_515;Skill_516;Skill_517;Skill_518;Skill_519;Skill_520;Skill_521;Skill_522;Skill_523;Skill_524;Skill_525;Skill_526;Skill_527;Skill_528;Skill_529;Skill_530;Skill_531;Skill_532;Skill_533;Skill_534;Skill_535;Skill_536;Skill_537;Skill_538;Skill_539;Skill_540;Skill_541;Skill_542;Skill_543;Skill_544;Skill_545;Skill_546;Skill_547;Skill_548;Skill_549;Skill_550;Skill_551;Skill_552;Skill_553;Skill_554;Skill_555;Skill_556;Skill_557;Skill_558;Skill_559;Skill_560;Skill_561;Skill_562;Skill_563;Skill_564;Skill_565;Skill_566;Skill_567;Skill_568;Skill_569;Skill_570;Skill_571;Skill_572;Skill_573;Skill_574;Skill_575;Skill_576;Skill_577;Skill_578;Skill_579;Skill_580;Skill_581;Skill_582;Skill_583;Skill_584;Skill_585;Skill_586;Skill_587;Skill_588;Skill_589;Skill_590;Skill_591;Skill_592;Skill_593;Skill_594;Skill_595;Skill_596;Skill_597;Skill_598;Skill_599;Skill_600;Skill_601;Skill_602;Skill_603;Skill_604;Skill_605;Skill_606;Skill_607;Skill_608;Skill_609;Skill_610;Skill_611;Skill_612;Skill_613;Skill_614;Skill_615;Skill_616;Skill_617;Skill_618;Skill_619;Skill_620;Skill_621;Skill_622;Skill_623;Skill_624;Skill_625;Skill_626;Skill_627;Skill_628;Skill_629;Skill_630;Skill_631;Skill_632;Skill_633;Skill_634;Skill_635;Skill_636;Skill_637;Skill_638;Skill_639;Skill_640;Skill_641;Skill_642;Skill_643;Skill_644;Skill_645;Skill_646;Skill_647;Skill_648;Skill_649;Skill_650;Skill_651;Skill_652;Skill_653;Skill_654;Skill_655;Skill_656;Skill_657;Skill_658;Skill_659;Skill_660;Skill_661;Skill_662;Skill_663;Skill_664;Skill_665;Skill_666;Skill_667;Skill_668;Skill_669;Skill_670;Skill_671;Skill_672;Skill_673;Skill_674;Skill_675;Skill_676;Skill_677;Skill_678;Skill_679;Skill_680;Skill_681;Skill_682;Skill_683;Skill_684;Skill_685;Skill_686;Skill_687;Skill_688;Skill_689;Skill_690;Skill_691;Skill_692;Skill_693;Skill_694;Skill_695;Skill_696;Skill_697;Skill_698;Skill_699;Skill_700;Skill_701;Skill_702;Skill_703;Skill_704;Skill_705;Skill_706;Skill_707;Skill_708;Skill_709;Skill_710;Skill_711;Skill_712;Skill_713;Skill_714;Skill_715;Skill_716;Skill_717;Skill_718;Skill_719;Skill_720;Skill_721;Skill_722;Skill_723;Skill_724;Skill_725;Skill_726;Skill_727;Skill_728;Skill_729;Skill_730;Skill_731;Skill_732;Skill_733;Skill_734;Skill_735;Skill_736;Skill_737;Skill_738;Skill_739;Skill_740;Skill_741;Skill_742;Skill_743;Skill_744;Skill_745;Skill_746;Skill_747;Skill_748;Skill_749;Skill_750;Skill_751;Skill_752;Skill_753;Skill_754;Skill_755;Skill_756;Skill_757;Skill_758;Skill_759;Skill_760;Skill_761;Skill_762;Skill_763;Skill_764;Skill_765;Skill_766;Skill_767;Skill_768;Skill_769;Skill_770;Skill_771;Skill_772;Skill_773;Skill_774;Skill_775;Skill_776;Skill_777;Skill_778;Skill_779;Skill_780;Skill_781;Skill_782;Skill_783;Skill_784;Skill_785;Skill_786;Skill_787;Skill_788;Skill_789;Skill_790;Skill_791;Skill_792;Skill_793;Skill_794;Skill_795;Skill_796;Skill_797;Skill_798;Skill_799;Skill_800;Skill_801;Skill_802;Skill_803;Skill_804;Skill_805;Skill_806;Skill_807;Skill_808;Skill_809;Skill_810;Skill_811;Skill_812;Skill_813;Skill_814;Skill_815;Skill_816;Skill_817;Skill_818;Skill_819;Skill_820;Skill_821;Skill_822;Skill_823;Skill_824;Skill_825;Skill_826;Skill_827;Skill_828;Skill_829;Skill_830;Skill_831;Skill_832;Skill_833;Skill_834;Skill_835;Skill_836;Skill_837;Skill_838;Skill_839;Skill_840;Skill_841;Skill_842;Skill_843;Skill_844;Skill_845;Skill_846;Skill_847;Skill_848;Skill_849;Skill_850;Skill_851;Skill_852;Skill_853;Skill_854;Skill_855;Skill_856;Skill_857;Skill_858;Skill_859;Skill_860;Skill_861;Skill_862;Skill_863;Skill_864;Skill_865;Skill_866;Skill_867;Skill_868;Skill_869;Skill_870;Skill_871;Skill_872;Skill_873;Skill_874;Skill_875;Skill_876;Skill_877;Skill_878;Skill_879;Skill_880;Skill_881;Skill_882;Skill_883;Skill_884;Skill_885;Skill_886;Skill_887;Skill_888;Skill_889;Skill_890;Skill_891;Skill_892;Skill_893;Skill_894;Skill_895;Skill_896;Skill_897;Skill_898;Skill_899;Skill_900;Skill_901;Skill_902;Skill_903;Skill_904;Skill_905;Skill_906;Skill_907;Skill_908;Skill_909;Skill_910;Skill_911;Skill_912;Skill_913;Skill_914;Skill_915;Skill_916;Skill_917;Skill_918;Skill_919;Skill_920;Skill_921;Skill_922;Skill_923;Skill_924;Skill_925;Skill_926;Skill_927;Skill_928;Skill_929;Skill_930;Skill_931;Skill_932;Skill_933;Skill_934;Skill_935;Skill_936;Skill_937;Skill_938;Skill_939;Skill_940;Skill_941;Skill_942;Skill_943;Skill_944;Skill_945;Skill_946;Skill_947;Skill_948;Skill_949;Skill_950;Skill_951;Skill_952;Skill_953;Skill_954;Skill_955;Skill_956;Skill_957;Skill_958;Skill_959;Skill_960;Skill_961;Skill_962;Skill_963;Skill_964;Skill_965;Skill_966;Skill_967;Skill_968;Skill_969;Skill_970;Skill_971;Skill_972;Skill_973;Skill_974;Skill_975;Skill_976;Skill_977;Skill_978;Skill_979;Skill_980;Skill_981;Skill_982;Skill_983;Skill_984;Skill_985;Skill_986;Skill_987;Skill_988;Skill_989;Skill_990;Skill_991;Skill_992;Skill_993;Skill_994;Skill_995;Skill_996;Skill_997;Skill_998;Skill_999;Skill_1000;Skill_1001 +candidateAssociations-ext-1,DataLoader,AssociationsOver500Test,DataLoader AssociationsOver500Test,Skill_* diff --git a/src/test/resources/integrationTest/integrationTest.properties b/src/test/resources/integrationTest/integrationTest.properties index 3dc4450f..d23d6b0d 100644 --- a/src/test/resources/integrationTest/integrationTest.properties +++ b/src/test/resources/integrationTest/integrationTest.properties @@ -112,6 +112,7 @@ clientUserIDColumn=clientContact.id listDelimiter=; dateFormat=MM/dd/yy HH:mm processEmptyAssociations=false +wildcardMatching=false singleByteEncoding=false # --------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/resources/integrationTest/wildcardMatching/CandidateWildcardMatching.csv b/src/test/resources/integrationTest/wildcardMatching/CandidateWildcardMatching.csv new file mode 100644 index 00000000..7b1d2926 --- /dev/null +++ b/src/test/resources/integrationTest/wildcardMatching/CandidateWildcardMatching.csv @@ -0,0 +1,4 @@ +externalID ,firstName,lastName ,name +candidate-wc-ext-1-1,Search-1 ,WildcardMatching,Search-1 WildcardMatching +candidate-wc-ext-1-2,Search-2 ,WildcardMatching,Search-2 WildcardMatching +candidate-wc-ext-1-3,Search-3 ,WildcardMatching,Search-3 WildcardMatching diff --git a/src/test/resources/integrationTest/wildcardMatching/NoteWildcardMatching.csv b/src/test/resources/integrationTest/wildcardMatching/NoteWildcardMatching.csv new file mode 100644 index 00000000..5c811681 --- /dev/null +++ b/src/test/resources/integrationTest/wildcardMatching/NoteWildcardMatching.csv @@ -0,0 +1,2 @@ +personReference.name ,externalID ,action,candidates.externalID,comments +Manager CorporateUser,note-wc-ext-1,Call 1,candidate-wc-ext-1-* ,Data Loader Wildcard Matching Comment - Matches multiple existing candidates using a single search using a wildcard