parent
00aa52085c
commit
2889e2f66c
@ -0,0 +1,24 @@
|
||||
package com.almworks.items.util;
|
||||
|
||||
import com.almworks.util.Pair;
|
||||
import com.almworks.util.tests.BaseTestCase;
|
||||
|
||||
/**
|
||||
* @author dyoma
|
||||
*/
|
||||
public class DBNamespaceTests extends BaseTestCase {
|
||||
public void testReverseFullId() {
|
||||
DBNamespace module = DBNamespace.moduleNs("module");
|
||||
String moduleTypeId = module.type("TT.typeId", "").getId();
|
||||
assertEquals("module:t:TT.typeId", moduleTypeId);
|
||||
assertEquals(Pair.create("t", "TT.typeId"), module.reverseFullId(moduleTypeId));
|
||||
|
||||
DBNamespace local = module.subNs("local");
|
||||
String localTypeId = local.type("TT.typeId", "").getId();
|
||||
assertEquals("module:t:local.TT.typeId", localTypeId);
|
||||
assertEquals(Pair.create("t", "TT.typeId"), local.reverseFullId(localTypeId));
|
||||
|
||||
assertEquals(Pair.create("t", "local.TT.typeId"), module.reverseFullId(localTypeId));
|
||||
assertNull(local.reverseFullId(moduleTypeId));
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
package com.almworks.jira.provider3.remotedata.issue.fields;
|
||||
|
||||
import com.almworks.items.entities.api.Entity;
|
||||
import com.almworks.items.entities.api.EntityKey;
|
||||
import com.almworks.items.entities.api.collector.transaction.EntityHolder;
|
||||
import com.almworks.jira.provider3.remotedata.issue.EditIssueRequest;
|
||||
import com.almworks.jira.provider3.services.upload.PostUploadContext;
|
||||
import com.almworks.jira.provider3.sync.schema.ServerJira;
|
||||
import com.almworks.restconnector.operations.RestServerInfo;
|
||||
import com.almworks.util.LogHelper;
|
||||
import com.almworks.util.Pair;
|
||||
import org.almworks.util.Collections15;
|
||||
import org.almworks.util.Const;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.simple.JSONArray;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author dyoma
|
||||
* @param <T> must implement {@link Object#equals(Object)}
|
||||
*/
|
||||
public abstract class DifferenceValue<T> extends BaseValue {
|
||||
private static final String ADD = "add";
|
||||
private static final String REMOVE = "remove";
|
||||
|
||||
private final List<T> myAdd;
|
||||
private final List<T> myRemove;
|
||||
private final List<T> myNewValue;
|
||||
|
||||
protected DifferenceValue(List<T> add, List<T> remove, List<T> newValue) {
|
||||
super(true);
|
||||
myAdd = add;
|
||||
myRemove = remove;
|
||||
myNewValue = newValue;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String[] getFormValue(RestServerInfo serverInfo) {
|
||||
ArrayList<String> result = Collections15.arrayList();
|
||||
for (T value : myNewValue) {
|
||||
String id = extractFormId(value);
|
||||
if (!id.isEmpty()) result.add(id);
|
||||
}
|
||||
return result.toArray(Const.EMPTY_STRINGS);
|
||||
}
|
||||
|
||||
protected abstract String extractFormId(@NotNull T value);
|
||||
|
||||
@Override
|
||||
public void addChange(EditIssueRequest edit) {
|
||||
String fieldId = getFieldId();
|
||||
if (edit.getFieldInfo(fieldId) == null) return; // No such field
|
||||
if (!needsUpload(edit.getServerInfo())) return;
|
||||
JSONArray changes = new JSONArray();
|
||||
addChanges(edit, changes, myAdd, ADD);
|
||||
addChanges(edit, changes, myRemove, REMOVE);
|
||||
if (changes.isEmpty()) return;
|
||||
edit.addEdit(this, fieldId, changes);
|
||||
}
|
||||
|
||||
protected abstract void addChanges(EditIssueRequest edit, JSONArray target, List<T> change, String operation);
|
||||
|
||||
protected abstract String getFieldId();
|
||||
|
||||
@Override
|
||||
public boolean isChanged() {
|
||||
return !myAdd.isEmpty() || !myRemove.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFinishUpload(long issueItem, EntityHolder issue, PostUploadContext context) {
|
||||
EntityKey<Collection<Entity>> key = getIssueKey();
|
||||
if (isUploadDone(loadCurrentState(issue, key, this::readValue), key, myAdd, myRemove) != null) return;
|
||||
context.reportUploaded(issueItem, ServerJira.toLinkSetAttribute(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param <T> MUST implement {@link Object#equals(Object)}
|
||||
* @return null if current state has all adds and no removes (everything has been uploaded right)<br>
|
||||
* [notAdded, notRemoved] pair if at least one add is missing or remove present. Both lists are not-null.
|
||||
*/
|
||||
public static <T> Pair<List<T>, List<T>> isUploadDone(List<T> current, EntityKey<Collection<Entity>> key, List<T> expectedAdd, List<T> expectedRemove) {
|
||||
List<T> notAdded = new ArrayList<>();
|
||||
for (T entity : expectedAdd)
|
||||
if (!current.contains(entity)) {
|
||||
LogHelper.warning("Failed to add", entity, key);
|
||||
notAdded.add(entity);
|
||||
}
|
||||
List<T> notRemoved = new ArrayList<>();
|
||||
for (T entity : expectedRemove) {
|
||||
if (current.contains(entity)) {
|
||||
LogHelper.warning("Failed to remove", entity, key);
|
||||
notRemoved.add(entity);
|
||||
}
|
||||
}
|
||||
if (!notAdded.isEmpty() || !notRemoved.isEmpty()) {
|
||||
LogHelper.warning("Server state", current, key);
|
||||
return Pair.create(notAdded, notRemoved);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static <T> List<T> loadCurrentState(EntityHolder issue, EntityKey<Collection<Entity>> key, Function<EntityHolder, T> readValue) {
|
||||
EntityHolder[] serverHolders = issue.getReferenceCollection(key);
|
||||
return Arrays.stream(serverHolders)
|
||||
.map(readValue).filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected abstract T readValue(EntityHolder holder);
|
||||
|
||||
protected abstract EntityKey<Collection<Entity>> getIssueKey();
|
||||
|
||||
protected String toString(Object descriptor) {
|
||||
return "Upload " + descriptor + "[+" + myAdd + ",-" + myRemove + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String checkInitialState(EntityHolder issue) {
|
||||
return null; // No test because of uploading difference
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
package com.almworks.jira.provider3.remotedata.issue.fields;
|
||||
|
||||
import com.almworks.items.api.DBItemType;
|
||||
import com.almworks.items.entities.api.Entity;
|
||||
import com.almworks.items.entities.api.collector.transaction.EntityHolder;
|
||||
import com.almworks.items.sync.ItemVersion;
|
||||
import com.almworks.jira.provider3.schema.User;
|
||||
import com.almworks.jira.provider3.services.upload.UploadJsonUtil;
|
||||
import com.almworks.jira.provider3.sync.download2.rest.EntityParser;
|
||||
import com.almworks.jira.provider3.sync.download2.rest.JRUser;
|
||||
import com.almworks.jira.provider3.sync.download2.rest.JsonEntityParser;
|
||||
import com.almworks.jira.provider3.sync.download2.rest.LoadedEntity;
|
||||
import com.almworks.jira.provider3.sync.schema.ServerUser;
|
||||
import com.almworks.restconnector.json.JSONKey;
|
||||
import com.almworks.util.LogHelper;
|
||||
import com.almworks.util.collections.Convertor;
|
||||
import org.almworks.util.Util;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
/**
|
||||
* Supports for different user identities by {@link ServerUser#ACCOUNT_ID}.
|
||||
* If an user has accountId value, assumes that user's connection supports accountId and uses it as preferred identity.
|
||||
* @author dyoma
|
||||
*/
|
||||
public class JsonUserParser implements JsonEntityParser {
|
||||
public static final JsonUserParser INSTANCE = new JsonUserParser(ServerUser.PARSER);
|
||||
|
||||
private final EntityParser myParser;
|
||||
|
||||
private JsonUserParser(EntityParser parser) {
|
||||
assert parser != null;
|
||||
myParser = parser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityParser getParser() {
|
||||
return myParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBItemType getType() {
|
||||
return User.DB_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Convertor<Object, Entity> createConvertor() {
|
||||
return new Convertor<Object, Entity>() {
|
||||
@Override
|
||||
public Entity convert(Object value) {
|
||||
return ServerUser.create(value, myParser);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonEntityParser withParser(EntityParser parser) {
|
||||
return new JsonUserParser(parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadedUser readValue(ItemVersion value) {
|
||||
if (value == null || value.getItem() <= 0) return null;
|
||||
return new LoadedUser(value.getValue(User.NAME), value.getValue(User.ACCOUNT_ID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadedUser readValue(EntityHolder value) {
|
||||
if (value == null) return null;
|
||||
return new LoadedUser(value.getScalarValue(ServerUser.NAME), value.getScalarValue(ServerUser.ACCOUNT_ID));
|
||||
}
|
||||
|
||||
private static final Convertor<Object, Entity> CONVERTOR = new Convertor<Object, Entity>() {
|
||||
@Override
|
||||
public Entity convert(Object value) {
|
||||
return ServerUser.create(value, ServerUser.PARSER);
|
||||
}
|
||||
};
|
||||
public static JSONKey<Entity> jsonKey(String key) {
|
||||
return new JSONKey<>(key, CONVERTOR);
|
||||
}
|
||||
|
||||
public static class LoadedUser implements LoadedEntity {
|
||||
private final String myDisplayName;
|
||||
private final String myAccountId;
|
||||
|
||||
public LoadedUser(String displayName, String accountId) {
|
||||
if (accountId != null) {
|
||||
accountId = accountId.trim();
|
||||
if (accountId.isEmpty()) accountId = null;
|
||||
LogHelper.assertError(accountId == null || accountId.length() > 5, "Too short accountId:", accountId, displayName);
|
||||
}
|
||||
LogHelper.assertError(accountId != null, "LoadedUser misses both username and accountID", displayName);
|
||||
myDisplayName = displayName;
|
||||
myAccountId = accountId;
|
||||
}
|
||||
|
||||
public String getAccountId() {
|
||||
return myAccountId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDisplayableText() {
|
||||
return myDisplayName != null ? myDisplayName : myAccountId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFormValueId() {
|
||||
return myAccountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject toJson() {
|
||||
if (myAccountId != null) return UploadJsonUtil.object(JRUser.ACCOUNT_ID.getName(), myAccountId);
|
||||
LogHelper.warning("LoadedUser misses any JSON identity:", this);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (myAccountId != null) return myAccountId.hashCode();
|
||||
return super.hashCode(); // This instance is not equal to any other (except itself)
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
LoadedUser other = Util.castNullable(LoadedUser.class, obj);
|
||||
if (other == null) return false;
|
||||
if (myAccountId != null && other.myAccountId != null) return myAccountId.equals(other.myAccountId);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LoadedUser{" +
|
||||
"myDisplayName='" + myDisplayName + '\'' +
|
||||
", myAccountId='" + myAccountId + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
public boolean sameUser(EntityHolder user) {
|
||||
if (user == null) return false;
|
||||
String userAccountId = user.getScalarValue(ServerUser.ACCOUNT_ID);
|
||||
if (myAccountId != null && userAccountId != null) return myAccountId.equals(userAccountId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package com.almworks.jira.provider3.sync.download2.rest;
|
||||
|
||||
import com.almworks.jira.provider3.services.upload.UploadJsonUtil;
|
||||
import com.almworks.util.LogHelper;
|
||||
import org.almworks.util.Util;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents an entity with identity and displayable text.
|
||||
* An instance may represent itself as JSON.<br>
|
||||
* Implementors MUST implement {@link Object#equals(Object)}
|
||||
* @author dyoma
|
||||
*/
|
||||
public interface LoadedEntity {
|
||||
/**
|
||||
* @return human-friendly display name of the entity
|
||||
*/
|
||||
@NotNull
|
||||
String getDisplayableText();
|
||||
|
||||
@Override
|
||||
int hashCode();
|
||||
|
||||
/**
|
||||
* Checks for equality against other LoadedEntity.
|
||||
* Entities are equal if they identify the same object.
|
||||
* If entities has different values those don't affect identity, the entities MUST be equal.
|
||||
*/
|
||||
@Override
|
||||
boolean equals(Object obj);
|
||||
|
||||
/**
|
||||
* @return an identifier of the entity to use for upload via HTML forms (when API calls are not available)
|
||||
*/
|
||||
@NotNull
|
||||
String getFormValueId();
|
||||
|
||||
/**
|
||||
* @return default JSON presentation of this entity. The JSON is expected to contain only identity, so it could be used to update reference fields.
|
||||
*/
|
||||
JSONObject toJson();
|
||||
|
||||
class Simple<I> implements LoadedEntity {
|
||||
private final String myJsonIdKey;
|
||||
private final I myId;
|
||||
private final String myDisplayableText;
|
||||
|
||||
public Simple(String jsonIdKey, I id, String displayableText) {
|
||||
myJsonIdKey = jsonIdKey;
|
||||
myId = id;
|
||||
myDisplayableText = displayableText != null ? displayableText : myId.toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFormValueId() {
|
||||
return myId != null ? myId.toString() : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject toJson() {
|
||||
if (myId == null) {
|
||||
LogHelper.error("Missing id", this);
|
||||
return null;
|
||||
}
|
||||
return UploadJsonUtil.object(myJsonIdKey, String.valueOf(myId));
|
||||
}
|
||||
|
||||
public I getId() {
|
||||
return myId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDisplayableText() {
|
||||
return myDisplayableText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myJsonIdKey, myId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
Simple<?> other = Util.castNullable(Simple.class, obj);
|
||||
return other != null && myJsonIdKey.equals(other.myJsonIdKey) && Objects.equals(myId, other.myId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("(%s=%s %s)", myJsonIdKey, myId, myDisplayableText);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,136 @@
|
||||
TYPE: Entity[TYPE:'types.attach']
|
||||
EntityKey[attach.author:Entity]=Entity[types.user](user.accountId=557058:f7042e9f-c74c-416a-9b75-f08f781eda42)
|
||||
EntityKey[attach.date:Date]=21 Dec 2019 11:15:05.970 GMT
|
||||
EntityKey[attach.fileName:String]=attachment.txt
|
||||
EntityKey[attach.fileUrl:String]=https://pdyoma.atlassian.net/secure/attachment/10000/attachment.txt
|
||||
EntityKey[attach.id:Int]=10000
|
||||
EntityKey[attach.issue:Entity]=Entity[types.issue](issue.id=10000, issue.key=TP-1)
|
||||
EntityKey[attach.mime:String]=text/plain
|
||||
EntityKey[attach.sizeStr:String]=10
|
||||
EntityKey[sys.api.key.type:Entity HINT]=Entity[TYPE:'types.attach']<no resolution>
|
||||
TYPE: Entity[TYPE:'types.comment']
|
||||
EntityKey[comment.author:Entity]=Entity[types.user](user.accountId=557058:f7042e9f-c74c-416a-9b75-f08f781eda42)
|
||||
EntityKey[comment.created:Date]=21 Dec 2019 11:15:06.646 GMT
|
||||
EntityKey[comment.editor:Entity]=Entity[types.user](user.accountId=557058:f7042e9f-c74c-416a-9b75-f08f781eda42)
|
||||
EntityKey[comment.id:Int]=10105
|
||||
EntityKey[comment.issue:Entity]=Entity[types.issue](issue.id=10000, issue.key=TP-1)
|
||||
EntityKey[comment.security:Entity]=<null>
|
||||
EntityKey[comment.text:String]=comment
|
||||
EntityKey[comment.updated:Date]=21 Dec 2019 11:15:06.646 GMT
|
||||
EntityKey[sys.api.key.type:Entity HINT]=Entity[TYPE:'types.comment']<no resolution>
|
||||
TYPE: Entity[TYPE:'types.issue']
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.editableDay.customfield_10211:Int]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.editableDecimal.customfield_10129:Decimal]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.multiEnum.customfield_10215:Entity COLLECTION]={}
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.multiEnumRO.customfield_10127:Entity COLLECTION]={Entity[type.CONN-ECTI-ON_I-D.customfield_10127](customField.enum.idString=1)}
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.multiLabels.labels:Entity COLLECTION]={Entity[type.CONN-ECTI-ON_I-D.labels](customField.enum.idString=label1)}
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.readonlyDateTime.customfield_10100:Date]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.singleEnum.customfield_10213:Entity]=Entity[type.CONN-ECTI-ON_I-D.customfield_10213](customField.enum.idString=10100)
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10000:String]={}
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10102:String]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10103:String]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10104:String]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10105:String]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10106:String]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10107:String]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10108:String]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10122:String]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10123:String]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10128:String]=0|hzzzzz:
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10212:String]=<null>
|
||||
EntityKey[cf.CONN-ECTI-ON_I-D.unknownKind.customfield_10214:String]=<null>
|
||||
EntityKey[issue.affectedVersions:Entity COLLECTION]={}
|
||||
EntityKey[issue.assignee:Entity]=Entity[types.user](user.accountId=557058:f7042e9f-c74c-416a-9b75-f08f781eda42)
|
||||
EntityKey[issue.components:Entity COLLECTION]={}
|
||||
EntityKey[issue.created:Date]=12 Nov 2016 12:34:12.742 GMT
|
||||
EntityKey[issue.description:String]=descr
|
||||
EntityKey[issue.due:Int]=<null>
|
||||
EntityKey[issue.environment:String]=<null>
|
||||
EntityKey[issue.fixVersions:Entity COLLECTION]={}
|
||||
EntityKey[issue.id:Int]=10000
|
||||
EntityKey[issue.key:String]=TP-1
|
||||
EntityKey[issue.originalEstimate:Int]=<null>
|
||||
EntityKey[issue.parent:Entity]=<null>
|
||||
EntityKey[issue.priority:Entity]=Entity[types.priority](entity.id=4)
|
||||
EntityKey[issue.project:Entity]=Entity[types.project](entity.id=10000, project.key=TP)
|
||||
EntityKey[issue.remainEstimate:Int]=3600
|
||||
EntityKey[issue.reporter:Entity]=Entity[types.user](user.accountId=557058:f7042e9f-c74c-416a-9b75-f08f781eda42)
|
||||
EntityKey[issue.resolution:Entity]=Entity[types.resolution](entity.id=10000)
|
||||
EntityKey[issue.resolved:Date]=21 Dec 2019 11:23:40.193 GMT
|
||||
EntityKey[issue.security:Entity]=<null>
|
||||
EntityKey[issue.status:Entity]=Entity[types.status](entity.id=10001)
|
||||
EntityKey[issue.summary:String]=Ensure setup is complete 1122win
|
||||
EntityKey[issue.timeSpent:Int]=1920300
|
||||
EntityKey[issue.type:Entity]=Entity[types.issueType](entity.id=10000)
|
||||
EntityKey[issue.updated:Date]=25 Feb 2020 13:34:51.706 GMT
|
||||
EntityKey[issue.watchCount:Int]=1
|
||||
EntityKey[issue.watching:bool]=true
|
||||
EntityKey[sys.api.key.type:Entity HINT]=Entity[TYPE:'types.issue']<no resolution>
|
||||
EntityKey[sys.store.downloadStage.mark:com.almworks.items.entities.dbwrite.downloadstage.DownloadStageMark HINT]=DownloadStage[FULL]
|
||||
TYPE: Entity[TYPE:'types.link']
|
||||
TYPE: Entity[TYPE:'types.linkType']
|
||||
TYPE: Entity[TYPE:'types.worklog']
|
||||
EntityKey[sys.api.key.type:Entity HINT]=Entity[TYPE:'types.worklog']<no resolution>
|
||||
EntityKey[worklog.author:Entity]=Entity[types.user](user.accountId=557058:f7042e9f-c74c-416a-9b75-f08f781eda42)
|
||||
EntityKey[worklog.comment:String]=<null>
|
||||
EntityKey[worklog.created:Date]=31 Oct 2017 13:08:29.334 GMT
|
||||
EntityKey[worklog.editor:Entity]=Entity[types.user](user.accountId=557058:f7042e9f-c74c-416a-9b75-f08f781eda42)
|
||||
EntityKey[worklog.id:Int]=10001
|
||||
EntityKey[worklog.issue:Entity]=Entity[types.issue](issue.id=10000, issue.key=TP-1)
|
||||
EntityKey[worklog.security:Entity]=<null>
|
||||
EntityKey[worklog.started:Date]=05 Sep 2017 10:34:00.0 GMT
|
||||
EntityKey[worklog.timeSeconds:Int]=1900860
|
||||
EntityKey[worklog.updated:Date]=31 Oct 2017 13:08:29.334 GMT
|
||||
------------------------------
|
||||
EntityKey[sys.api.key.type:Entity HINT]=Entity[TYPE:'types.worklog']<no resolution>
|
||||
EntityKey[worklog.author:Entity]=Entity[types.user](user.accountId=557058:f7042e9f-c74c-416a-9b75-f08f781eda42)
|
||||
EntityKey[worklog.comment:String]=<null>
|
||||
EntityKey[worklog.created:Date]=31 Oct 2017 13:08:30.418 GMT
|
||||
EntityKey[worklog.editor:Entity]=Entity[types.user](user.accountId=557058:f7042e9f-c74c-416a-9b75-f08f781eda42)
|
||||
EntityKey[worklog.id:Int]=10002
|
||||
EntityKey[worklog.issue:Entity]=Entity[types.issue](issue.id=10000, issue.key=TP-1)
|
||||
EntityKey[worklog.security:Entity]=<null>
|
||||
EntityKey[worklog.started:Date]=27 Sep 2017 05:25:00.0 GMT
|
||||
EntityKey[worklog.timeSeconds:Int]=18240
|
||||
EntityKey[worklog.updated:Date]=31 Oct 2017 13:08:30.418 GMT
|
||||
------------------------------
|
||||
EntityKey[sys.api.key.type:Entity HINT]=Entity[TYPE:'types.worklog']<no resolution>
|
||||
EntityKey[worklog.author:Entity]=Entity[types.user](user.accountId=557058:f7042e9f-c74c-416a-9b75-f08f781eda42)
|
||||
EntityKey[worklog.comment:String]=prev
|
||||
EntityKey[worklog.created:Date]=31 Oct 2017 13:08:31.276 GMT
|
||||
EntityKey[worklog.editor:Entity]=Entity[types.user](user.accountId=557058:f7042e9f-c74c-416a-9b75-f08f781eda42)
|
||||
EntityKey[worklog.id:Int]=10003
|
||||
EntityKey[worklog.issue:Entity]=Entity[types.issue](issue.id=10000, issue.key=TP-1)
|
||||
EntityKey[worklog.security:Entity]=<null>
|
||||
EntityKey[worklog.started:Date]=27 Sep 2017 10:10:17.0 GMT
|
||||
EntityKey[worklog.timeSeconds:Int]=1200
|
||||
EntityKey[worklog.updated:Date]=31 Oct 2017 13:08:31.276 GMT
|
||||
*** BAGS ***
|
||||
Entity[TYPE:'types.attach']<no resolution>
|
||||
EntityKey[attach.issue:Entity]=Entity[types.issue](issue.id=10000, issue.key=TP-1)
|
||||
DELETE
|
||||
Entity[types.attach](attach.fileUrl=https://pdyoma.atlassian.net/secure/attachment/10000/attachment.txt, attach.issue=Entity[types.issue])
|
||||
------------------------------
|
||||
Entity[TYPE:'types.comment']<no resolution>
|
||||
EntityKey[comment.issue:Entity]=Entity[types.issue](issue.id=10000, issue.key=TP-1)
|
||||
DELETE
|
||||
Entity[types.comment](comment.id=10105, comment.issue=Entity[types.issue])
|
||||
------------------------------
|
||||
Entity[TYPE:'types.issue']<no resolution>
|
||||
EntityKey[issue.parent:Entity]=Entity[types.issue](issue.id=10000, issue.key=TP-1)
|
||||
CHANGE: EntityKey[issue.parent:Entity]=<null>
|
||||
------------------------------
|
||||
Entity[TYPE:'types.link']<no resolution>
|
||||
EntityKey[link.source:Entity]=Entity[types.issue](issue.id=10000, issue.key=TP-1)
|
||||
DELETE
|
||||
------------------------------
|
||||
Entity[TYPE:'types.link']<no resolution>
|
||||
EntityKey[link.target:Entity]=Entity[types.issue](issue.id=10000, issue.key=TP-1)
|
||||
DELETE
|
||||
------------------------------
|
||||
Entity[TYPE:'types.worklog']<no resolution>
|
||||
EntityKey[worklog.issue:Entity]=Entity[types.issue](issue.id=10000, issue.key=TP-1)
|
||||
DELETE
|
||||
Entity[types.worklog](worklog.id=10001, worklog.issue=Entity[types.issue])
|
||||
Entity[types.worklog](worklog.id=10002, worklog.issue=Entity[types.issue])
|
||||
Entity[types.worklog](worklog.id=10003, worklog.issue=Entity[types.issue])
|
@ -0,0 +1,65 @@
|
||||
package com.almworks.jira.provider3.sync.download2.details;
|
||||
|
||||
import com.almworks.restconnector.json.JSONKey;
|
||||
import com.almworks.restconnector.json.sax.JSONCollector;
|
||||
import com.almworks.restconnector.json.sax.LocationHandler;
|
||||
import com.almworks.restconnector.json.sax.PeekObjectEntry;
|
||||
import com.almworks.util.LogHelper;
|
||||
import com.almworks.util.commons.Procedure;
|
||||
import org.almworks.util.Collections15;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class CollectOperations {
|
||||
private static final JSONKey<String> ID = JSONKey.text("id");
|
||||
|
||||
private final LocationHandler myIssueHandler = PeekObjectEntry.objectEntry("operations", PeekObjectEntry.objectEntry("linkGroups", new LocationHandler() {
|
||||
@Override
|
||||
public void visit(Location what, boolean start, @Nullable String key, @Nullable Object value) throws ParseException, IOException {
|
||||
doVisit(what, start, key, value);
|
||||
}
|
||||
}));
|
||||
private final LocationHandler myLinkHandler = JSONCollector.objectConsumer(new Procedure<JSONObject>() {
|
||||
@Override
|
||||
public void invoke(JSONObject link) {
|
||||
String id = ID.getValue(link);
|
||||
if (id == null) return; // Some links does not have ID (example: reference to XML: /si/jira.issueviews:issue-xml/<issueKey>/<issueKey>.xml
|
||||
if (!myIds.add(id)) LogHelper.error("Duplicated id", id);
|
||||
}
|
||||
});
|
||||
private int myInLinks = 0;
|
||||
private final HashSet<String> myIds = Collections15.hashSet();
|
||||
|
||||
public LocationHandler getIssueHandler() {
|
||||
return myIssueHandler;
|
||||
}
|
||||
|
||||
public void doVisit(LocationHandler.Location what, boolean start, @Nullable String key, @Nullable Object value) throws ParseException, IOException {
|
||||
if (what == LocationHandler.Location.TOP && start) {
|
||||
myInLinks = 0;
|
||||
return;
|
||||
}
|
||||
if (start && what == LocationHandler.Location.ENTRY && "links".equals(key)) {
|
||||
myInLinks = 1;
|
||||
return;
|
||||
} else if (myInLinks == 0) return;
|
||||
else if (start && what != LocationHandler.Location.PRIMITIVE) myInLinks++;
|
||||
if (myInLinks >= 3) {
|
||||
if (myInLinks == 3 && start) {
|
||||
myLinkHandler.visit(LocationHandler.Location.TOP, true, null, null);
|
||||
// System.out.println("************ START");
|
||||
}
|
||||
myLinkHandler.visit(what, start, key, value);
|
||||
// System.out.println("Processing: " + what + " " + start + " " + key + " " + value);
|
||||
if (myInLinks == 3 && !start) {
|
||||
myLinkHandler.visit(LocationHandler.Location.TOP, false, null, null);
|
||||
// System.out.println("************ END");
|
||||
}
|
||||
}
|
||||
if (!start) myInLinks--;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.almworks.jira.provider3.sync.download2.details;
|
||||
|
||||
import com.almworks.items.api.DBIdentifiedObject;
|
||||
import com.almworks.items.entities.api.collector.transaction.EntityTransaction;
|
||||
import com.almworks.items.entities.api.collector.typetable.TransactionTestUtil;
|
||||
import com.almworks.items.sync.util.identity.DBIdentity;
|
||||
import com.almworks.jira.provider3.custom.FieldKind;
|
||||
import com.almworks.jira.provider3.custom.impl.CustomFieldsComponent;
|
||||
import com.almworks.jira.provider3.custom.loadxml.FieldKeysLoader;
|
||||
import com.almworks.jira.provider3.schema.Jira;
|
||||
import com.almworks.jira.provider3.sync.ServerInfo;
|
||||
import com.almworks.jira.provider3.sync.download2.TestResources;
|
||||
import com.almworks.jira.provider3.sync.schema.*;
|
||||
import com.almworks.util.tests.BaseTestCase;
|
||||
import org.almworks.util.TypedKey;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class LoadDetailsRTests extends BaseTestCase {
|
||||
private static final TestResources RESOURCES = TestResources.create(LoadDetailsRTests.class, "com/almworks/jira/provider3/sync/download2/details/");
|
||||
private static Map<String,FieldKind> CUSTOM_FIELDS_MAP;
|
||||
|
||||
static {
|
||||
try {
|
||||
FieldKeysLoader loader = FieldKeysLoader.load("/com/almworks/jira/provider3/customFields.xml", CustomFieldsComponent.SCHEMA);
|
||||
List<Map<TypedKey<?>, ?>> kinds = loader.getLoadedKinds();
|
||||
CUSTOM_FIELDS_MAP = CustomFieldsComponent.createKindsMap(false, kinds);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// https://pdyoma.atlassian.net/rest/api/2/issue/TP-1?expand=operations%2Ctransitions%2Cschema
|
||||
public void testNewCloudUsers() throws IOException, ParseException {
|
||||
runTest("newCloudUsers.json", "newCloudUsers.json.txt");
|
||||
}
|
||||
|
||||
private static final String CONNECTION_ID = "CONN-ECTI-ON_I-D";
|
||||
private void runTest(String source, String result) throws IOException, ParseException {
|
||||
Object json = RESOURCES.loadJson(source);
|
||||
CollectOperations operations = new CollectOperations();
|
||||
RESOURCES.parseJsonResource(source, operations.getIssueHandler());
|
||||
DBIdentifiedObject connectionObject = Jira.createConnectionObject(CONNECTION_ID);
|
||||
DBIdentity connection = DBIdentity.fromDBObject(connectionObject);
|
||||
EntityTransaction transaction = ServerInfo.priCreateTransaction(CONNECTION_ID, connection);
|
||||
LoadDetails details = new LoadDetails(transaction, new CustomFieldsSchema.RestLoader(CUSTOM_FIELDS_MAP, CONNECTION_ID));
|
||||
RESOURCES.parseJsonResource(source, details.createHandler("Test Issue"));
|
||||
RESOURCES.assertTextEquals(result, TransactionTestUtil.printTransaction(transaction, ServerIssue.TYPE, ServerComment.TYPE, ServerLink.TYPE, ServerLinkType.TYPE, ServerWorklog.TYPE, ServerAttachment.TYPE));
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.almworks.jira.provider3.sync.schema;
|
||||
|
||||
import com.almworks.items.api.DBItemType;
|
||||
import com.almworks.items.entities.api.Entity;
|
||||
import com.almworks.util.tests.BaseTestCase;
|
||||
|
||||
/**
|
||||
* @author dyoma
|
||||
*/
|
||||
public class ServerJiraTests extends BaseTestCase {
|
||||
public void testReverseEntityType() {
|
||||
Entity type = Entity.buildType("my.type");
|
||||
DBItemType dbType = ServerJira.toItemType(type);
|
||||
Entity reversedType = ServerJira.dbTypeToEntity(dbType);
|
||||
assertEquals(type.getTypeId(), reversedType.getTypeId());
|
||||
}
|
||||
}
|
Loading…
Reference in new issue