mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-23 13:38:38 +00:00
MINOR: App changes for collate support (#21068)
* feat: prep for collate support - Added support for delayed background jobs. This is to delete the support user after a while. - added ScheduleType.OnlyManual for applications to support only manual triggers - Added a four hour token expiration - Allow a null trigger type in job data (not sure what it's for) * added test for delayed job * added type checking for enumCleanupArgs in navbar * - added a support toten type - added job handler for delete token * fixed whitelist links * patch OnlyManual apps * Update openmetadata-spec/src/main/resources/json/schema/entity/applications/app.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * added indexes for runAt * use NO_JOB_SLEEP_SECONDS in background job test * Fixed NavBar test * removed backticls * fix indexes * change for support app * fix tests --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Karan Hotchandani <33024356+karanh37@users.noreply.github.com> Co-authored-by: karanh37 <karanh37@gmail.com> Co-authored-by: Pere Miquel Brull <peremiquelbrull@gmail.com>
This commit is contained in:
parent
6ea630d7ef
commit
baee931b85
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE background_jobs
|
||||||
|
ADD COLUMN runAt BIGINT;
|
||||||
|
|
||||||
|
CREATE INDEX background_jobs_run_at_index ON background_jobs(runAt);
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE background_jobs
|
||||||
|
ADD COLUMN runAt BIGINT;
|
||||||
|
|
||||||
|
CREATE INDEX background_jobs_run_at_index ON background_jobs(runAt);
|
||||||
@ -60,6 +60,7 @@ import org.glassfish.jersey.media.multipart.MultiPartFeature;
|
|||||||
import org.glassfish.jersey.server.ServerProperties;
|
import org.glassfish.jersey.server.ServerProperties;
|
||||||
import org.jdbi.v3.core.Jdbi;
|
import org.jdbi.v3.core.Jdbi;
|
||||||
import org.jdbi.v3.sqlobject.SqlObjects;
|
import org.jdbi.v3.sqlobject.SqlObjects;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.openmetadata.schema.api.security.AuthenticationConfiguration;
|
import org.openmetadata.schema.api.security.AuthenticationConfiguration;
|
||||||
import org.openmetadata.schema.api.security.AuthorizerConfiguration;
|
import org.openmetadata.schema.api.security.AuthorizerConfiguration;
|
||||||
import org.openmetadata.schema.api.security.ClientType;
|
import org.openmetadata.schema.api.security.ClientType;
|
||||||
@ -241,8 +242,7 @@ public class OpenMetadataApplication extends Application<OpenMetadataApplication
|
|||||||
registerEventFilter(catalogConfig, environment);
|
registerEventFilter(catalogConfig, environment);
|
||||||
environment.lifecycle().manage(new ManagedShutdown());
|
environment.lifecycle().manage(new ManagedShutdown());
|
||||||
|
|
||||||
JobHandlerRegistry registry = new JobHandlerRegistry();
|
JobHandlerRegistry registry = getJobHandlerRegistry();
|
||||||
registry.register("EnumCleanupHandler", new EnumCleanupHandler(getDao(jdbi)));
|
|
||||||
environment
|
environment
|
||||||
.lifecycle()
|
.lifecycle()
|
||||||
.manage(new GenericBackgroundWorker(jdbi.onDemand(JobDAO.class), registry));
|
.manage(new GenericBackgroundWorker(jdbi.onDemand(JobDAO.class), registry));
|
||||||
@ -284,6 +284,12 @@ public class OpenMetadataApplication extends Application<OpenMetadataApplication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected @NotNull JobHandlerRegistry getJobHandlerRegistry() {
|
||||||
|
JobHandlerRegistry registry = new JobHandlerRegistry();
|
||||||
|
registry.register("EnumCleanupHandler", new EnumCleanupHandler(getDao(jdbi)));
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
|
||||||
private void registerHealthCheckJobs(OpenMetadataApplicationConfig catalogConfig) {
|
private void registerHealthCheckJobs(OpenMetadataApplicationConfig catalogConfig) {
|
||||||
ServicesStatusJobHandler healthCheckStatusHandler =
|
ServicesStatusJobHandler healthCheckStatusHandler =
|
||||||
ServicesStatusJobHandler.create(
|
ServicesStatusJobHandler.create(
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import static org.openmetadata.service.resources.apps.AppResource.SCHEDULED_TYPE
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -66,10 +67,12 @@ public class AbstractNativeApplication implements NativeApplication {
|
|||||||
@Override
|
@Override
|
||||||
public void install(String installedBy) {
|
public void install(String installedBy) {
|
||||||
// If the app does not have any Schedule Return without scheduling
|
// If the app does not have any Schedule Return without scheduling
|
||||||
if (Boolean.TRUE.equals(app.getDeleted()) || (app.getAppSchedule() == null)) {
|
if (Boolean.TRUE.equals(app.getDeleted())
|
||||||
return;
|
|| (app.getAppSchedule() == null)
|
||||||
}
|
|| Set.of(ScheduleType.NoSchedule, ScheduleType.OnlyManual)
|
||||||
if (app.getAppType().equals(AppType.Internal)
|
.contains(app.getScheduleType())) {
|
||||||
|
LOG.debug("App {} does not support scheduling.", app.getName());
|
||||||
|
} else if (app.getAppType().equals(AppType.Internal)
|
||||||
&& (SCHEDULED_TYPES.contains(app.getScheduleType()))) {
|
&& (SCHEDULED_TYPES.contains(app.getScheduleType()))) {
|
||||||
try {
|
try {
|
||||||
ApplicationHandler.getInstance().removeOldJobs(app);
|
ApplicationHandler.getInstance().removeOldJobs(app);
|
||||||
@ -101,7 +104,8 @@ public class AbstractNativeApplication implements NativeApplication {
|
|||||||
@Override
|
@Override
|
||||||
public void triggerOnDemand(Map<String, Object> config) {
|
public void triggerOnDemand(Map<String, Object> config) {
|
||||||
// Validate Native Application
|
// Validate Native Application
|
||||||
if (app.getScheduleType().equals(ScheduleType.ScheduledOrManual)) {
|
if (Set.of(ScheduleType.ScheduledOrManual, ScheduleType.OnlyManual)
|
||||||
|
.contains(app.getScheduleType())) {
|
||||||
AppRuntime runtime = getAppRuntime(app);
|
AppRuntime runtime = getAppRuntime(app);
|
||||||
validateServerExecutableApp(runtime);
|
validateServerExecutableApp(runtime);
|
||||||
// Trigger the application with the provided configuration payload
|
// Trigger the application with the provided configuration payload
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import com.cronutils.parser.CronParser;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
@ -206,7 +207,11 @@ public class AppScheduler {
|
|||||||
private JobDetail jobBuilder(App app, String jobIdentity) throws ClassNotFoundException {
|
private JobDetail jobBuilder(App app, String jobIdentity) throws ClassNotFoundException {
|
||||||
JobDataMap dataMap = new JobDataMap();
|
JobDataMap dataMap = new JobDataMap();
|
||||||
dataMap.put(APP_NAME, app.getName());
|
dataMap.put(APP_NAME, app.getName());
|
||||||
dataMap.put("triggerType", app.getAppSchedule().getScheduleTimeline().value());
|
dataMap.put(
|
||||||
|
"triggerType",
|
||||||
|
Optional.ofNullable(app.getAppSchedule())
|
||||||
|
.map(v -> v.getScheduleTimeline().value())
|
||||||
|
.orElse(null));
|
||||||
Class<? extends NativeApplication> clz =
|
Class<? extends NativeApplication> clz =
|
||||||
(Class<? extends NativeApplication>) Class.forName(app.getClassName());
|
(Class<? extends NativeApplication>) Class.forName(app.getClassName());
|
||||||
JobBuilder jobBuilder =
|
JobBuilder jobBuilder =
|
||||||
|
|||||||
@ -70,6 +70,7 @@ import org.openmetadata.schema.auth.PasswordResetToken;
|
|||||||
import org.openmetadata.schema.auth.PersonalAccessToken;
|
import org.openmetadata.schema.auth.PersonalAccessToken;
|
||||||
import org.openmetadata.schema.auth.RefreshToken;
|
import org.openmetadata.schema.auth.RefreshToken;
|
||||||
import org.openmetadata.schema.auth.TokenType;
|
import org.openmetadata.schema.auth.TokenType;
|
||||||
|
import org.openmetadata.schema.auth.collate.SupportToken;
|
||||||
import org.openmetadata.schema.configuration.AssetCertificationSettings;
|
import org.openmetadata.schema.configuration.AssetCertificationSettings;
|
||||||
import org.openmetadata.schema.configuration.WorkflowSettings;
|
import org.openmetadata.schema.configuration.WorkflowSettings;
|
||||||
import org.openmetadata.schema.dataInsight.DataInsightChart;
|
import org.openmetadata.schema.dataInsight.DataInsightChart;
|
||||||
@ -5764,6 +5765,7 @@ public interface CollectionDAO {
|
|||||||
case PASSWORD_RESET -> JsonUtils.readValue(json, PasswordResetToken.class);
|
case PASSWORD_RESET -> JsonUtils.readValue(json, PasswordResetToken.class);
|
||||||
case REFRESH_TOKEN -> JsonUtils.readValue(json, RefreshToken.class);
|
case REFRESH_TOKEN -> JsonUtils.readValue(json, RefreshToken.class);
|
||||||
case PERSONAL_ACCESS_TOKEN -> JsonUtils.readValue(json, PersonalAccessToken.class);
|
case PERSONAL_ACCESS_TOKEN -> JsonUtils.readValue(json, PersonalAccessToken.class);
|
||||||
|
case SUPPORT_TOKEN -> JsonUtils.readValue(json, SupportToken.class);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ public class GenericBackgroundWorker implements Managed {
|
|||||||
|
|
||||||
private static final int INITIAL_BACKOFF_SECONDS = 1;
|
private static final int INITIAL_BACKOFF_SECONDS = 1;
|
||||||
private static final int MAX_BACKOFF_SECONDS = 600; // 10 minutes
|
private static final int MAX_BACKOFF_SECONDS = 600; // 10 minutes
|
||||||
private static final int NO_JOB_SLEEP_SECONDS = 10; // Sleep if no jobs are available
|
public static final int NO_JOB_SLEEP_SECONDS = 10; // Sleep if no jobs are available
|
||||||
|
|
||||||
private final JobDAO jobDao;
|
private final JobDAO jobDao;
|
||||||
private final JobHandlerRegistry handlerRegistry;
|
private final JobHandlerRegistry handlerRegistry;
|
||||||
|
|||||||
@ -26,37 +26,50 @@ public interface JobDAO {
|
|||||||
|
|
||||||
default long insertJob(
|
default long insertJob(
|
||||||
BackgroundJob.JobType jobType, JobHandler handler, String jobArgs, String createdBy) {
|
BackgroundJob.JobType jobType, JobHandler handler, String jobArgs, String createdBy) {
|
||||||
|
return insertJob(jobType, handler, jobArgs, createdBy, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
default long insertJob(
|
||||||
|
BackgroundJob.JobType jobType,
|
||||||
|
JobHandler handler,
|
||||||
|
String jobArgs,
|
||||||
|
String createdBy,
|
||||||
|
Long runAt) {
|
||||||
try {
|
try {
|
||||||
JsonUtils.readTree(jobArgs);
|
JsonUtils.readTree(jobArgs);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException("jobArgs must be a valid JSON string");
|
throw new IllegalArgumentException("jobArgs must be a valid JSON string");
|
||||||
}
|
}
|
||||||
return insertJobInternal(
|
return insertJobInternal(
|
||||||
jobType.name(), handler.getClass().getSimpleName(), jobArgs, createdBy);
|
jobType.name(), handler.getClass().getSimpleName(), jobArgs, createdBy, runAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConnectionAwareSqlUpdate(
|
@ConnectionAwareSqlUpdate(
|
||||||
value =
|
value =
|
||||||
"INSERT INTO background_jobs (jobType, methodName, jobArgs, createdBy) "
|
"INSERT INTO background_jobs (jobType, methodName, jobArgs, createdBy, runAt) "
|
||||||
+ "VALUES (:jobType, :methodName, :jobArgs, :createdBy)",
|
+ "VALUES (:jobType, :methodName, :jobArgs, :createdBy, :runAt)",
|
||||||
connectionType = MYSQL)
|
connectionType = MYSQL)
|
||||||
@ConnectionAwareSqlUpdate(
|
@ConnectionAwareSqlUpdate(
|
||||||
value =
|
value =
|
||||||
"INSERT INTO background_jobs (jobType, methodName, jobArgs,createdBy) VALUES (:jobType, :methodName, :jobArgs::jsonb,:createdBy) ",
|
"INSERT INTO background_jobs (jobType, methodName, jobArgs,createdBy,runAt) VALUES (:jobType, :methodName, :jobArgs::jsonb,:createdBy,:runAt) ",
|
||||||
connectionType = POSTGRES)
|
connectionType = POSTGRES)
|
||||||
@GetGeneratedKeys
|
@GetGeneratedKeys
|
||||||
long insertJobInternal(
|
long insertJobInternal(
|
||||||
@Bind("jobType") String jobType,
|
@Bind("jobType") String jobType,
|
||||||
@Bind("methodName") String methodName,
|
@Bind("methodName") String methodName,
|
||||||
@Bind("jobArgs") String jobArgs,
|
@Bind("jobArgs") String jobArgs,
|
||||||
@Bind("createdBy") String createdBy);
|
@Bind("createdBy") String createdBy,
|
||||||
|
@Bind("runAt") Long runAt);
|
||||||
|
|
||||||
default Optional<BackgroundJob> fetchPendingJob() throws BackgroundJobException {
|
default Optional<BackgroundJob> fetchPendingJob() throws BackgroundJobException {
|
||||||
return Optional.ofNullable(fetchPendingJobInternal());
|
return Optional.ofNullable(fetchPendingJobInternal());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SqlQuery(
|
@SqlQuery(
|
||||||
"SELECT id,jobType,methodName,jobArgs,status,createdAt,updatedAt,createdBy FROM background_jobs WHERE status = 'PENDING' ORDER BY createdAt LIMIT 1")
|
"SELECT id,jobType,methodName,jobArgs,status,createdAt,updatedAt,createdBy,runAt FROM background_jobs"
|
||||||
|
+ " WHERE status = 'PENDING'"
|
||||||
|
+ " AND COALESCE(runAt, 0) <= UNIX_TIMESTAMP(NOW(3)) * 1000"
|
||||||
|
+ " ORDER BY createdAt LIMIT 1")
|
||||||
@RegisterRowMapper(BackgroundJobMapper.class)
|
@RegisterRowMapper(BackgroundJobMapper.class)
|
||||||
BackgroundJob fetchPendingJobInternal() throws StatementException;
|
BackgroundJob fetchPendingJobInternal() throws StatementException;
|
||||||
|
|
||||||
|
|||||||
@ -10,10 +10,17 @@ public class JobHandlerRegistry {
|
|||||||
private final Map<String, JobHandler> handlers = new HashMap<>();
|
private final Map<String, JobHandler> handlers = new HashMap<>();
|
||||||
|
|
||||||
public void register(String methodName, JobHandler handler) {
|
public void register(String methodName, JobHandler handler) {
|
||||||
LOG.info("Registering background job handler for: {}", handler.getClass().getSimpleName());
|
LOG.info(
|
||||||
|
"Registering background job handler for: {} -> {}",
|
||||||
|
handler.getClass().getSimpleName(),
|
||||||
|
handler.getClass().getCanonicalName());
|
||||||
handlers.put(methodName, handler);
|
handlers.put(methodName, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void register(JobHandler handler) {
|
||||||
|
register(handler.getClass().getSimpleName(), handler);
|
||||||
|
}
|
||||||
|
|
||||||
public JobHandler getHandler(BackgroundJob job) {
|
public JobHandler getHandler(BackgroundJob job) {
|
||||||
String methodName = job.getMethodName();
|
String methodName = job.getMethodName();
|
||||||
Long jobId = job.getId();
|
Long jobId = job.getId();
|
||||||
|
|||||||
@ -107,7 +107,11 @@ public class AppResource extends EntityResource<App, AppRepository> {
|
|||||||
static final String FIELDS = "owners";
|
static final String FIELDS = "owners";
|
||||||
private SearchRepository searchRepository;
|
private SearchRepository searchRepository;
|
||||||
public static final List<ScheduleType> SCHEDULED_TYPES =
|
public static final List<ScheduleType> SCHEDULED_TYPES =
|
||||||
List.of(ScheduleType.Scheduled, ScheduleType.ScheduledOrManual, ScheduleType.NoSchedule);
|
List.of(
|
||||||
|
ScheduleType.Scheduled,
|
||||||
|
ScheduleType.ScheduledOrManual,
|
||||||
|
ScheduleType.NoSchedule,
|
||||||
|
ScheduleType.OnlyManual);
|
||||||
private final AppMapper mapper = new AppMapper();
|
private final AppMapper mapper = new AppMapper();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -99,7 +99,8 @@ public class JwtFilter implements ContainerRequestFilter {
|
|||||||
"v1/users/generatePasswordResetLink",
|
"v1/users/generatePasswordResetLink",
|
||||||
"v1/users/password/reset",
|
"v1/users/password/reset",
|
||||||
"v1/users/login",
|
"v1/users/login",
|
||||||
"v1/users/refresh");
|
"v1/users/refresh",
|
||||||
|
"v1/collate/apps/support/login");
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private JwtFilter() {}
|
private JwtFilter() {}
|
||||||
|
|||||||
@ -6,11 +6,10 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.openmetadata.service.jobs.GenericBackgroundWorker.NO_JOB_SLEEP_SECONDS;
|
||||||
import static org.openmetadata.service.security.SecurityUtil.getPrincipalName;
|
import static org.openmetadata.service.security.SecurityUtil.getPrincipalName;
|
||||||
import static org.openmetadata.service.util.TestUtils.ADMIN_AUTH_HEADERS;
|
import static org.openmetadata.service.util.TestUtils.ADMIN_AUTH_HEADERS;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -31,6 +30,7 @@ import org.openmetadata.service.OpenMetadataApplicationTest;
|
|||||||
import org.openmetadata.service.jdbi3.CollectionDAO;
|
import org.openmetadata.service.jdbi3.CollectionDAO;
|
||||||
import org.openmetadata.service.jobs.BackgroundJobException;
|
import org.openmetadata.service.jobs.BackgroundJobException;
|
||||||
import org.openmetadata.service.jobs.EnumCleanupHandler;
|
import org.openmetadata.service.jobs.EnumCleanupHandler;
|
||||||
|
import org.openmetadata.service.jobs.GenericBackgroundWorker;
|
||||||
import org.openmetadata.service.jobs.JobDAO;
|
import org.openmetadata.service.jobs.JobDAO;
|
||||||
import org.openmetadata.service.jobs.JobHandler;
|
import org.openmetadata.service.jobs.JobHandler;
|
||||||
import org.openmetadata.service.jobs.JobHandlerRegistry;
|
import org.openmetadata.service.jobs.JobHandlerRegistry;
|
||||||
@ -50,11 +50,12 @@ public class BackgroundJobWorkerTest extends OpenMetadataApplicationTest {
|
|||||||
|
|
||||||
public static CustomProperty customPropertyMulti;
|
public static CustomProperty customPropertyMulti;
|
||||||
public static CustomProperty customPropertySingle;
|
public static CustomProperty customPropertySingle;
|
||||||
|
public static GenericBackgroundWorker worker;
|
||||||
|
|
||||||
public static Table TABLE4;
|
public static Table TABLE4;
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void setup(TestInfo test) throws IOException, URISyntaxException {
|
public static void setup(TestInfo test) throws Exception {
|
||||||
registry = new JobHandlerRegistry();
|
registry = new JobHandlerRegistry();
|
||||||
jobDAO = Entity.getJobDAO();
|
jobDAO = Entity.getJobDAO();
|
||||||
collectionDao = Entity.getCollectionDAO();
|
collectionDao = Entity.getCollectionDAO();
|
||||||
@ -78,7 +79,7 @@ public class BackgroundJobWorkerTest extends OpenMetadataApplicationTest {
|
|||||||
.withConfig(
|
.withConfig(
|
||||||
Map.of(
|
Map.of(
|
||||||
"values",
|
"values",
|
||||||
List.of("single1", "single2", "single3", "single4", "\"single5\""),
|
List.of("\"single5\"", "single1", "single2", "single3", "single4"),
|
||||||
"multiSelect",
|
"multiSelect",
|
||||||
false)));
|
false)));
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ public class BackgroundJobWorkerTest extends OpenMetadataApplicationTest {
|
|||||||
.withConfig(
|
.withConfig(
|
||||||
Map.of(
|
Map.of(
|
||||||
"values",
|
"values",
|
||||||
List.of("multi1", "multi2", "multi3", "multi4", "\"multi5\""),
|
List.of("\"multi5\"", "multi1", "multi2", "multi3", "multi4"),
|
||||||
"multiSelect",
|
"multiSelect",
|
||||||
true)));
|
true)));
|
||||||
CustomProperty[] customProperties = {customPropertySingle, customPropertyMulti};
|
CustomProperty[] customProperties = {customPropertySingle, customPropertyMulti};
|
||||||
@ -173,7 +174,6 @@ public class BackgroundJobWorkerTest extends OpenMetadataApplicationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public final void testBackgroundJobTriggerWithValidArgs() {
|
public final void testBackgroundJobTriggerWithValidArgs() {
|
||||||
|
|
||||||
EnumCleanupArgs enumCleanupArgs =
|
EnumCleanupArgs enumCleanupArgs =
|
||||||
new EnumCleanupArgs()
|
new EnumCleanupArgs()
|
||||||
.withPropertyName(customPropertyMulti.getName())
|
.withPropertyName(customPropertyMulti.getName())
|
||||||
@ -204,4 +204,58 @@ public class BackgroundJobWorkerTest extends OpenMetadataApplicationTest {
|
|||||||
assertEquals(enumCleanupArgs, actualArgs, "Job arguments should match");
|
assertEquals(enumCleanupArgs, actualArgs, "Job arguments should match");
|
||||||
assertEquals(job.getCreatedBy(), fetchedJob.getCreatedBy(), "Created by should match");
|
assertEquals(job.getCreatedBy(), fetchedJob.getCreatedBy(), "Created by should match");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public final void testDelayedJobTrigger() throws InterruptedException {
|
||||||
|
// Create a delayed job for enum cleanup
|
||||||
|
EnumCleanupArgs enumCleanupArgs =
|
||||||
|
new EnumCleanupArgs()
|
||||||
|
.withPropertyName(customPropertyMulti.getName())
|
||||||
|
.withRemovedEnumKeys(List.of())
|
||||||
|
.withEntityType("table");
|
||||||
|
String jobArgs = JsonUtils.pojoToJson(enumCleanupArgs);
|
||||||
|
String createdBy = "admin";
|
||||||
|
long delayInMillis = 100; // 100ms delay
|
||||||
|
|
||||||
|
long jobId =
|
||||||
|
Entity.getJobDAO()
|
||||||
|
.insertJob(
|
||||||
|
BackgroundJob.JobType.CUSTOM_PROPERTY_ENUM_CLEANUP,
|
||||||
|
new EnumCleanupHandler(collectionDao),
|
||||||
|
jobArgs,
|
||||||
|
createdBy,
|
||||||
|
System.currentTimeMillis() + delayInMillis);
|
||||||
|
|
||||||
|
Optional<BackgroundJob> fetchedJobOptional = Entity.getJobDAO().fetchJobById(jobId);
|
||||||
|
assertTrue(fetchedJobOptional.isPresent(), "Delayed job should be present");
|
||||||
|
|
||||||
|
BackgroundJob fetchedJob = fetchedJobOptional.get();
|
||||||
|
assertEquals(
|
||||||
|
BackgroundJob.JobType.CUSTOM_PROPERTY_ENUM_CLEANUP,
|
||||||
|
fetchedJob.getJobType(),
|
||||||
|
"Job type should match");
|
||||||
|
assertEquals("EnumCleanupHandler", fetchedJob.getMethodName(), "Method name should match");
|
||||||
|
assertEquals(createdBy, fetchedJob.getCreatedBy(), "Created by should match");
|
||||||
|
|
||||||
|
// Verify the job arguments
|
||||||
|
EnumCleanupArgs actualArgs =
|
||||||
|
JsonUtils.readValue(JsonUtils.pojoToJson(fetchedJob.getJobArgs()), EnumCleanupArgs.class);
|
||||||
|
assertEquals(enumCleanupArgs, actualArgs, "Job arguments should match");
|
||||||
|
|
||||||
|
// Verify job is not executed immediately
|
||||||
|
Thread.sleep(delayInMillis - 50);
|
||||||
|
Optional<BackgroundJob> jobAfterShortWait = Entity.getJobDAO().fetchJobById(jobId);
|
||||||
|
assertTrue(jobAfterShortWait.isPresent(), "Job should still exist after short wait");
|
||||||
|
assertEquals(
|
||||||
|
BackgroundJob.Status.PENDING,
|
||||||
|
jobAfterShortWait.get().getStatus(),
|
||||||
|
"Job should not be completed yet");
|
||||||
|
|
||||||
|
// Wait for the next run cycle
|
||||||
|
Thread.sleep(delayInMillis + NO_JOB_SLEEP_SECONDS);
|
||||||
|
Optional<BackgroundJob> jobAfterDelay = Entity.getJobDAO().fetchJobById(jobId);
|
||||||
|
assertTrue(jobAfterDelay.isPresent(), "Job should still exist after delay");
|
||||||
|
assertEquals(
|
||||||
|
BackgroundJob.Status.COMPLETED, jobAfterDelay.get().getStatus(), "Job should be completed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,8 @@
|
|||||||
"REFRESH_TOKEN",
|
"REFRESH_TOKEN",
|
||||||
"EMAIL_VERIFICATION",
|
"EMAIL_VERIFICATION",
|
||||||
"PASSWORD_RESET",
|
"PASSWORD_RESET",
|
||||||
"PERSONAL_ACCESS_TOKEN"
|
"PERSONAL_ACCESS_TOKEN",
|
||||||
|
"SUPPORT_TOKEN"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"$id": "https://open-metadata.org/schema/auth/supportToken.json",
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "SupportToken",
|
||||||
|
"description": "This schema defines an access token used for support purposes. It is used only in Collate.",
|
||||||
|
"type": "object",
|
||||||
|
"javaType": "org.openmetadata.schema.auth.collate.SupportToken",
|
||||||
|
"javaInterfaces": ["org.openmetadata.schema.TokenInterface"],
|
||||||
|
"properties": {
|
||||||
|
"token": {
|
||||||
|
"description": "Unique Refresh Token for user",
|
||||||
|
"$ref": "../type/basic.json#/definitions/uuid"
|
||||||
|
},
|
||||||
|
"tokenName": {
|
||||||
|
"description": "Name of the token",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"description": "User Id of the User this refresh token is given to",
|
||||||
|
"$ref": "../type/basic.json#/definitions/uuid"
|
||||||
|
},
|
||||||
|
"tokenType": {
|
||||||
|
"description": "Token Type",
|
||||||
|
"$ref": "./emailVerificationToken.json#/definitions/tokenType",
|
||||||
|
"default": "SUPPORT_TOKEN"
|
||||||
|
},
|
||||||
|
"expiryDate": {
|
||||||
|
"description": "Expiry Date-Time of the token",
|
||||||
|
"$ref": "../type/basic.json#/definitions/timestamp"
|
||||||
|
},
|
||||||
|
"jwtToken": {
|
||||||
|
"description": "JWT Auth Token.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"token",
|
||||||
|
"userId",
|
||||||
|
"expiryDate"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
@ -3,7 +3,9 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "App",
|
"title": "App",
|
||||||
"javaType": "org.openmetadata.schema.entity.app.App",
|
"javaType": "org.openmetadata.schema.entity.app.App",
|
||||||
"javaInterfaces": ["org.openmetadata.schema.EntityInterface"],
|
"javaInterfaces": [
|
||||||
|
"org.openmetadata.schema.EntityInterface"
|
||||||
|
],
|
||||||
"description": "This schema defines the applications for Open-Metadata.",
|
"description": "This schema defines the applications for Open-Metadata.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"definitions": {
|
"definitions": {
|
||||||
@ -15,20 +17,29 @@
|
|||||||
"Live",
|
"Live",
|
||||||
"Scheduled",
|
"Scheduled",
|
||||||
"ScheduledOrManual",
|
"ScheduledOrManual",
|
||||||
"NoSchedule"
|
"NoSchedule",
|
||||||
|
"OnlyManual"
|
||||||
],
|
],
|
||||||
"javaEnums": [
|
"javaEnums": [
|
||||||
{
|
{
|
||||||
"name": "Live"
|
"name": "Live",
|
||||||
|
"description": "An app that has other trigger mechanisms."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Scheduled"
|
"name": "Scheduled",
|
||||||
|
"description": "An app that has a schedule and cannot be run manually."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ScheduledOrManual"
|
"name": "ScheduledOrManual",
|
||||||
|
"description": "An app that has a schedule and can be run manually."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "NoSchedule"
|
"name": "NoSchedule",
|
||||||
|
"description": "An app that has no schedule and cannot be run manually."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OnlyManual",
|
||||||
|
"description": "An app that has no schedule but can be run manually."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -36,7 +47,14 @@
|
|||||||
"javaType": "org.openmetadata.schema.entity.app.ScheduleTimeline",
|
"javaType": "org.openmetadata.schema.entity.app.ScheduleTimeline",
|
||||||
"description": "This schema defines the Application ScheduleTimeline Options",
|
"description": "This schema defines the Application ScheduleTimeline Options",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["Hourly"," Daily", "Weekly", "Monthly", "Custom", "None"],
|
"enum": [
|
||||||
|
"Hourly",
|
||||||
|
"Daily",
|
||||||
|
"Weekly",
|
||||||
|
"Monthly",
|
||||||
|
"Custom",
|
||||||
|
"None"
|
||||||
|
],
|
||||||
"default": "Weekly"
|
"default": "Weekly"
|
||||||
},
|
},
|
||||||
"appSchedule": {
|
"appSchedule": {
|
||||||
@ -51,7 +69,9 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["scheduleTimeline"],
|
"required": [
|
||||||
|
"scheduleTimeline"
|
||||||
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"appType": {
|
"appType": {
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"jobType": {
|
"jobType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["CUSTOM_PROPERTY_ENUM_CLEANUP"],
|
"enum": ["CUSTOM_PROPERTY_ENUM_CLEANUP", "DELETE_ENTITY", "DELETE_TOKEN"],
|
||||||
"description": "Type of the job."
|
"description": "Type of the job."
|
||||||
},
|
},
|
||||||
"methodName": {
|
"methodName": {
|
||||||
@ -23,10 +23,18 @@
|
|||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"$ref": "./enumCleanupArgs.json"
|
"$ref": "./enumCleanupArgs.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Object containing job arguments."
|
"description": "Object containing job arguments."
|
||||||
},
|
},
|
||||||
|
"runAt": {
|
||||||
|
"description": "Timestamp when the job was run in Unix epoch time milliseconds (default: as soon as possible).",
|
||||||
|
"$ref": "../type/basic.json#/definitions/timestamp"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["COMPLETED", "FAILED", "RUNNING","PENDING"],
|
"enum": ["COMPLETED", "FAILED", "RUNNING","PENDING"],
|
||||||
|
|||||||
@ -82,14 +82,17 @@ module.exports = {
|
|||||||
moduleDirectories: ['node_modules', 'src'],
|
moduleDirectories: ['node_modules', 'src'],
|
||||||
|
|
||||||
reporters: [
|
reporters: [
|
||||||
"default",
|
'default',
|
||||||
["jest-junit", {
|
[
|
||||||
outputDirectory: "../../../../target/test-reports",
|
'jest-junit',
|
||||||
outputName: "jest-junit.xml",
|
{
|
||||||
classNameTemplate: "{classname}",
|
outputDirectory: '../../../../target/test-reports',
|
||||||
titleTemplate: "{title}",
|
outputName: 'jest-junit.xml',
|
||||||
ancestorSeparator: " › ",
|
classNameTemplate: '{classname}',
|
||||||
usePathForSuiteName: "true"
|
titleTemplate: '{title}',
|
||||||
}]
|
ancestorSeparator: ' › ',
|
||||||
]
|
usePathForSuiteName: 'true',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -26,10 +26,6 @@ jest.mock('../../hooks/useApplicationStore', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock('../../utils/AuthProvider.util', () => ({
|
|
||||||
isProtectedRoute: jest.fn().mockReturnValue(true),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'../Settings/Applications/ApplicationsProvider/ApplicationsProvider',
|
'../Settings/Applications/ApplicationsProvider/ApplicationsProvider',
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import { useApplicationStore } from '../../hooks/useApplicationStore';
|
|||||||
import { getLimitConfig } from '../../rest/limitsAPI';
|
import { getLimitConfig } from '../../rest/limitsAPI';
|
||||||
import { getSettingsByType } from '../../rest/settingConfigAPI';
|
import { getSettingsByType } from '../../rest/settingConfigAPI';
|
||||||
import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase';
|
import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase';
|
||||||
import { isProtectedRoute } from '../../utils/AuthProvider.util';
|
|
||||||
import { LimitBanner } from '../common/LimitBanner/LimitBanner';
|
import { LimitBanner } from '../common/LimitBanner/LimitBanner';
|
||||||
import LeftSidebar from '../MyData/LeftSidebar/LeftSidebar.component';
|
import LeftSidebar from '../MyData/LeftSidebar/LeftSidebar.component';
|
||||||
import NavBar from '../NavBar/NavBar';
|
import NavBar from '../NavBar/NavBar';
|
||||||
@ -75,7 +74,8 @@ const AppContainer = () => {
|
|||||||
{/* Render main content */}
|
{/* Render main content */}
|
||||||
<Layout>
|
<Layout>
|
||||||
{/* Render Appbar */}
|
{/* Render Appbar */}
|
||||||
{isProtectedRoute(location.pathname) && isAuthenticated ? (
|
{applicationRoutesClass.isProtectedRoute(location.pathname) &&
|
||||||
|
isAuthenticated ? (
|
||||||
<NavBar />
|
<NavBar />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@ -24,13 +24,15 @@ import PageNotFound from '../../pages/PageNotFound/PageNotFound';
|
|||||||
import SignUpPage from '../../pages/SignUp/SignUpPage';
|
import SignUpPage from '../../pages/SignUp/SignUpPage';
|
||||||
import AppContainer from '../AppContainer/AppContainer';
|
import AppContainer from '../AppContainer/AppContainer';
|
||||||
import Loader from '../common/Loader/Loader';
|
import Loader from '../common/Loader/Loader';
|
||||||
import { UnAuthenticatedAppRouter } from './UnAuthenticatedAppRouter';
|
|
||||||
|
|
||||||
import { LogoutPage } from '../../pages/LogoutPage/LogoutPage';
|
import { LogoutPage } from '../../pages/LogoutPage/LogoutPage';
|
||||||
import SamlCallback from '../../pages/SamlCallback';
|
import SamlCallback from '../../pages/SamlCallback';
|
||||||
|
import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase';
|
||||||
|
|
||||||
const AppRouter = () => {
|
const AppRouter = () => {
|
||||||
const location = useCustomLocation();
|
const location = useCustomLocation();
|
||||||
|
const UnAuthenticatedAppRouter =
|
||||||
|
applicationRoutesClass.getUnAuthenticatedRouteElements();
|
||||||
|
|
||||||
// web analytics instance
|
// web analytics instance
|
||||||
const analytics = useAnalytics();
|
const analytics = useAnalytics();
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { useApplicationStore } from '../../hooks/useApplicationStore';
|
|||||||
import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation';
|
import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation';
|
||||||
import PageNotFound from '../../pages/PageNotFound/PageNotFound';
|
import PageNotFound from '../../pages/PageNotFound/PageNotFound';
|
||||||
import AccountActivationConfirmation from '../../pages/SignUp/account-activation-confirmation.component';
|
import AccountActivationConfirmation from '../../pages/SignUp/account-activation-confirmation.component';
|
||||||
import { isProtectedRoute } from '../../utils/AuthProvider.util';
|
import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase';
|
||||||
import Auth0Callback from '../Auth/AppCallbacks/Auth0Callback/Auth0Callback';
|
import Auth0Callback from '../Auth/AppCallbacks/Auth0Callback/Auth0Callback';
|
||||||
import withSuspenseFallback from './withSuspenseFallback';
|
import withSuspenseFallback from './withSuspenseFallback';
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ export const UnAuthenticatedAppRouter = () => {
|
|||||||
}
|
}
|
||||||
}, [authConfig?.provider]);
|
}, [authConfig?.provider]);
|
||||||
|
|
||||||
if (isProtectedRoute(location.pathname)) {
|
if (applicationRoutesClass.isProtectedRoute(location.pathname)) {
|
||||||
return <Redirect to={ROUTES.SIGNIN} />;
|
return <Redirect to={ROUTES.SIGNIN} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -64,13 +64,13 @@ import {
|
|||||||
fetchAuthorizerConfig,
|
fetchAuthorizerConfig,
|
||||||
} from '../../../rest/miscAPI';
|
} from '../../../rest/miscAPI';
|
||||||
import { getLoggedInUser } from '../../../rest/userAPI';
|
import { getLoggedInUser } from '../../../rest/userAPI';
|
||||||
|
import applicationRoutesClass from '../../../utils/ApplicationRoutesClassBase';
|
||||||
import TokenService from '../../../utils/Auth/TokenService/TokenServiceUtil';
|
import TokenService from '../../../utils/Auth/TokenService/TokenServiceUtil';
|
||||||
import {
|
import {
|
||||||
extractDetailsFromToken,
|
extractDetailsFromToken,
|
||||||
getAuthConfig,
|
getAuthConfig,
|
||||||
getUrlPathnameExpiry,
|
getUrlPathnameExpiry,
|
||||||
getUserManagerConfig,
|
getUserManagerConfig,
|
||||||
isProtectedRoute,
|
|
||||||
prepareUserProfileFromClaims,
|
prepareUserProfileFromClaims,
|
||||||
} from '../../../utils/AuthProvider.util';
|
} from '../../../utils/AuthProvider.util';
|
||||||
import {
|
import {
|
||||||
@ -209,7 +209,7 @@ export const AuthProvider = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handledVerifiedUser = () => {
|
const handledVerifiedUser = () => {
|
||||||
if (!isProtectedRoute(location.pathname)) {
|
if (!applicationRoutesClass.isProtectedRoute(location.pathname)) {
|
||||||
history.push(ROUTES.HOME);
|
history.push(ROUTES.HOME);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -404,7 +404,7 @@ export const AuthProvider = ({
|
|||||||
* Stores redirect URL for successful login
|
* Stores redirect URL for successful login
|
||||||
*/
|
*/
|
||||||
const handleStoreProtectedRedirectPath = useCallback(() => {
|
const handleStoreProtectedRedirectPath = useCallback(() => {
|
||||||
if (isProtectedRoute(location.pathname)) {
|
if (applicationRoutesClass.isProtectedRoute(location.pathname)) {
|
||||||
storeRedirectPath(location.pathname);
|
storeRedirectPath(location.pathname);
|
||||||
}
|
}
|
||||||
}, [location.pathname, storeRedirectPath]);
|
}, [location.pathname, storeRedirectPath]);
|
||||||
|
|||||||
@ -58,12 +58,16 @@ import { useTourProvider } from '../../context/TourProvider/TourProvider';
|
|||||||
import { useWebSocketConnector } from '../../context/WebSocketProvider/WebSocketProvider';
|
import { useWebSocketConnector } from '../../context/WebSocketProvider/WebSocketProvider';
|
||||||
import { EntityTabs, EntityType } from '../../enums/entity.enum';
|
import { EntityTabs, EntityType } from '../../enums/entity.enum';
|
||||||
import { EntityReference } from '../../generated/entity/type';
|
import { EntityReference } from '../../generated/entity/type';
|
||||||
import { BackgroundJob, JobType } from '../../generated/jobs/backgroundJob';
|
import {
|
||||||
|
BackgroundJob,
|
||||||
|
EnumCleanupArgs,
|
||||||
|
JobType,
|
||||||
|
} from '../../generated/jobs/backgroundJob';
|
||||||
import { useCurrentUserPreferences } from '../../hooks/currentUserStore/useCurrentUserStore';
|
import { useCurrentUserPreferences } from '../../hooks/currentUserStore/useCurrentUserStore';
|
||||||
import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation';
|
import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation';
|
||||||
import { useDomainStore } from '../../hooks/useDomainStore';
|
import { useDomainStore } from '../../hooks/useDomainStore';
|
||||||
import { getVersion } from '../../rest/miscAPI';
|
import { getVersion } from '../../rest/miscAPI';
|
||||||
import { isProtectedRoute } from '../../utils/AuthProvider.util';
|
import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase';
|
||||||
import brandClassBase from '../../utils/BrandData/BrandClassBase';
|
import brandClassBase from '../../utils/BrandData/BrandClassBase';
|
||||||
import {
|
import {
|
||||||
hasNotificationPermission,
|
hasNotificationPermission,
|
||||||
@ -259,6 +263,18 @@ const NavBar = () => {
|
|||||||
const { jobArgs, status, jobType } = backgroundJobData;
|
const { jobArgs, status, jobType } = backgroundJobData;
|
||||||
|
|
||||||
if (jobType === JobType.CustomPropertyEnumCleanup) {
|
if (jobType === JobType.CustomPropertyEnumCleanup) {
|
||||||
|
const enumCleanupArgs = jobArgs as EnumCleanupArgs;
|
||||||
|
if (!enumCleanupArgs.entityType) {
|
||||||
|
showErrorToast(
|
||||||
|
{
|
||||||
|
isAxiosError: true,
|
||||||
|
message: 'Invalid job arguments: entityType is required',
|
||||||
|
} as AxiosError,
|
||||||
|
t('message.unexpected-error')
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
body = t('message.custom-property-update', {
|
body = t('message.custom-property-update', {
|
||||||
propertyName: jobArgs.propertyName,
|
propertyName: jobArgs.propertyName,
|
||||||
entityName: jobArgs.entityType,
|
entityName: jobArgs.entityType,
|
||||||
@ -267,7 +283,7 @@ const NavBar = () => {
|
|||||||
|
|
||||||
path = getSettingPath(
|
path = getSettingPath(
|
||||||
GlobalSettingsMenuCategory.CUSTOM_PROPERTIES,
|
GlobalSettingsMenuCategory.CUSTOM_PROPERTIES,
|
||||||
getCustomPropertyEntityPathname(jobArgs.entityType)
|
getCustomPropertyEntityPathname(enumCleanupArgs.entityType)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +319,10 @@ const NavBar = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDocumentVisibilityChange = async () => {
|
const handleDocumentVisibilityChange = async () => {
|
||||||
if (isProtectedRoute(location.pathname) && isTourRoute) {
|
if (
|
||||||
|
applicationRoutesClass.isProtectedRoute(location.pathname) &&
|
||||||
|
isTourRoute
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newVersion = await getVersion();
|
const newVersion = await getVersion();
|
||||||
|
|||||||
@ -54,11 +54,9 @@ const AppSchedule = ({
|
|||||||
const { config } = useLimitStore();
|
const { config } = useLimitStore();
|
||||||
|
|
||||||
const showRunNowButton = useMemo(() => {
|
const showRunNowButton = useMemo(() => {
|
||||||
if (appData && appData.scheduleType === ScheduleType.ScheduledOrManual) {
|
return [ScheduleType.ScheduledOrManual, ScheduleType.OnlyManual].includes(
|
||||||
return true;
|
appData?.scheduleType
|
||||||
}
|
);
|
||||||
|
|
||||||
return false;
|
|
||||||
}, [appData]);
|
}, [appData]);
|
||||||
|
|
||||||
const { pipelineSchedules } =
|
const { pipelineSchedules } =
|
||||||
|
|||||||
@ -54,4 +54,5 @@ export enum TokenType {
|
|||||||
PasswordReset = "PASSWORD_RESET",
|
PasswordReset = "PASSWORD_RESET",
|
||||||
PersonalAccessToken = "PERSONAL_ACCESS_TOKEN",
|
PersonalAccessToken = "PERSONAL_ACCESS_TOKEN",
|
||||||
RefreshToken = "REFRESH_TOKEN",
|
RefreshToken = "REFRESH_TOKEN",
|
||||||
|
SupportToken = "SUPPORT_TOKEN",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Collate.
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* This schema defines an access token used for support purposes. It is used only in Collate.
|
||||||
|
*/
|
||||||
|
export interface SupporToken {
|
||||||
|
/**
|
||||||
|
* Expiry Date-Time of the token
|
||||||
|
*/
|
||||||
|
expiryDate: number;
|
||||||
|
/**
|
||||||
|
* JWT Auth Token.
|
||||||
|
*/
|
||||||
|
jwtToken?: string;
|
||||||
|
/**
|
||||||
|
* Unique Refresh Token for user
|
||||||
|
*/
|
||||||
|
token: string;
|
||||||
|
/**
|
||||||
|
* Name of the token
|
||||||
|
*/
|
||||||
|
tokenName?: string;
|
||||||
|
/**
|
||||||
|
* Token Type
|
||||||
|
*/
|
||||||
|
tokenType?: TokenType;
|
||||||
|
/**
|
||||||
|
* User Id of the User this refresh token is given to
|
||||||
|
*/
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token Type
|
||||||
|
*
|
||||||
|
* Different Type of User token
|
||||||
|
*/
|
||||||
|
export enum TokenType {
|
||||||
|
EmailVerification = "EMAIL_VERIFICATION",
|
||||||
|
PasswordReset = "PASSWORD_RESET",
|
||||||
|
PersonalAccessToken = "PERSONAL_ACCESS_TOKEN",
|
||||||
|
RefreshToken = "REFRESH_TOKEN",
|
||||||
|
SupportToken = "SUPPORT_TOKEN",
|
||||||
|
}
|
||||||
@ -1324,6 +1324,7 @@ export interface ExecutionContext {
|
|||||||
export enum ScheduleType {
|
export enum ScheduleType {
|
||||||
Live = "Live",
|
Live = "Live",
|
||||||
NoSchedule = "NoSchedule",
|
NoSchedule = "NoSchedule",
|
||||||
|
OnlyManual = "OnlyManual",
|
||||||
Scheduled = "Scheduled",
|
Scheduled = "Scheduled",
|
||||||
ScheduledOrManual = "ScheduledOrManual",
|
ScheduledOrManual = "ScheduledOrManual",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,11 @@ export interface BackgroundJob {
|
|||||||
* JobHandler name of the method that will be executed for this job.
|
* JobHandler name of the method that will be executed for this job.
|
||||||
*/
|
*/
|
||||||
methodName: string;
|
methodName: string;
|
||||||
|
/**
|
||||||
|
* Timestamp when the job was run in Unix epoch time milliseconds (default: as soon as
|
||||||
|
* possible).
|
||||||
|
*/
|
||||||
|
runAt?: number;
|
||||||
/**
|
/**
|
||||||
* Current status of the job.
|
* Current status of the job.
|
||||||
*/
|
*/
|
||||||
@ -58,15 +63,16 @@ export interface EnumCleanupArgs {
|
|||||||
/**
|
/**
|
||||||
* Type of the entity.
|
* Type of the entity.
|
||||||
*/
|
*/
|
||||||
entityType: string;
|
entityType?: string;
|
||||||
/**
|
/**
|
||||||
* Name of the property.
|
* Name of the property.
|
||||||
*/
|
*/
|
||||||
propertyName: string;
|
propertyName?: string;
|
||||||
/**
|
/**
|
||||||
* List of removed enum keys.
|
* List of removed enum keys.
|
||||||
*/
|
*/
|
||||||
removedEnumKeys: string[];
|
removedEnumKeys?: string[];
|
||||||
|
[property: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,6 +80,8 @@ export interface EnumCleanupArgs {
|
|||||||
*/
|
*/
|
||||||
export enum JobType {
|
export enum JobType {
|
||||||
CustomPropertyEnumCleanup = "CUSTOM_PROPERTY_ENUM_CLEANUP",
|
CustomPropertyEnumCleanup = "CUSTOM_PROPERTY_ENUM_CLEANUP",
|
||||||
|
DeleteEntity = "DELETE_ENTITY",
|
||||||
|
DeleteToken = "DELETE_TOKEN",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -13,11 +13,37 @@
|
|||||||
|
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import AuthenticatedAppRouter from '../components/AppRouter/AuthenticatedAppRouter';
|
import AuthenticatedAppRouter from '../components/AppRouter/AuthenticatedAppRouter';
|
||||||
|
import { UnAuthenticatedAppRouter } from '../components/AppRouter/UnAuthenticatedAppRouter';
|
||||||
|
import { ROUTES } from '../constants/constants';
|
||||||
|
|
||||||
class ApplicationRoutesClassBase {
|
class ApplicationRoutesClassBase {
|
||||||
public getRouteElements(): FC {
|
public getRouteElements(): FC {
|
||||||
return AuthenticatedAppRouter;
|
return AuthenticatedAppRouter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getUnAuthenticatedRouteElements(): FC {
|
||||||
|
return UnAuthenticatedAppRouter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isProtectedRoute(pathname: string): boolean {
|
||||||
|
return (
|
||||||
|
[
|
||||||
|
ROUTES.SIGNUP,
|
||||||
|
ROUTES.SIGNIN,
|
||||||
|
ROUTES.FORGOT_PASSWORD,
|
||||||
|
ROUTES.CALLBACK,
|
||||||
|
ROUTES.SILENT_CALLBACK,
|
||||||
|
ROUTES.SAML_CALLBACK,
|
||||||
|
ROUTES.REGISTER,
|
||||||
|
ROUTES.RESET_PASSWORD,
|
||||||
|
ROUTES.ACCOUNT_ACTIVATION,
|
||||||
|
ROUTES.HOME,
|
||||||
|
ROUTES.AUTH_CALLBACK,
|
||||||
|
ROUTES.NOT_FOUND,
|
||||||
|
ROUTES.LOGOUT,
|
||||||
|
].indexOf(pathname) === -1
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const applicationRoutesClass = new ApplicationRoutesClassBase();
|
const applicationRoutesClass = new ApplicationRoutesClassBase();
|
||||||
|
|||||||
@ -296,26 +296,6 @@ export const getNameFromUserData = (
|
|||||||
return { name: userName, email: email, picture: user.picture };
|
return { name: userName, email: email, picture: user.picture };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isProtectedRoute = (pathname: string) => {
|
|
||||||
return (
|
|
||||||
[
|
|
||||||
ROUTES.SIGNUP,
|
|
||||||
ROUTES.SIGNIN,
|
|
||||||
ROUTES.FORGOT_PASSWORD,
|
|
||||||
ROUTES.CALLBACK,
|
|
||||||
ROUTES.SILENT_CALLBACK,
|
|
||||||
ROUTES.SAML_CALLBACK,
|
|
||||||
ROUTES.REGISTER,
|
|
||||||
ROUTES.RESET_PASSWORD,
|
|
||||||
ROUTES.ACCOUNT_ACTIVATION,
|
|
||||||
ROUTES.HOME,
|
|
||||||
ROUTES.AUTH_CALLBACK,
|
|
||||||
ROUTES.NOT_FOUND,
|
|
||||||
ROUTES.LOGOUT,
|
|
||||||
].indexOf(pathname) === -1
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isTourRoute = (pathname: string) => {
|
export const isTourRoute = (pathname: string) => {
|
||||||
return pathname === ROUTES.TOUR;
|
return pathname === ROUTES.TOUR;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -28,8 +28,8 @@ import ptBR from '../../locale/languages/pt-br.json';
|
|||||||
import ptPT from '../../locale/languages/pt-pt.json';
|
import ptPT from '../../locale/languages/pt-pt.json';
|
||||||
import ruRU from '../../locale/languages/ru-ru.json';
|
import ruRU from '../../locale/languages/ru-ru.json';
|
||||||
import thTH from '../../locale/languages/th-th.json';
|
import thTH from '../../locale/languages/th-th.json';
|
||||||
import zhCN from '../../locale/languages/zh-cn.json';
|
|
||||||
import trTR from '../../locale/languages/tr-tr.json';
|
import trTR from '../../locale/languages/tr-tr.json';
|
||||||
|
import zhCN from '../../locale/languages/zh-cn.json';
|
||||||
|
|
||||||
export enum SupportedLocales {
|
export enum SupportedLocales {
|
||||||
English = 'en-US',
|
English = 'en-US',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user