Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0d91203
added service now api integration
vktr7601 Jul 22, 2025
617a472
Merge branch 'main' into feature/service-now-module
vktr7601 Jul 23, 2025
2c646a9
Add Service Now functions and Service Now Getting Started
Jul 24, 2025
a94b24d
Add Service Now functions and Service Now Getting Started
Jul 25, 2025
1629dba
Add ServiceNow functions and ServiceNow Getting Started
Jul 25, 2025
37f5333
Add ServiceNow functions and ServiceNow Getting Started
Jul 25, 2025
1662a15
Refactoring based on the comments
Jul 29, 2025
945e1ad
Merge branch 'main' into feature/service-now-module-initial-setup
Aug 7, 2025
55e7760
Merge branch 'main' into feature/service-now-module-initial-setup
Aug 7, 2025
94d5a93
refactoring bellatrix.servicenow
Aug 7, 2025
2ecfdb1
Merge remote-tracking branch 'origin/main' into feature/service-now-m…
Aug 7, 2025
09f45a6
Merge branch 'feature/service-now-module' into feature/service-now-mo…
Aug 7, 2025
d3e1658
refactoring bellatrix.servicenow
Aug 7, 2025
f17cc51
refactoring bellatrix.servicenow
Aug 8, 2025
1201a81
refactoring bellatrix.servicenow
Aug 8, 2025
eff6f47
refactoring bellatrix.servicenow
Aug 8, 2025
242f9b1
refactoring bellatrix.servicenow
Aug 8, 2025
8129fc2
refactoring bellatrix.servicenow
Aug 11, 2025
ba837e9
Merge branch 'main' into feature/service-now-uib-setup
EliNedyalkova Sep 10, 2025
46f2369
Add UIB features to servicenow module
EliNedyalkova Sep 16, 2025
abe3de2
Add UIB features to servicenow module
EliNedyalkova Sep 16, 2025
7cb92d3
Add UIB features to servicenow module
EliNedyalkova Sep 16, 2025
89d42eb
Add UIB features to servicenow module
EliNedyalkova Sep 16, 2025
ddc4cd9
Add UIB features to servicenow module
EliNedyalkova Sep 16, 2025
7d97fe4
Add UIB features to servicenow module
EliNedyalkova Sep 16, 2025
91662a0
Add UIB features to servicenow module
EliNedyalkova Sep 16, 2025
3ccb965
Add UIB features examples to servicenow module
EliNedyalkova Sep 18, 2025
fbd92b8
Add UIB features examples to servicenow module
EliNedyalkova Sep 18, 2025
f9511a2
Merge branch 'main' into feature/service-now-uib-setup
EliNedyalkova Oct 1, 2025
89afc04
UIB Data Creation features refactoring aligned
EliNedyalkova Oct 14, 2025
7dd35ac
UIB Data Creation features refactoring aligned
EliNedyalkova Oct 14, 2025
74b9897
UIB fixes
EliNedyalkova Oct 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public class DependencyResolver {
private static final Set<Class<?>> buildingEntities = new HashSet<>();
private static final Set<Class<?>> creatingEntities = new HashSet<>();
private static final Set<Class<?>> deletingEntities = new HashSet<>();

private static final Set<Class<?>> updatingEntities = new HashSet<>();

/**
* Resolves all dependencies for the given entity by creating
* dependent entities where fields are marked with @Dependency
Expand Down Expand Up @@ -56,6 +57,13 @@ public static <T> T createDependencies(T entity) {
return createDependenciesRecursive(entity);
}

public static <T> T updateDependencies(T entity) {
if (entity == null) {
return null;
}
return updateDependenciesRecursive(entity);
}

public static <T> void deleteDependencies(T entity) {
if (entity == null) return;
deletingEntities.clear();
Expand Down Expand Up @@ -163,14 +171,46 @@ private static <T> T deleteDependenciesRecursive(T entity) {
deletingEntities.remove(entityClass);
}
}

private static <T> T updateDependenciesRecursive(T entity) {
if (entity == null) {
return null;
}

Class<?> entityClass = entity.getClass();

// Check for circular dependency
if (updatingEntities.contains(entityClass)) {
throw new RuntimeException("Circular dependency detected for entity: " + entityClass.getSimpleName());
}

// Add to resolving set
updatingEntities.add(entityClass);

try {
// Get all fields with @Dependency annotations
List<Field> dependencyFields = getDependencyFields(entityClass);

// Resolve each dependency field
for (Field field : dependencyFields) {
updateFieldDependencyRecursive(entity, field);
}

return entity;
} finally {
// Remove from resolving set
updatingEntities.remove(entityClass);
}
}


/**
* Gets all fields with @Dependency annotations from the given class.
*
* @param entityClass The entity class to scan
* @return List of fields with @Dependency annotations
*/
private static List<Field> getDependencyFields(Class<?> entityClass) {
protected static List<Field> getDependencyFields(Class<?> entityClass) {
List<Field> dependencyFields = new ArrayList<>();
Field[] fields = entityClass.getDeclaredFields();

Expand Down Expand Up @@ -333,7 +373,7 @@ private static Object buildDependencyEntity(Dependency dependencyAnnotation) {
* @param entityType The entity class to find a factory for
* @return The factory instance or null if not found
*/
private static EntityFactory<?> findFactoryForEntity(Class<?> entityType) {
public static EntityFactory<?> findFactoryForEntity(Class<?> entityType) {
String entityName = entityType.getSimpleName();
String factoryClassName = entityName + "RepositoryFactory";

Expand Down Expand Up @@ -406,4 +446,34 @@ private static boolean trySetViaSetter(Object target, Field field, Object value)
}
return false;
}

private static void updateFieldDependencyRecursive(Object entity, Field field) {
try {
field.setAccessible(true);
Dependency dependencyAnnotation = field.getAnnotation(Dependency.class);
Entity currentValue = (Entity)field.get(entity);

// Skip if field already has a value and forceCreate is false
if (currentValue == null || currentValue.getIdentifier() == null) {
return;
}

// Create the dependency entity using the repository
Object builtDependencyEntity = currentValue == null ? buildDependencyEntity(dependencyAnnotation) : currentValue;

// Get the repository directly using the entity type from the annotation
@SuppressWarnings("unchecked")
Repository<Entity> repository = (Repository<Entity>) RepositoryProvider.INSTANCE.get((Class<? extends Entity>) dependencyAnnotation.entityType());

Log.info("Updating dependency entity for field '" + field.getName() + "' of type '" + dependencyAnnotation.entityType().getSimpleName() + "'.");
repository.update((Entity) builtDependencyEntity);

// Recursively resolve dependencies for the dependency entity
// This ensures that all dependencies are resolved bottom-up
updateDependenciesRecursive(builtDependencyEntity);

} catch (Exception e) {
throw new RuntimeException("Failed to update dependency for field: " + field.getName(), e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
package solutions.bellatrix.data.http.infrastructure;

import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import solutions.bellatrix.data.annotations.Dependency;
import solutions.bellatrix.data.configuration.RepositoryProvider;
import solutions.bellatrix.data.contracts.Repository;
import solutions.bellatrix.data.http.contracts.EntityFactory;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

import static solutions.bellatrix.data.http.infrastructure.DependencyResolver.getDependencyFields;

@SuperBuilder
@NoArgsConstructor
@SuppressWarnings("unchecked")
public abstract class Entity<TIdentifier, TEntity> {
public TEntity get() {
Expand Down Expand Up @@ -40,6 +50,48 @@ public void deleteDependenciesAndSelf() {
DependencyResolver.deleteDependencies(this);
}

public TEntity getWithDependencies() throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException {
var repository = (Repository<Entity>)RepositoryProvider.INSTANCE.get(this.getClass());
var record = repository.getById(this);
List<Field> dependencyFields = getDependencyFields(record.getClass());

for (Field field : dependencyFields) {
field.setAccessible(true);
Dependency dependencyAnnotation = field.getAnnotation(Dependency.class);

Entity currentValue = (Entity)field.get(this);
// Skip if field already has a value and forceCreate is false
if (currentValue != null && !dependencyAnnotation.forceCreate()) {
break;
}

Field f = record.getClass().getDeclaredField(String.format("id%s", Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1)));
f.setAccessible(true);
var dependencyId = f.get(record).toString();

Class<?> fieldType = field.getType();
EntityFactory<?> factory = DependencyResolver.findFactoryForEntity(fieldType);
if (factory == null) {
throw new IllegalStateException("No factory found for entity type: " + dependencyAnnotation.entityType().getSimpleName());
}

Entity newEntity = factory.buildDefault();

newEntity.setIdentifier(dependencyId);

field.set(record, newEntity.getWithDependencies());
}

return (TEntity) record;
}

public TEntity updateWithDependencies() {
DependencyResolver.updateDependencies(this);
var repository = (Repository<Entity>)RepositoryProvider.INSTANCE.get(this.getClass());
this.setIdentifier((String)repository.update(this).getIdentifier());
return (TEntity)this;
}

public abstract TIdentifier getIdentifier();
public abstract void setIdentifier(String id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import solutions.bellatrix.data.http.contracts.Queryable;

import java.util.Objects;

@Data
@SuperBuilder
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public abstract class HttpEntity<TIdentifier, TEntity> extends Entity<TIdentifier, TEntity> implements Queryable {
private transient HttpResponse response;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package solutions.bellatrix.servicenow.components.enums;

import lombok.Getter;

@Getter
public enum UibToolbarButtonLabel {
HOME("Home"),
LIST("List"),
TEAMS("Teams"),
SCHEDULES("Schedules"),
DASHBOARD("Dashboard");

private final String value;

UibToolbarButtonLabel(String label) {
this.value = label;
}

@Override
public String toString() {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package solutions.bellatrix.servicenow.components.uiBuilder;
import solutions.bellatrix.servicenow.components.enums.UibComponentType;
import solutions.bellatrix.web.components.*;
import solutions.bellatrix.web.components.contracts.ComponentDisabled;

public class RecordCheckbox extends WebComponent implements ComponentDisabled {

public class RecordCheckbox extends UIBDefaultComponent implements ComponentDisabled {
protected CheckBox checkbox() {
return this.createByCss(CheckBox.class, "input[type='checkbox']");
}
Expand All @@ -12,11 +12,20 @@ public boolean isChecked() {
return checkbox().isChecked();
}

@Override
public void setText(String text) {
}

@Override
public boolean isDisabled() {
return getAttribute("readonly") != null || checkbox().isDisabled() || getAttribute("disabled") != null;
}

@Override
public UibComponentType componentType() {
return null;
}

public void check() {
if (!isChecked() && !isDisabled()) {
checkbox().check();
Expand All @@ -40,4 +49,9 @@ public void assertIsChecked() {
public void assertIsUnchecked() {
checkbox().validateIsUnchecked();
}

@Override
public String getText() {
return "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@

public class RecordChoice extends UIBDefaultComponent implements ComponentDisabled, ComponentText {

protected WebComponent dropdown() {
public WebComponent dropdown() {
return this.createByXPath(WebComponent.class, ".//now-select");
}

protected Button dropdownButton() {
public Button dropdownButton() {
return dropdown().createByCss(Button.class, "button");
}

protected List<Button> getDropdownOptions() {
public List<Button> getDropdownOptions() {
return getDropDownWrapper().createAllByCss(Button.class, "div[role='option']");
}

protected Button getOptionByIndex(int index) {
public Button getOptionByIndex(int index) {
return getDropdownOptions().get(index);
}

protected Button getOptionByText(String text) {
public Button getOptionByText(String text) {
var optionFound = getDropdownOptions().stream().filter(x -> x.getText().equals(text)).findFirst();
if (optionFound.isEmpty()) {
throw new RuntimeException("Option with text %s not found.".formatted(text));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protected Button customUiNowDateInputCalendarButton() {

//calendar pop up
protected ShadowRoot customUiDropDownSeismicHoist() {
return create().byCss(ShadowRoot.class, "seismic-hoist").toShadowRootToBeAttached();
return create().byCss(ShadowRoot.class, "seismic-hoist").getShadowRoot();
}

protected Button calendarPopUpOkayButton() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
public class RecordDateTimeInput extends UIBDefaultComponent implements ComponentDisabled, ComponentText {

protected ShadowRoot customUiDropDownSeismicHoist() {
return create().byCss(ShadowRoot.class, "seismic-hoist").toShadowRootToBeAttached();
return create().byCss(ShadowRoot.class, "seismic-hoist").getShadowRoot();
}

protected ShadowRoot customUiNowDateTimeInputByLabel() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

import org.openqa.selenium.ElementNotInteractableException;
import solutions.bellatrix.servicenow.components.enums.UibComponentType;
import solutions.bellatrix.web.components.TextInput;
import solutions.bellatrix.web.components.contracts.ComponentDisabled;
import solutions.bellatrix.web.components.contracts.ComponentText;

public class RecordInput extends UIBDefaultComponent implements ComponentDisabled, ComponentText {

protected TextInput textInput() {
return create().byCss(TextInput.class, "input");
public UIBDefaultComponent textInput() {
return create().byCss(UIBDefaultComponent.class, "input");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
public class RecordReference extends UIBDefaultComponent implements ComponentDisabled, ComponentText {

protected WebComponent recordTypehead() {
return this.create().byCss(WebComponent.class, "now-record-typeahead").toShadowRootToBeAttached();
return this.create().byCss(WebComponent.class, "now-record-typeahead").getShadowRoot();
}

protected Button referenceButton() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

import org.openqa.selenium.ElementNotInteractableException;
import solutions.bellatrix.servicenow.components.enums.UibComponentType;
import solutions.bellatrix.web.components.TextArea;
import solutions.bellatrix.web.components.contracts.ComponentDisabled;
import solutions.bellatrix.web.components.contracts.ComponentText;

public class RecordTextArea extends UIBDefaultComponent implements ComponentDisabled, ComponentText {

protected TextArea textInput() {
return this.createByCss(TextArea.class, "textarea");
public UIBDefaultComponent textInput() {
return this.createByCss(UIBDefaultComponent.class, "textarea");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ public abstract class UIBDefaultComponent extends SnComponent implements Compone
public final static EventListener<ComponentActionEventArgs> SETTING_VALUE = new EventListener<>();
public final static EventListener<ComponentActionEventArgs> VALUE_SET = new EventListener<>();

public String getValue() {
return this.defaultGetValue();
}

public UIBDefaultComponent textInput() {
var shadow = create().by(ShadowRoot.class, this.getFindStrategy()).getShadowRoot();
return shadow.createByXPath(UIBDefaultComponent.class, ".//input");
}

protected String formControlXpathLocator() {
return ".//*[contains(concat(' ',normalize-space(@class),' '),' form-control ')]";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected Button customUiNowDateInputCalendarButton() {
}

protected WebComponent customUiDropDownSeismicHoist() {
return create().byCss(WebComponent.class, "seismic-hoist").toShadowRootToBeAttached();
return create().byCss(WebComponent.class, "seismic-hoist").getShadowRoot();
}

protected Button calendarPopUpOkayButton() {
Expand Down
Loading