diff --git a/api-contract.md b/api-contract.md index a9a0fbd3..1dcc54c4 100644 --- a/api-contract.md +++ b/api-contract.md @@ -542,6 +542,7 @@ http://localhost:8080 "frequency": "string", "last_run_time": "timestamp", "last_run_state": "string", + "last_run_type": "string", "created_at": "timestamp", "updated_at": "timestamp", "activate": "boolean", @@ -578,7 +579,8 @@ http://localhost:8080 }, "frequency": "string", "streams_config": "json", - "activate": "boolean", // send this to activate or deactivate job + "difference_streams": "string", + "activate": "boolean" // send this to activate or deactivate job } ``` @@ -715,11 +717,12 @@ http://localhost:8080 "message": "string", "data": [ { - "id":"string", + "file_path": "string", "start_time": "timestamp", "runtime": "integer", - "status": "string" - }, + "status": "string", + "job_type": "string" + } ] } ``` @@ -814,6 +817,72 @@ http://localhost:8080 } ``` + +### Clear destination for a Job +--- + +- **Endpoint**: `/api/v1/project/:projectid/jobs/:id/clear-destination` +- **Method**: POST +- **Description**: clears the destination data +- **Headers**: `Authorization: Bearer ` + +- **Response**: + + ```json + { + "success": "boolean", + "message": "string", + "data": { + "message": "string" + } + } + ``` + +### Get Clear Destination Status +--- + +- **Endpoint**: `/api/v1/project/:projectid/jobs/:id/clear-destination` +- **Method**: GET +- **Description**: Get the status of clear destination operation for a job +- **Headers**: `Authorization: Bearer ` + +- **Response**: + + ```json + { + "success": "boolean", + "message": "string", + "data": { + "running": "boolean" + } + } + ``` + + ### Difference Streams +- **Endpoint**: `/api/v1/project/:projectid/jobs/:id/difference-streams` +- **Method**: POST +- **Description**: returns the stream difference bewtween the saved and the updated streams +- **Headers**: `Authorization: Bearer ` +- - **Request Body**: + + ```json + { + "updated_streams_config": "json" + } + ``` + +- **Response**: + + ```json + { + "success": "boolean", + "message": "string", + "data": { + "difference_streams": "json" + } + } + ``` + ## Error Responses All endpoints may return the following error responses: diff --git a/server/go.mod b/server/go.mod index 2be44495..cc3e8a91 100644 --- a/server/go.mod +++ b/server/go.mod @@ -5,20 +5,25 @@ go 1.24.2 require github.com/beego/beego/v2 v2.3.8 require ( + github.com/apache/spark-connect-go/v35 v35.0.0-20250317154112-ffd832059443 github.com/apache/spark-connect-go/v35 v35.0.0-20250317154112-ffd832059443 github.com/aws/aws-sdk-go-v2/config v1.29.17 github.com/aws/aws-sdk-go-v2/service/ecr v1.50.5 github.com/aws/aws-sdk-go-v2/service/kms v1.41.1 github.com/docker/docker v28.3.3+incompatible + github.com/docker/docker v28.3.3+incompatible github.com/go-playground/validator/v10 v10.27.0 github.com/lib/pq v1.10.9 github.com/oklog/ulid v1.3.1 github.com/rs/zerolog v1.34.0 github.com/spf13/viper v1.20.1 github.com/testcontainers/testcontainers-go v0.39.0 + github.com/testcontainers/testcontainers-go v0.39.0 go.temporal.io/sdk v1.34.0 golang.org/x/crypto v0.41.0 golang.org/x/mod v0.26.0 + golang.org/x/crypto v0.41.0 + golang.org/x/mod v0.26.0 ) require ( @@ -37,6 +42,12 @@ require ( ) require ( + cloud.google.com/go/compute/metadata v0.7.0 // indirect + dario.cat/mergo v1.0.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/apache/arrow-go/v18 v18.2.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect cloud.google.com/go/compute/metadata v0.7.0 // indirect dario.cat/mergo v1.0.2 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect @@ -48,22 +59,32 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/ebitengine/purego v0.8.4 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-errors/errors v1.5.1 // indirect + github.com/go-errors/errors v1.5.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/google/flatbuffers v25.2.10+incompatible // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.10 // indirect @@ -76,10 +97,17 @@ require ( github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect + github.com/moby/go-archive v0.1.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v4 v4.25.6 // indirect @@ -88,6 +116,13 @@ require ( github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shirou/gopsutil/v4 v4.25.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect @@ -100,12 +135,24 @@ require ( golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/tools v0.35.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.8.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/tools v0.35.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-viper/mapstructure/v2 v2.3.0 // indirect @@ -114,25 +161,31 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/jmoiron/sqlx v1.4.0 + github.com/jmoiron/sqlx v1.4.0 github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nexus-rpc/sdk-go v0.3.0 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.19.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/robfig/cron v1.2.0 // indirect + github.com/robfig/cron v1.2.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.14.0 // indirect + github.com/spf13/afero v1.14.0 // indirect github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.11.1 + github.com/stretchr/testify v1.11.1 github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect go.temporal.io/api v1.46.0 @@ -142,10 +195,18 @@ require ( golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.28.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.8.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/grpc v1.75.0 // indirect google.golang.org/protobuf v1.36.8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/grpc v1.75.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/server/go.sum b/server/go.sum index 4110f958..e3dd270a 100644 --- a/server/go.sum +++ b/server/go.sum @@ -3,12 +3,20 @@ cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeO cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= @@ -20,6 +28,16 @@ github.com/apache/spark-connect-go/v35 v35.0.0-20250317154112-ffd832059443 h1:pA github.com/apache/spark-connect-go/v35 v35.0.0-20250317154112-ffd832059443/go.mod h1:ODlxb8YN0y/JyS7h+vhz+afnQ+beSkYTqDHYtg2T6E8= github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/apache/arrow-go/v18 v18.2.0 h1:QhWqpgZMKfWOniGPhbUxrHohWnooGURqL2R2Gg4SO1Q= +github.com/apache/arrow-go/v18 v18.2.0/go.mod h1:Ic/01WSwGJWRrdAZcxjBZ5hbApNJ28K96jGYaxzzGUc= +github.com/apache/spark-connect-go/v35 v35.0.0-20250317154112-ffd832059443 h1:pA4aHBVygvcQZuXVSJg2kH3z0rZO3M/YJUyUuPX82ko= +github.com/apache/spark-connect-go/v35 v35.0.0-20250317154112-ffd832059443/go.mod h1:ODlxb8YN0y/JyS7h+vhz+afnQ+beSkYTqDHYtg2T6E8= +github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= +github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I= github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0= @@ -57,6 +75,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -79,16 +99,24 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -107,6 +135,8 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -116,6 +146,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -147,8 +179,13 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -157,10 +194,14 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= @@ -169,6 +210,12 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -198,6 +245,10 @@ github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpsp github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -206,6 +257,10 @@ github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= @@ -216,6 +271,12 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/nexus-rpc/sdk-go v0.3.0 h1:Y3B0kLYbMhd4C2u00kcYajvmOrfozEtTV/nHSnV57jA= @@ -231,6 +292,8 @@ github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNH github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -239,6 +302,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -261,6 +328,8 @@ github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94 github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= +github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -268,6 +337,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= @@ -284,6 +355,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/testcontainers/testcontainers-go v0.39.0 h1:uCUJ5tA+fcxbFAB0uP3pIK3EJ2IjjDUHFSZ1H1UxAts= @@ -292,6 +365,12 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/testcontainers/testcontainers-go v0.39.0 h1:uCUJ5tA+fcxbFAB0uP3pIK3EJ2IjjDUHFSZ1H1UxAts= +github.com/testcontainers/testcontainers-go v0.39.0/go.mod h1:qmHpkG7H5uPf/EvOORKvS6EuDkBUPE3zpVGaH9NL7f8= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -303,6 +382,12 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= @@ -323,6 +408,24 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= +go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= go.temporal.io/api v1.46.0 h1:O1efPDB6O2B8uIeCDIa+3VZC7tZMvYsMZYQapSbHvCg= go.temporal.io/api v1.46.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.34.0 h1:VLg/h6ny7GvLFVoQPqz2NcC93V9yXboQwblkRvZ1cZE= @@ -340,9 +443,13 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -352,6 +459,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -363,9 +472,13 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -374,17 +487,22 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -397,10 +515,14 @@ golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -415,6 +537,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -423,6 +547,10 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -432,6 +560,10 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1: google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -441,6 +573,10 @@ google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/server/internal/constants/constants.go b/server/internal/constants/constants.go index 8eb0d0eb..436d6000 100644 --- a/server/internal/constants/constants.go +++ b/server/internal/constants/constants.go @@ -3,6 +3,7 @@ package constants import ( "fmt" "strings" + "time" "github.com/beego/beego/v2/core/config" "github.com/beego/beego/v2/server/web" @@ -24,6 +25,7 @@ var ( DefaultLogRetentionPeriod = 30 DefaultSpecVersion = "v0.2.0" DefaultTemporalAddress = "localhost:7233" + DefaultCancelSyncWaitTime = 30 * time.Second // logging EnvLogLevel = "LOG_LEVEL" EnvLogFormat = "LOG_FORMAT" diff --git a/server/internal/constants/messages.go b/server/internal/constants/messages.go index 2629190e..0fc828c2 100644 --- a/server/internal/constants/messages.go +++ b/server/internal/constants/messages.go @@ -36,6 +36,7 @@ var ( ErrFailedToCreate = errors.New("failed to create") ErrFailedToUpdate = errors.New("failed to update") ErrFailedToDelete = errors.New("failed to delete") + ErrInProgress = errors.New("in progress") ) // Success messages diff --git a/server/internal/handlers/job.go b/server/internal/handlers/job.go index e929de78..590908a3 100644 --- a/server/internal/handlers/job.go +++ b/server/internal/handlers/job.go @@ -260,6 +260,83 @@ func (h *Handler) CancelJobRun() { utils.SuccessResponse(&h.Controller, fmt.Sprintf("job workflow cancel requested successfully for job_id[%d]", id), nil) } +// @router /project/:projectid/jobs/:id/clear-destination [post] +func (h *Handler) ClearDestination() { + projectID, err := GetProjectIDFromPath(&h.Controller) + if err != nil { + utils.ErrorResponse(&h.Controller, http.StatusBadRequest, fmt.Sprintf("failed to validate request: %s", err), err) + return + } + + id, err := GetIDFromPath(&h.Controller) + if err != nil { + utils.ErrorResponse(&h.Controller, http.StatusBadRequest, fmt.Sprintf("failed to validate request: %s", err), err) + return + } + result, err := h.etl.ClearDestination(h.Ctx.Request.Context(), projectID, id, "", 0) + if err != nil { + utils.ErrorResponse(&h.Controller, http.StatusInternalServerError, fmt.Sprintf("failed to trigger clear destination: %s", err), err) + return + } + utils.SuccessResponse(&h.Controller, fmt.Sprintf("clear destination triggered successfully for job_id[%d]", id), result) +} + +// @router /project/:projectid/jobs/:id/stream-difference [post] +func (h *Handler) GetStreamDifference() { + projectID, err := GetProjectIDFromPath(&h.Controller) + if err != nil { + utils.ErrorResponse(&h.Controller, http.StatusBadRequest, fmt.Sprintf("failed to validate request: %s", err), err) + return + } + + id, err := GetIDFromPath(&h.Controller) + if err != nil { + utils.ErrorResponse(&h.Controller, http.StatusBadRequest, fmt.Sprintf("failed to validate request: %s", err), err) + return + } + + var req dto.StreamDifferenceRequest + if err := UnmarshalAndValidate(h.Ctx.Input.RequestBody, &req); err != nil { + utils.ErrorResponse(&h.Controller, http.StatusBadRequest, fmt.Sprintf("failed to validate request: %s", err), err) + return + } + + logger.Debugf("Get stream difference initiated project_id[%s] job_id[%d]", projectID, id) + + diffStreams, err := h.etl.GetStreamDifference(h.Ctx.Request.Context(), projectID, id, req) + if err != nil { + utils.ErrorResponse(&h.Controller, http.StatusInternalServerError, "Failed to get stream difference", err) + return + } + utils.SuccessResponse(&h.Controller, fmt.Sprintf("stream difference retrieved successfully for job_id[%d]", id), dto.StreamDifferenceResponse{ + DifferenceStreams: diffStreams, + }) +} + +// @router /project/:projectid/jobs/:id/clear-destination [get] +func (h *Handler) GetClearDestinationStatus() { + projectID, err := GetProjectIDFromPath(&h.Controller) + if err != nil { + utils.ErrorResponse(&h.Controller, http.StatusBadRequest, fmt.Sprintf("failed to validate request: %s", err), err) + return + } + + jobID, err := GetIDFromPath(&h.Controller) + if err != nil { + utils.ErrorResponse(&h.Controller, http.StatusBadRequest, fmt.Sprintf("failed to validate request: %s", err), err) + return + } + + status, err := h.etl.GetClearDestinationStatus(h.Ctx.Request.Context(), projectID, jobID) + if err != nil { + utils.ErrorResponse(&h.Controller, http.StatusInternalServerError, fmt.Sprintf("failed to get clear destination status: %s", err), err) + return + } + utils.SuccessResponse(&h.Controller, fmt.Sprintf("clear destination status retrieved successfully for job_id[%d]", jobID), dto.ClearDestinationStatusResponse{ + Running: status, + }) +} + // @router /project/:projectid/jobs/:id/tasks [get] func (h *Handler) GetJobTasks() { projectID, err := GetProjectIDFromPath(&h.Controller) diff --git a/server/internal/models/dto/requests.go b/server/internal/models/dto/requests.go index c09b07e9..9bc718f9 100644 --- a/server/internal/models/dto/requests.go +++ b/server/internal/models/dto/requests.go @@ -87,7 +87,19 @@ type CreateJobRequest struct { Activate bool `json:"activate,omitempty"` } -type UpdateJobRequest = CreateJobRequest +type UpdateJobRequest struct { + Name string `json:"name" validate:"required"` + Source *DriverConfig `json:"source" validate:"required"` + Destination *DriverConfig `json:"destination" validate:"required"` + Frequency string `json:"frequency" validate:"required"` + StreamsConfig string `json:"streams_config" orm:"type(jsonb)" validate:"required"` + DifferenceStreams string `json:"difference_streams,omitempty"` + Activate bool `json:"activate,omitempty"` +} + +type StreamDifferenceRequest struct { + UpdatedStreamsConfig string `json:"updated_streams_config" validate:"required"` +} type JobTaskRequest struct { FilePath string `json:"file_path" validate:"required"` diff --git a/server/internal/models/dto/response.go b/server/internal/models/dto/response.go index 83a73d7b..2cf64c4e 100644 --- a/server/internal/models/dto/response.go +++ b/server/internal/models/dto/response.go @@ -50,6 +50,14 @@ type TestConnectionResponse struct { Logs []map[string]interface{} `json:"logs"` } +type StreamDifferenceResponse struct { + DifferenceStreams map[string]interface{} `json:"difference_streams"` +} + +type ClearDestinationStatusResponse struct { + Running bool `json:"running"` +} + // Job response type JobResponse struct { ID int `json:"id"` @@ -60,6 +68,7 @@ type JobResponse struct { Frequency string `json:"frequency"` LastRunTime string `json:"last_run_time,omitempty"` LastRunState string `json:"last_run_state,omitempty"` + LastRunType string `json:"last_run_type,omitempty"` // "sync" | "clear-destination" CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` Activate bool `json:"activate"` @@ -72,6 +81,7 @@ type JobTask struct { StartTime string `json:"start_time"` Status string `json:"status"` FilePath string `json:"file_path"` + JobType string `json:"job_type"` // "sync" | "clear-destination" } type SourceDataItem struct { diff --git a/server/internal/services/etl/job.go b/server/internal/services/etl/job.go index 9da89f6d..553a9eb4 100644 --- a/server/internal/services/etl/job.go +++ b/server/internal/services/etl/job.go @@ -3,6 +3,7 @@ package services import ( "context" "crypto/sha256" + "encoding/json" "fmt" "path/filepath" "strings" @@ -11,6 +12,7 @@ import ( "github.com/datazip-inc/olake-ui/server/internal/constants" "github.com/datazip-inc/olake-ui/server/internal/models" "github.com/datazip-inc/olake-ui/server/internal/models/dto" + "github.com/datazip-inc/olake-ui/server/internal/services/temporal" "github.com/datazip-inc/olake-ui/server/utils" "github.com/datazip-inc/olake-ui/server/utils/logger" "github.com/datazip-inc/olake-ui/server/utils/telemetry" @@ -94,6 +96,33 @@ func (s *ETLService) UpdateJob(ctx context.Context, req *dto.UpdateJobRequest, p return fmt.Errorf("failed to get job: %s", err) } + // Block when clear-destination is running + if clearRunning, _, _ := isWorkflowRunning(ctx, s.temporal, projectID, jobID, temporal.ClearDestination); clearRunning { + return fmt.Errorf("clear-destination is in progress, cannot update job") + } + + // Cancel sync before updating the job + if err := cancelAllJobWorkflows(ctx, s.temporal, []*models.Job{existingJob}, projectID); err != nil { + return fmt.Errorf("failed to cancel sync: %s", err) + } + + // Handle stream difference if provided + // TODO: handle clear-destination workflow failure + if req.DifferenceStreams != "" { + var diffCatalog map[string]interface{} + if err := json.Unmarshal([]byte(req.DifferenceStreams), &diffCatalog); err != nil { + return fmt.Errorf("invalid difference_streams JSON: %s", err) + } + + if len(diffCatalog) > 0 { + logger.Infof("stream difference detected for job %d, running clear destination workflow", existingJob.ID) + if _, err := s.ClearDestination(ctx, projectID, jobID, req.DifferenceStreams, constants.DefaultCancelSyncWaitTime); err != nil { + return fmt.Errorf("failed to run clear destination workflow: %s", err) + } + logger.Infof("successfully triggered clear destination workflow for job %d", existingJob.ID) + } + } + // Snapshot previous job state for compensation on schedule update failure prevJob := *existingJob @@ -115,16 +144,12 @@ func (s *ETLService) UpdateJob(ctx context.Context, req *dto.UpdateJobRequest, p existingJob.StreamsConfig = req.StreamsConfig existingJob.ProjectID = projectID existingJob.UpdatedBy = &models.User{ID: *userID} - // cancel existing workflow - err = cancelAllJobWorkflows(ctx, s.temporal, []*models.Job{existingJob}, projectID) - if err != nil { - return fmt.Errorf("failed to cancel workflow for job %s", err) - } + if err := s.db.UpdateJob(existingJob); err != nil { return fmt.Errorf("failed to update job: %s", err) } - err = s.temporal.UpdateSchedule(ctx, existingJob.Frequency, existingJob.ProjectID, existingJob.ID) + err = s.temporal.UpdateSchedule(ctx, existingJob.Frequency, existingJob.ProjectID, existingJob.ID, nil) if err != nil { // Compensation: restore previous DB state if schedule update fails if rerr := s.db.UpdateJob(&prevJob); rerr != nil { @@ -156,6 +181,15 @@ func (s *ETLService) DeleteJob(ctx context.Context, jobID int) (string, error) { } func (s *ETLService) SyncJob(ctx context.Context, projectID string, jobID int) (interface{}, error) { + job, err := s.db.GetJobByID(jobID, true) + if err != nil { + return nil, fmt.Errorf("failed to find job: %s", err) + } + + if !job.Active { + return nil, fmt.Errorf("job is paused, please unpause to run sync") + } + if err := s.temporal.TriggerSchedule(ctx, projectID, jobID); err != nil { return nil, fmt.Errorf("failed to trigger sync: %s", err) } @@ -209,6 +243,61 @@ func (s *ETLService) ActivateJob(ctx context.Context, jobID int, req dto.JobStat return nil } +func (s *ETLService) ClearDestination(ctx context.Context, projectID string, jobID int, streamsConfig string, syncWaitTime time.Duration) (map[string]interface{}, error) { + job, err := s.db.GetJobByID(jobID, true) + if err != nil { + return nil, fmt.Errorf("job not found: %s", err) + } + + if !job.Active { + return nil, fmt.Errorf("job is paused, please unpause to run clear destination") + } + + // Check if sync is running and wait for it to stop + if running, _, _ := isWorkflowRunning(ctx, s.temporal, projectID, jobID, temporal.Sync); running { + if err := waitForSyncToStop(ctx, s.temporal, projectID, jobID, syncWaitTime); err != nil { + logger.Infof("failed to wait for sync to stop: %s", err) + return nil, fmt.Errorf("sync is in progress, please cancel it before running clear-destination") + } + } + + if err := s.temporal.ClearDestination(ctx, job, streamsConfig); err != nil { + return nil, err + } + + return map[string]interface{}{ + "message": "Clear destination initiated successfully", + }, nil +} + +func (s *ETLService) GetStreamDifference(ctx context.Context, projectID string, jobID int, req dto.StreamDifferenceRequest) (map[string]interface{}, error) { + job, err := s.db.GetJobByID(jobID, true) + if err != nil { + return nil, fmt.Errorf("job not found: %s", err) + } + + diffCatalog, err := s.temporal.GetStreamDifference(ctx, job, job.StreamsConfig, req.UpdatedStreamsConfig) + if err != nil { + return nil, fmt.Errorf("failed to get stream difference: %s", err) + } + + return diffCatalog, nil +} + +func (s *ETLService) GetClearDestinationStatus(ctx context.Context, projectID string, jobID int) (bool, error) { + _, err := s.db.GetJobByID(jobID, true) + if err != nil { + return false, fmt.Errorf("job not found: %s", err) + } + + isClearRunning, _, err := isWorkflowRunning(ctx, s.temporal, projectID, jobID, temporal.ClearDestination) + if err != nil { + return false, fmt.Errorf("failed to check if clear destination is running: %s", err) + } + + return isClearRunning, nil +} + func (s *ETLService) CheckUniqueJobName(_ context.Context, projectID string, req dto.CheckUniqueJobNameRequest) (bool, error) { unique, err := s.db.IsJobNameUniqueInProject(projectID, req.JobName) if err != nil { @@ -242,11 +331,15 @@ func (s *ETLService) GetJobTasks(ctx context.Context, projectID string, jobID in } else { runTime = time.Since(startTime).Round(time.Second).String() } + + opType := syncWorkflowOperationType(execution) + jobType := utils.Ternary(opType == temporal.Sync, "sync", "clear").(string) tasks = append(tasks, dto.JobTask{ Runtime: runTime, StartTime: startTime.Format(time.RFC3339), Status: execution.Status.String(), FilePath: execution.Execution.WorkflowId, + JobType: jobType, }) } @@ -320,8 +413,10 @@ func (s *ETLService) buildJobResponse(ctx context.Context, job *models.Job, proj return dto.JobResponse{}, fmt.Errorf("failed to list workflows: %s", err) } if len(resp.Executions) > 0 { + opType := syncWorkflowOperationType(resp.Executions[0]) jobResp.LastRunTime = resp.Executions[0].StartTime.AsTime().Format(time.RFC3339) jobResp.LastRunState = resp.Executions[0].Status.String() + jobResp.LastRunType = utils.Ternary(opType == temporal.Sync, "sync", "clear").(string) } return jobResp, nil diff --git a/server/internal/services/etl/utils.go b/server/internal/services/etl/utils.go index 9229f541..178a9df9 100644 --- a/server/internal/services/etl/utils.go +++ b/server/internal/services/etl/utils.go @@ -9,7 +9,11 @@ import ( "github.com/datazip-inc/olake-ui/server/internal/models" "github.com/datazip-inc/olake-ui/server/internal/models/dto" "github.com/datazip-inc/olake-ui/server/internal/services/temporal" + commonpb "go.temporal.io/api/common/v1" + enumspb "go.temporal.io/api/enums/v1" + "go.temporal.io/api/workflow/v1" "go.temporal.io/api/workflowservice/v1" + "go.temporal.io/sdk/converter" ) func cancelAllJobWorkflows(ctx context.Context, tempClient *temporal.Temporal, jobs []*models.Job, projectID string) error { @@ -21,8 +25,8 @@ func cancelAllJobWorkflows(ctx context.Context, tempClient *temporal.Temporal, j var conditions []string for _, job := range jobs { conditions = append(conditions, fmt.Sprintf( - "(WorkflowId BETWEEN 'sync-%s-%d' AND 'sync-%s-%d-~')", - projectID, job.ID, projectID, job.ID, + "(WorkflowId BETWEEN 'sync-%s-%d' AND 'sync-%s-%d-~' AND OperationType != '%s')", + projectID, job.ID, projectID, job.ID, temporal.ClearDestination, )) } @@ -108,3 +112,78 @@ func setJobWorkflowInfo(jobInfo *dto.JobDataItem, jobID int, projectID string, t } return nil } + +// Checks if the sync worklfow run is "sync" or "clear-destination" +func syncWorkflowOperationType(execution *workflow.WorkflowExecutionInfo) temporal.Command { + if execution.SearchAttributes == nil { + return temporal.Sync + } + + opTypePayload, ok := execution.SearchAttributes.IndexedFields["OperationType"] + if !ok || opTypePayload == nil { + return temporal.Sync + } + + var opType string + if err := converter.GetDefaultDataConverter().FromPayload(opTypePayload, &opType); err == nil { + return temporal.Command(opType) + } + + return temporal.Sync +} + +// isWorkflowRunning checks if workflows of a specific type are running +func isWorkflowRunning(ctx context.Context, tempClient *temporal.Temporal, projectID string, jobID int, opType temporal.Command) (bool, []*workflow.WorkflowExecutionInfo, error) { + query := fmt.Sprintf( + "WorkflowId BETWEEN 'sync-%s-%d' AND 'sync-%s-%d-~' AND OperationType = '%s' AND ExecutionStatus = 'Running'", + projectID, jobID, projectID, jobID, opType, + ) + + resp, err := tempClient.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{ + Query: query, + PageSize: 1, + }) + if err != nil { + return false, nil, err + } + return len(resp.Executions) > 0, resp.Executions, nil +} + +// waitForSyncToStop waits for sync workflows to stop with timeout +func waitForSyncToStop(ctx context.Context, tempClient *temporal.Temporal, projectID string, jobID int, maxWaitTime time.Duration) error { + if maxWaitTime <= 0 { + return fmt.Errorf("wait timeout is 0, skipping sync wait") + } + + timedCtx, cancel := context.WithTimeout(ctx, maxWaitTime) + defer cancel() + + isSyncRunning, executions, err := isWorkflowRunning(timedCtx, tempClient, projectID, jobID, temporal.Sync) + if err != nil { + return fmt.Errorf("failed to check sync status: %s", err) + } + if !isSyncRunning { + return nil + } + + workflowID := executions[0].Execution.WorkflowId + runID := executions[0].Execution.RunId + + _, err = tempClient.Client.WorkflowService().GetWorkflowExecutionHistory( + timedCtx, + &workflowservice.GetWorkflowExecutionHistoryRequest{ + Namespace: "default", + Execution: &commonpb.WorkflowExecution{ + WorkflowId: workflowID, + RunId: runID, + }, + WaitNewEvent: true, + HistoryEventFilterType: enumspb.HISTORY_EVENT_FILTER_TYPE_CLOSE_EVENT, + }, + ) + if err != nil || timedCtx.Err() != nil { + return fmt.Errorf("timeout waiting for sync to stop after %v", maxWaitTime) + } + + return nil +} diff --git a/server/internal/services/temporal/client.go b/server/internal/services/temporal/client.go index ffead967..f6a193f4 100644 --- a/server/internal/services/temporal/client.go +++ b/server/internal/services/temporal/client.go @@ -80,17 +80,31 @@ func (t *Temporal) CreateSchedule(ctx context.Context, job *models.Job) error { return err } -// updateSchedule updates an existing schedule -func (t *Temporal) UpdateSchedule(ctx context.Context, frequency, projectID string, jobID int) error { - cronExpression := utils.ToCron(frequency) +// UpdateScheduleSpec updates an existing schedule's spec +func (t *Temporal) UpdateSchedule(ctx context.Context, frequency, projectID string, jobID int, args *ExecutionRequest) error { + _, scheduleID := t.WorkflowAndScheduleID(projectID, jobID) handle := t.Client.ScheduleClient().GetHandle(ctx, scheduleID) return handle.Update(ctx, client.ScheduleUpdateOptions{ DoUpdate: func(input client.ScheduleUpdateInput) (*client.ScheduleUpdate, error) { - input.Description.Schedule.Spec = &client.ScheduleSpec{ - CronExpressions: []string{cronExpression}, + if frequency != "" { + cronExpression := utils.ToCron(frequency) + input.Description.Schedule.Spec = &client.ScheduleSpec{ + CronExpressions: []string{cronExpression}, + } + } + + // update schedule action + if args != nil { + input.Description.Schedule.Action = &client.ScheduleWorkflowAction{ + ID: args.WorkflowID, + Workflow: RunSyncWorkflow, + Args: []any{*args}, + TaskQueue: t.taskQueue, + } } + return &client.ScheduleUpdate{ Schedule: &input.Description.Schedule, }, nil diff --git a/server/internal/services/temporal/execute.go b/server/internal/services/temporal/execute.go index a8bb4e88..696f195f 100644 --- a/server/internal/services/temporal/execute.go +++ b/server/internal/services/temporal/execute.go @@ -7,23 +7,12 @@ import ( "github.com/beego/beego/v2/server/web" "github.com/datazip-inc/olake-ui/server/internal/constants" + "github.com/datazip-inc/olake-ui/server/internal/models" "github.com/datazip-inc/olake-ui/server/internal/models/dto" "go.temporal.io/sdk/client" "golang.org/x/mod/semver" ) -const ( - RunSyncWorkflow = "RunSyncWorkflow" - ExecuteWorkflow = "ExecuteWorkflow" -) - -type Command string - -type JobConfig struct { - Name string `json:"name"` - Data string `json:"data"` -} - type ExecutionRequest struct { Type string `json:"type"` Command Command `json:"command"` @@ -32,16 +21,28 @@ type ExecutionRequest struct { Args []string `json:"args"` Configs []JobConfig `json:"configs"` WorkflowID string `json:"workflow_id"` + ProjectID string `json:"project_id"` JobID int `json:"job_id"` Timeout time.Duration `json:"timeout"` OutputFile string `json:"output_file"` // to get the output file from the workflow } +type JobConfig struct { + Name string `json:"name"` + Data string `json:"data"` +} + +type Command string + const ( - Discover Command = "discover" - Check Command = "check" - Sync Command = "sync" - Spec Command = "spec" + Discover Command = "discover" + Check Command = "check" + Sync Command = "sync" + Spec Command = "spec" + ClearDestination Command = "clear-destination" + + RunSyncWorkflow = "RunSyncWorkflow" + ExecuteWorkflow = "ExecuteWorkflow" ) // DiscoverStreams runs a workflow to discover catalog data @@ -208,3 +209,76 @@ func (t *Temporal) VerifyDriverCredentials(ctx context.Context, workflowID, flag "status": status, }, nil } + +func (t *Temporal) ClearDestination(ctx context.Context, job *models.Job, streamsConfig string) error { + workflowID, scheduleID := t.WorkflowAndScheduleID(job.ProjectID, job.ID) + + handle := t.Client.ScheduleClient().GetHandle(ctx, scheduleID) + if _, err := handle.Describe(ctx); err != nil { + return fmt.Errorf("schedule does not exist: %s", err) + } + + if err := t.PauseSchedule(ctx, job.ProjectID, job.ID); err != nil { + return fmt.Errorf("failed to pause sync schedule: %s", err) + } + + // update schedule to use clear-destination request + // unpause and update args back to sync is performed in the activity cleanup + clearReq := buildExecutionReqForClearDestination(job, workflowID, streamsConfig) + err := t.UpdateSchedule(ctx, job.Frequency, job.ProjectID, job.ID, &clearReq) + if err != nil { + _ = t.ResumeSchedule(ctx, job.ProjectID, job.ID) + return fmt.Errorf("failed to update schedule for clear-destination: %s", err) + } + + return t.TriggerSchedule(ctx, job.ProjectID, job.ID) +} + +// GetStreamDifference compares old and new stream configs and returns the difference +func (t *Temporal) GetStreamDifference(ctx context.Context, job *models.Job, oldConfig, newConfig string) (map[string]interface{}, error) { + workflowID := fmt.Sprintf("difference-%s-%d-%d", job.ProjectID, job.ID, time.Now().Unix()) + + configs := []JobConfig{ + {Name: "old_streams.json", Data: oldConfig}, + {Name: "new_streams.json", Data: newConfig}, + } + + cmdArgs := []string{ + "discover", + "--streams", "/mnt/config/old_streams.json", + "--difference", "/mnt/config/new_streams.json", + } + if encryptionKey, _ := web.AppConfig.String(constants.ConfEncryptionKey); encryptionKey != "" { + cmdArgs = append(cmdArgs, "--encryption-key", encryptionKey) + } + + req := &ExecutionRequest{ + Type: "docker", + Command: Discover, + ConnectorType: job.SourceID.Type, + Version: job.SourceID.Version, + Args: cmdArgs, + Configs: configs, + WorkflowID: workflowID, + JobID: job.ID, + Timeout: GetWorkflowTimeout(Discover), + OutputFile: "difference_streams.json", + } + + workflowOptions := client.StartWorkflowOptions{ + ID: workflowID, + TaskQueue: t.taskQueue, + } + + run, err := t.Client.ExecuteWorkflow(ctx, workflowOptions, ExecuteWorkflow, req) + if err != nil { + return nil, fmt.Errorf("failed to execute stream difference workflow: %s", err) + } + + result, err := ExtractWorkflowResponse(ctx, run) + if err != nil { + return nil, fmt.Errorf("failed to extract workflow response: %v", err) + } + + return result, nil +} diff --git a/server/internal/services/temporal/utils.go b/server/internal/services/temporal/utils.go index 1c234bb4..c76f36b8 100644 --- a/server/internal/services/temporal/utils.go +++ b/server/internal/services/temporal/utils.go @@ -34,6 +34,41 @@ func buildExecutionReqForSync(job *models.Job, workflowID string) ExecutionReque } } +// buildExecutionReqForClearDestination builds the ExecutionRequest for a clear-destination job +func buildExecutionReqForClearDestination(job *models.Job, workflowID string, streamsConfig string) ExecutionRequest { + catalog := streamsConfig + if catalog == "" { + catalog = job.StreamsConfig + } + + configs := []JobConfig{ + {Name: "streams.json", Data: catalog}, + {Name: "state.json", Data: job.State}, + {Name: "destination.json", Data: job.DestID.Config}, + } + + args := []string{ + "clear-destination", + "--streams", "/mnt/config/streams.json", + "--state", "/mnt/config/state.json", + "--destination", "/mnt/config/destination.json", + } + + return ExecutionRequest{ + Type: "docker", + Command: ClearDestination, + ConnectorType: job.SourceID.Type, + Version: job.SourceID.Version, + Args: args, + Configs: configs, + WorkflowID: workflowID, + ProjectID: job.ProjectID, + JobID: job.ID, + Timeout: GetWorkflowTimeout(ClearDestination), + OutputFile: "state.json", + } +} + // extractWorkflowResponse extracts and parses the JSON response from a workflow execution result func ExtractWorkflowResponse(ctx context.Context, run client.WorkflowRun) (map[string]interface{}, error) { var result map[string]interface{} @@ -64,6 +99,8 @@ func GetWorkflowTimeout(op Command) time.Duration { return time.Minute * 5 case Sync: return time.Hour * 24 * 30 + case ClearDestination: + return time.Hour * 24 * 30 // check what can the fallback time be default: return time.Minute * 5 diff --git a/server/routes/router.go b/server/routes/router.go index 3dc17344..cad7ec74 100644 --- a/server/routes/router.go +++ b/server/routes/router.go @@ -88,6 +88,9 @@ func Init(h *handlers.Handler) { web.Router("/api/v1/project/:projectid/jobs/:id/cancel", h, "get:CancelJobRun") web.Router("/api/v1/project/:projectid/jobs/:id/tasks/:taskid/logs", h, "post:GetTaskLogs") web.Router("/api/v1/project/:projectid/jobs/check-unique", h, "post:CheckUniqueJobName") + web.Router("/api/v1/project/:projectid/jobs/:id/clear-destination", h, "post:ClearDestination") + web.Router("/api/v1/project/:projectid/jobs/:id/clear-destination", h, "get:GetClearDestinationStatus") + web.Router("/api/v1/project/:projectid/jobs/:id/stream-difference", h, "post:GetStreamDifference") // worker callback routes web.Router("/internal/worker/callback/sync-telemetry", h, "post:UpdateSyncTelemetry") diff --git a/ui/src/api/services/jobService.ts b/ui/src/api/services/jobService.ts index 40bfa184..7b3763d2 100644 --- a/ui/src/api/services/jobService.ts +++ b/ui/src/api/services/jobService.ts @@ -1,6 +1,14 @@ import api from "../axios" import { API_CONFIG } from "../config" -import { Job, JobBase, JobTask, TaskLog } from "../../types" +import { + APIResponse, + Job, + JobBase, + JobTask, + StreamsDataStructure, + TaskLog, +} from "../../types" +import { AxiosError } from "axios" export const jobService = { getJobs: async (): Promise => { @@ -34,7 +42,7 @@ export const jobService = { const response = await api.put( `${API_CONFIG.ENDPOINTS.JOBS(API_CONFIG.PROJECT_ID)}/${id}`, job, - { showNotification: true }, + { timeout: 30000, showNotification: true }, ) return response.data } catch (error) { @@ -78,7 +86,9 @@ export const jobService = { return response.data } catch (error) { console.error("Error syncing job:", error) - throw error + throw error instanceof AxiosError && error.response?.data.message + ? error.response?.data.message + : "Failed to sync job" } }, @@ -140,4 +150,37 @@ export const jobService = { throw error } }, + + clearDestination: async ( + jobId: string, + ): Promise> => { + try { + const response = await api.post>( + `${API_CONFIG.ENDPOINTS.JOBS(API_CONFIG.PROJECT_ID)}/${jobId}/clear-destination`, + ) + + return response.data + } catch (error) { + console.error("Error clearing destination:", error) + throw error + } + }, + getStreamDifference: async ( + jobId: string, + streamsConfig: string, + ): Promise<{ difference_streams: StreamsDataStructure }> => { + try { + const response = await api.post<{ + difference_streams: StreamsDataStructure + }>( + `${API_CONFIG.ENDPOINTS.JOBS(API_CONFIG.PROJECT_ID)}/${jobId}/stream-difference`, + { updated_streams_config: streamsConfig }, + {timeout: 30000} + ) + return response.data + } catch (error) { + console.error("Error getting stream difference:", error) + throw error + } + }, } diff --git a/ui/src/modules/common/Modals/ClearDestinationAndSyncModal.tsx b/ui/src/modules/common/Modals/ClearDestinationAndSyncModal.tsx deleted file mode 100644 index 71f9c3be..00000000 --- a/ui/src/modules/common/Modals/ClearDestinationAndSyncModal.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useNavigate } from "react-router-dom" -import { WarningIcon } from "@phosphor-icons/react" -import { Button, message, Modal } from "antd" -import { useAppStore } from "../../../store" - -const ClearDestinationAndSyncModal = () => { - const { - showClearDestinationAndSyncModal, - setShowClearDestinationAndSyncModal, - } = useAppStore() - const navigate = useNavigate() - - return ( - -
- - -
- Clear destination and sync deletes all the data in your destination - and sync your job -
- -
- - -
-
-
- ) -} - -export default ClearDestinationAndSyncModal diff --git a/ui/src/modules/common/Modals/ClearDestinationModal.tsx b/ui/src/modules/common/Modals/ClearDestinationModal.tsx new file mode 100644 index 00000000..5783a731 --- /dev/null +++ b/ui/src/modules/common/Modals/ClearDestinationModal.tsx @@ -0,0 +1,80 @@ +import { useNavigate } from "react-router-dom" +import { WarningIcon } from "@phosphor-icons/react" +import { Button, message, Modal } from "antd" +import { useAppStore } from "../../../store" +import { jobService } from "../../../api" +import { useState } from "react" + +const ClearDestinationModal = () => { + const { + showClearDestinationModal, + setShowClearDestinationModal, + selectedJobId, + fetchJobs, + } = useAppStore() + const navigate = useNavigate() + + const [isLoading, setIsLoading] = useState(false) + + const handleClearDestination = async () => { + if (!selectedJobId) { + message.error("No job selected") + return + } + setIsLoading(true) + try { + await jobService.clearDestination(selectedJobId) + // wait for 1 second before refreshing jobs to avoid fetching old state + await new Promise(resolve => setTimeout(resolve, 1000)) + await fetchJobs() + navigate(`/jobs/${selectedJobId}/history`) + } catch (error) { + console.error("Failed to clear destination", error) + } finally { + setShowClearDestinationModal(false) + setIsLoading(false) + } + } + + return ( + +
+ + +
+ This will erase all data that was synced by this job in the + destination. This action{" "} + cannot be undone. Are you sure you + want to proceed? +
+ +
+ + +
+
+
+ ) +} + +export default ClearDestinationModal diff --git a/ui/src/modules/common/Modals/StreamDifferenceModal.tsx b/ui/src/modules/common/Modals/StreamDifferenceModal.tsx new file mode 100644 index 00000000..8d9e7b04 --- /dev/null +++ b/ui/src/modules/common/Modals/StreamDifferenceModal.tsx @@ -0,0 +1,106 @@ +import { InfoIcon, WarningIcon } from "@phosphor-icons/react" +import { Button, Modal } from "antd" +import { useAppStore } from "../../../store" +import { StreamDifferenceModalProps } from "../../../types/modalTypes" + +const StreamDifferenceModal = ({ + streamDifference, + onConfirm, +}: StreamDifferenceModalProps) => { + const { showStreamDifferenceModal, setShowStreamDifferenceModal } = + useAppStore() + + const handleCloseModal = () => { + setShowStreamDifferenceModal(false) + } + + const handleFinish = () => { + onConfirm() + setShowStreamDifferenceModal(false) + } + + const renderStreamsByNamespace = () => { + const namespaces = Object.keys(streamDifference.selected_streams) + + return namespaces.map(namespace => { + const streams = streamDifference.selected_streams[namespace] + + if (!streams || streams.length === 0) return null + + return ( +
    +
  • + {namespace} +
      + {streams.map((stream, index) => ( +
    • + {stream.stream_name} +
    • + ))} +
    +
  • +
+ ) + }) + } + + return ( + <> + + + + } + open={showStreamDifferenceModal} + onCancel={handleCloseModal} + footer={[ + , + , + ]} + centered + width="30%" + > +
+

+ Are you sure you want to continue? +

+

+ Modifying stream configurations will clear destination data for the + impacted streams. Following streams will be impacted: +

+
+ + Any ongoing sync will be auto cancelled. +
+
+
+ {renderStreamsByNamespace()} +
+
+ + ) +} + +export default StreamDifferenceModal diff --git a/ui/src/modules/common/Modals/StreamEditDisabledModal.tsx b/ui/src/modules/common/Modals/StreamEditDisabledModal.tsx new file mode 100644 index 00000000..696a89ec --- /dev/null +++ b/ui/src/modules/common/Modals/StreamEditDisabledModal.tsx @@ -0,0 +1,58 @@ +import { InfoIcon } from "@phosphor-icons/react" +import { Button, Modal } from "antd" +import { useAppStore } from "../../../store" +import { useNavigate } from "react-router-dom" +import { StreamEditDisabledModalProps } from "../../../types/modalTypes" + +const StreamEditDisabledModal = ({ from }: StreamEditDisabledModalProps) => { + const navigate = useNavigate() + const { showStreamEditDisabledModal, setShowStreamEditDisabledModal } = + useAppStore() + + const handleCloseModal = () => { + navigate("/jobs") + setShowStreamEditDisabledModal(false) + } + + return ( + <> + + + + } + open={showStreamEditDisabledModal} + closable={false} + footer={ +
+ +
+ } + centered + width="30%" + > +
+

Editing Disabled

+

+ {from === "jobSettings" ? "Job Settings Edit" : "Stream editing"} is + disabled while the destination is being cleared. It will be + available once the process finishes. +

+
+
+ + ) +} + +export default StreamEditDisabledModal diff --git a/ui/src/modules/jobs/components/JobTable.tsx b/ui/src/modules/jobs/components/JobTable.tsx index 498bae6a..4e6b23c4 100644 --- a/ui/src/modules/jobs/components/JobTable.tsx +++ b/ui/src/modules/jobs/components/JobTable.tsx @@ -14,9 +14,11 @@ import { XIcon, } from "@phosphor-icons/react" -import { EntityBase, Job, JobTableProps } from "../../../types" +import { EntityBase, Job, JobTableProps, JobType } from "../../../types" import { getConnectorImage, + getJobTypeClass, + getJobTypeLabel, getStatusClass, getStatusLabel, } from "../../../utils/utils" @@ -84,12 +86,16 @@ const JobTable: React.FC = ({ key: "sync", icon: , label: "Sync now", + disabled: + record.last_run_state?.toLowerCase() === "running" || + !record.activate, onClick: () => onSync(record.id.toString()), }, { key: "edit", icon: , label: "Edit Streams", + disabled: !record.activate, onClick: () => onEdit(record.id.toString()), }, { @@ -100,25 +106,32 @@ const JobTable: React.FC = ({ ), label: record.activate ? "Pause job" : "Resume job", + disabled: record.last_run_state?.toLowerCase() === "running", onClick: () => onPause(record.id.toString(), record.activate), }, { key: "cancel", icon: , label: "Cancel Run", - disabled: record.last_run_state?.toLowerCase() !== "running", + disabled: + !record.activate || + record.last_run_state?.toLowerCase() !== "running" || + (record.last_run_type === JobType.ClearDestination && + record.last_run_state?.toLowerCase() === "running"), onClick: () => onCancelJob(record.id.toString()), }, { key: "history", icon: , label: "Job Logs & History", + disabled: !record.activate, onClick: () => handleViewHistory(record.id.toString()), }, { key: "settings", icon: , label: "Job settings", + disabled: !record.activate, onClick: () => handleViewSettings(record.id.toString()), }, { @@ -190,13 +203,13 @@ const JobTable: React.FC = ({ ), }, { - title: "Last sync", + title: "Last Run", dataIndex: "last_run_time", key: "last_run_time", render: formatLastSyncTime, }, { - title: "Last sync status", + title: "Last Run status", dataIndex: "last_run_state", key: "last_run_state", render: (status: string) => { @@ -211,6 +224,21 @@ const JobTable: React.FC = ({ ) }, }, + { + title: "Job Type", + dataIndex: "last_run_type", + key: "last_run_type", + render: (lastRunType: JobType) => { + if (!lastRunType) return
-
+ return ( +
+ {getJobTypeLabel(lastRunType)} +
+ ) + }, + }, ] const filteredJobs = jobs.filter( diff --git a/ui/src/modules/jobs/pages/JobEdit.tsx b/ui/src/modules/jobs/pages/JobEdit.tsx index fae4efa3..977f3141 100644 --- a/ui/src/modules/jobs/pages/JobEdit.tsx +++ b/ui/src/modules/jobs/pages/JobEdit.tsx @@ -14,6 +14,7 @@ import { SourceData, DestinationData, StreamsDataStructure, + JobType, } from "../../../types" import JobConfiguration from "../components/JobConfiguration" import StepProgress from "../components/StepIndicator" @@ -35,6 +36,8 @@ import { JOB_STEP_NUMBERS, } from "../../../utils/constants" import ResetStreamsModal from "../../common/Modals/ResetStreamsModal" +import StreamDifferenceModal from "../../common/Modals/StreamDifferenceModal" +import StreamEditDisabledModal from "../../common/Modals/StreamEditDisabledModal" // Custom wrapper component for SourceEdit to use in job flow const JobSourceEdit = ({ @@ -132,6 +135,8 @@ const JobEdit: React.FC = () => { fetchSources, fetchDestinations, setShowResetStreamsModal, + setShowStreamDifferenceModal, + setShowStreamEditDisabledModal, } = useAppStore() const [currentStep, setCurrentStep] = useState( @@ -144,7 +149,8 @@ const JobEdit: React.FC = () => { const [destinationData, setDestinationData] = useState(null) const [nextStep, setNextStep] = useState(null) - + const [streamDifference, setStreamDifference] = + useState(null) // Streams step states const [selectedStreams, setSelectedStreams] = useState({ selected_streams: {}, @@ -166,6 +172,16 @@ const JobEdit: React.FC = () => { fetchDestinations() }, []) + // Disable stream editing if clear destination is running + useEffect(() => { + if ( + job?.last_run_type === JobType.ClearDestination && + job?.last_run_state.toLowerCase() === "running" + ) { + setShowStreamEditDisabledModal(true) + } + }, [job]) + const initializeFromExistingJob = (job: Job) => { setJobName(job.name) // Parse source config @@ -337,10 +353,52 @@ const JobEdit: React.FC = () => { }), frequency: cronExpression, activate: job?.activate, + difference_streams: JSON.stringify(streamDifference), } return jobUpdateRequestPayload } + const handleStreamDifference = async () => { + if (!sourceData || !destinationData || !jobId) { + message.error("Source and destination data are required") + return + } + + if ( + !validateStreams(getSelectedStreams(selectedStreams.selected_streams)) + ) { + message.error("Filter Value cannot be empty") + return + } + + const streamDifferenceResponse = ( + await jobService.getStreamDifference( + jobId, + typeof selectedStreams === "string" + ? selectedStreams + : JSON.stringify({ + ...selectedStreams, + selected_streams: getSelectedStreams( + selectedStreams.selected_streams, + ), + }), + ) + )?.difference_streams + + const diff = + typeof streamDifferenceResponse === "string" + ? JSON.parse(streamDifferenceResponse || "{}") + : streamDifferenceResponse || {} + const hasDiff = Object.keys(diff?.selected_streams ?? diff).length > 0 + // if there is a stream difference, show the stream difference modal + if (hasDiff) { + setStreamDifference(streamDifferenceResponse) + setShowStreamDifferenceModal(true) + return + } + handleJobSubmit() + } + // Handle job submission const handleJobSubmit = async () => { if (!sourceData || !destinationData || !jobId) { @@ -354,17 +412,16 @@ const JobEdit: React.FC = () => { message.error("Filter Value cannot be empty") return } - setIsSubmitting(true) - try { // Create the job update payload const jobUpdatePayload = getjobUpdatePayLoad() await jobService.updateJob(jobId, jobUpdatePayload) - // Refresh jobs and navigate back to jobs list - fetchJobs() + // wait for 1 second before refreshing jobs to avoid fetching old state + await new Promise(resolve => setTimeout(resolve, 1000)) + await fetchJobs() navigate("/jobs") } catch (error) { console.error("Error saving job:", error) @@ -385,7 +442,7 @@ const JobEdit: React.FC = () => { setCurrentStep(JOB_CREATION_STEPS.STREAMS) } } else if (currentStep === JOB_CREATION_STEPS.STREAMS) { - handleJobSubmit() + handleStreamDifference() } else if (currentStep === JOB_CREATION_STEPS.CONFIG) { if (!jobName.trim()) { message.error("Job name is required") @@ -409,6 +466,7 @@ const JobEdit: React.FC = () => { } const handleStepClick = async (step: string) => { + if (step === currentStep) return if (currentStep === JOB_CREATION_STEPS.STREAMS) { setNextStep(step as JobCreationSteps) setShowResetStreamsModal(true) @@ -582,6 +640,13 @@ const JobEdit: React.FC = () => { + {streamDifference && ( + + )} + ) } diff --git a/ui/src/modules/jobs/pages/JobHistory.tsx b/ui/src/modules/jobs/pages/JobHistory.tsx index 0ed14753..dc58e3ab 100644 --- a/ui/src/modules/jobs/pages/JobHistory.tsx +++ b/ui/src/modules/jobs/pages/JobHistory.tsx @@ -12,10 +12,13 @@ import { import { useAppStore } from "../../../store" import { getConnectorImage, + getJobTypeClass, + getJobTypeLabel, getStatusClass, getStatusLabel, } from "../../../utils/utils" import { getStatusIcon } from "../../../utils/statusIcons" +import { JobType } from "../../../types" const JobHistory: React.FC = () => { const { jobId } = useParams<{ jobId: string }>() @@ -121,6 +124,21 @@ const JobHistory: React.FC = () => { ), }, + { + title: "Job Type", + dataIndex: "job_type", + key: "job_type", + render: (job_type: JobType) => ( +
+ {getJobTypeLabel(job_type)} +
+ ), + }, { title: "Actions", key: "actions", diff --git a/ui/src/modules/jobs/pages/JobSettings.tsx b/ui/src/modules/jobs/pages/JobSettings.tsx index fdc15526..e20545cd 100644 --- a/ui/src/modules/jobs/pages/JobSettings.tsx +++ b/ui/src/modules/jobs/pages/JobSettings.tsx @@ -16,7 +16,9 @@ import { import { DAYS, FREQUENCY_OPTIONS } from "../../../utils/constants" import DeleteJobModal from "../../common/Modals/DeleteJobModal" import ClearDataModal from "../../common/Modals/ClearDataModal" -import ClearDestinationAndSyncModal from "../../common/Modals/ClearDestinationAndSyncModal" +import ClearDestinationModal from "../../common/Modals/ClearDestinationModal" +import { JobType } from "../../../types/jobTypes" +import StreamEditDisabledModal from "../../common/Modals/StreamEditDisabledModal" const JobSettings: React.FC = () => { const { jobId } = useParams<{ jobId: string }>() @@ -42,8 +44,14 @@ const JobSettings: React.FC = () => { days: DAYS.map(day => ({ value: day, label: day })), } - const { jobs, fetchJobs, setShowDeleteJobModal, setSelectedJobId } = - useAppStore() + const { + jobs, + fetchJobs, + setShowDeleteJobModal, + setSelectedJobId, + setShowClearDestinationModal, + setShowStreamEditDisabledModal, + } = useAppStore() useEffect(() => { if (!jobs.length) { @@ -62,6 +70,10 @@ const JobSettings: React.FC = () => { } }, [job]) + const isClearDestinationRunning = + job?.last_run_type === JobType.ClearDestination && + job?.last_run_state.toLowerCase() === "running" + const getParsedDate = (value: Date) => value.toUTCString() const updateNextRuns = (cronValue: string) => { @@ -200,6 +212,19 @@ const JobSettings: React.FC = () => { setShowDeleteJobModal(true) } + const handleClearDestination = () => { + if (jobId) { + setSelectedJobId(jobId) + } + setShowClearDestinationModal(true) + } + + useEffect(() => { + if (isClearDestinationRunning) { + setShowStreamEditDisabledModal(true) + } + }, [isClearDestinationRunning]) + // Helper to determine if time selection should be shown const isTimeSelectionFrequency = (freq: string): boolean => { return freq === "days" || freq === "weeks" @@ -245,6 +270,8 @@ const JobSettings: React.FC = () => { typeof job.streams_config === "string" ? job.streams_config : JSON.stringify(job.streams_config), + // In settings page, we are not modifying the streams, there will be no stream difference + difference_streams: "{}", } await jobService.updateJob(jobId, jobUpdatePayload) @@ -427,12 +454,31 @@ const JobSettings: React.FC = () => { /> -
-
Delete the job:
+
Clear Destination
+
+ This will erase all data that was synced by this job in + the destination. +
+
+ +
+
+
+
+
+
+
+
Delete the job
No data will be deleted in your source and destination.
@@ -447,7 +493,6 @@ const JobSettings: React.FC = () => {
-
@@ -463,6 +508,8 @@ const JobSettings: React.FC = () => { + + ) } diff --git a/ui/src/modules/jobs/pages/Jobs.tsx b/ui/src/modules/jobs/pages/Jobs.tsx index 91fd82dc..04657377 100644 --- a/ui/src/modules/jobs/pages/Jobs.tsx +++ b/ui/src/modules/jobs/pages/Jobs.tsx @@ -6,15 +6,15 @@ import { GitCommitIcon, PlusIcon } from "@phosphor-icons/react" import { useAppStore } from "../../../store" import { jobService } from "../../../api" import analyticsService from "../../../api/services/analyticsService" -import { JobType } from "../../../types/jobTypes" -import { JOB_TYPES } from "../../../utils/constants" +import { JobStatus } from "../../../types/jobTypes" +import { JOB_STATUS } from "../../../utils/constants" import JobTable from "../components/JobTable" import JobEmptyState from "../components/JobEmptyState" import DeleteJobModal from "../../common/Modals/DeleteJobModal" const Jobs: React.FC = () => { - const [activeTab, setActiveTab] = useState( - JOB_TYPES.ACTIVE as JobType, + const [activeTab, setActiveTab] = useState( + JOB_STATUS.ACTIVE as JobStatus, ) const navigate = useNavigate() const { @@ -41,13 +41,13 @@ const Jobs: React.FC = () => { await jobService.syncJob(id) await fetchJobs() } catch (error) { - message.error("Failed to sync job") - console.error(error) + message.error(error as string) + console.error("Error syncing job:", error) } } const handleEditJob = (id: string) => { - if (activeTab === JOB_TYPES.SAVED) { + if (activeTab === JOB_STATUS.SAVED) { const savedJob = savedJobs.find(job => job.id.toString() === id) if (savedJob) { const initialData = { @@ -91,7 +91,7 @@ const Jobs: React.FC = () => { } const handleDeleteJob = (id: string) => { - if (activeTab === JOB_TYPES.SAVED) { + if (activeTab === JOB_STATUS.SAVED) { const savedJobsFromStorage = JSON.parse( localStorage.getItem("savedJobs") || "[]", ) @@ -122,16 +122,16 @@ const Jobs: React.FC = () => { const updateJobsList = () => { switch (activeTab) { - case JOB_TYPES.ACTIVE: + case JOB_STATUS.ACTIVE: setFilteredJobs(jobs.filter(job => job?.activate === true)) break - case JOB_TYPES.INACTIVE: + case JOB_STATUS.INACTIVE: setFilteredJobs(jobs.filter(job => job?.activate === false)) break - case JOB_TYPES.SAVED: + case JOB_STATUS.SAVED: setFilteredJobs(savedJobs) break - case JOB_TYPES.FAILED: + case JOB_STATUS.FAILED: setFilteredJobs( jobs.filter( job => (job?.last_run_state ?? "").toLowerCase() === "failed", @@ -147,10 +147,10 @@ const Jobs: React.FC = () => { const showEmpty = !isLoadingJobs && jobs.length === 0 const tabItems = [ - { key: JOB_TYPES.ACTIVE, label: "Active jobs" }, - { key: JOB_TYPES.INACTIVE, label: "Inactive jobs" }, - { key: JOB_TYPES.SAVED, label: "Saved jobs" }, - { key: JOB_TYPES.FAILED, label: "Failed jobs" }, + { key: JOB_STATUS.ACTIVE, label: "Active jobs" }, + { key: JOB_STATUS.INACTIVE, label: "Inactive jobs" }, + { key: JOB_STATUS.SAVED, label: "Saved jobs" }, + { key: JOB_STATUS.FAILED, label: "Failed jobs" }, ] if (jobsError) { @@ -189,7 +189,7 @@ const Jobs: React.FC = () => { setActiveTab(key as JobType)} + onChange={key => setActiveTab(key as JobStatus)} items={tabItems.map(tab => ({ key: tab.key, label: tab.label, @@ -200,7 +200,7 @@ const Jobs: React.FC = () => { tip="Loading sources..." /> - ) : tab.key === JOB_TYPES.ACTIVE && showEmpty ? ( + ) : tab.key === JOB_STATUS.ACTIVE && showEmpty ? ( ) : filteredJobs.length === 0 ? ( = ({
{destinationDatabase}
- {!fromJobEditFlow && ( - - setShowDestinationDatabaseModal(true)} - /> - - )} + + setShowDestinationDatabaseModal(true)} + /> + s.stream_name === stream.stream.name, - ) - useEffect(() => { setActiveTab("config") const initialApiSyncMode = stream.stream.sync_mode @@ -802,7 +795,7 @@ const StreamConfiguration = ({
@@ -825,7 +818,7 @@ const StreamConfiguration = ({ {fullLoadFilter && ( @@ -879,13 +872,13 @@ const StreamConfiguration = ({ className="w-full" value={partitionRegex} onChange={e => setPartitionRegex(e.target.value)} - disabled={!!activePartitionRegex || isStreamInInitialSelection} + disabled={!!activePartitionRegex} /> {!activePartitionRegex ? ( @@ -902,7 +895,6 @@ const StreamConfiguration = ({ size="small" className="rounded-md py-1 text-sm" onClick={handleClearPartitionRegex} - disabled={isStreamInInitialSelection} > Delete Partition @@ -935,7 +927,7 @@ const StreamConfiguration = ({ ? "bg-white text-gray-800 shadow-sm" : "bg-transparent text-gray-600", )} - disabled={isStreamInInitialSelection || !isSelected} + disabled={!isSelected} > AND @@ -948,7 +940,7 @@ const StreamConfiguration = ({ ? "bg-white text-gray-800 shadow-sm" : "bg-transparent text-gray-600", )} - disabled={isStreamInInitialSelection || !isSelected} + disabled={!isSelected} > OR @@ -958,7 +950,7 @@ const StreamConfiguration = ({ danger icon={} onClick={() => handleRemoveFilter(index)} - disabled={isStreamInInitialSelection || !isSelected} + disabled={!isSelected} > Remove @@ -992,7 +984,7 @@ const StreamConfiguration = ({ options={getColumnOptions()} labelInValue={false} optionLabelProp="value" - disabled={isStreamInInitialSelection || !isSelected} + disabled={!isSelected} />
@@ -1007,7 +999,7 @@ const StreamConfiguration = ({ handleFilterConditionChange(index, "operator", value) } options={operatorOptions} - disabled={isStreamInInitialSelection || !isSelected} + disabled={!isSelected} />
@@ -1018,7 +1010,7 @@ const StreamConfiguration = ({ onChange={e => handleFilterConditionChange(index, "value", e.target.value) } - disabled={isStreamInInitialSelection || !isSelected} + disabled={!isSelected} />
@@ -1030,7 +1022,7 @@ const StreamConfiguration = ({ icon={} onClick={handleAddFilter} className="w-fit" - disabled={isStreamInInitialSelection || !isSelected} + disabled={!isSelected} > New Column filter diff --git a/ui/src/store/modalStore.ts b/ui/src/store/modalStore.ts index 527f8e4a..238399c1 100644 --- a/ui/src/store/modalStore.ts +++ b/ui/src/store/modalStore.ts @@ -10,7 +10,9 @@ export interface ModalSlice { showDeleteModal: boolean showDeleteJobModal: boolean showClearDataModal: boolean - showClearDestinationAndSyncModal: boolean + showClearDestinationModal: boolean + showStreamDifferenceModal: boolean + showStreamEditDisabledModal: boolean showEditSourceModal: boolean showEditDestinationModal: boolean showDestinationDatabaseModal: boolean @@ -26,7 +28,9 @@ export interface ModalSlice { setShowDeleteModal: (show: boolean) => void setShowDeleteJobModal: (show: boolean) => void setShowClearDataModal: (show: boolean) => void - setShowClearDestinationAndSyncModal: (show: boolean) => void + setShowClearDestinationModal: (show: boolean) => void + setShowStreamDifferenceModal: (show: boolean) => void + setShowStreamEditDisabledModal: (show: boolean) => void setShowEditSourceModal: (show: boolean) => void setShowEditDestinationModal: (show: boolean) => void setShowDestinationDatabaseModal: (show: boolean) => void @@ -45,7 +49,9 @@ export const createModalSlice: StateCreator = set => ({ showDeleteModal: false, showDeleteJobModal: false, showClearDataModal: false, - showClearDestinationAndSyncModal: false, + showClearDestinationModal: false, + showStreamDifferenceModal: false, + showStreamEditDisabledModal: false, showEditSourceModal: false, showEditDestinationModal: false, showDestinationDatabaseModal: false, @@ -61,8 +67,12 @@ export const createModalSlice: StateCreator = set => ({ setShowDeleteModal: show => set({ showDeleteModal: show }), setShowDeleteJobModal: show => set({ showDeleteJobModal: show }), setShowClearDataModal: show => set({ showClearDataModal: show }), - setShowClearDestinationAndSyncModal: show => - set({ showClearDestinationAndSyncModal: show }), + setShowClearDestinationModal: show => + set({ showClearDestinationModal: show }), + setShowStreamDifferenceModal: show => + set({ showStreamDifferenceModal: show }), + setShowStreamEditDisabledModal: show => + set({ showStreamEditDisabledModal: show }), setShowEditSourceModal: show => set({ showEditSourceModal: show }), setShowEditDestinationModal: show => set({ showEditDestinationModal: show }), setShowDestinationDatabaseModal: show => diff --git a/ui/src/types/jobTypes.ts b/ui/src/types/jobTypes.ts index aac3be81..b3cd22a0 100644 --- a/ui/src/types/jobTypes.ts +++ b/ui/src/types/jobTypes.ts @@ -19,6 +19,7 @@ export interface Job { } streams_config: string frequency: string + last_run_type: JobType last_run_state: string last_run_time: string created_at: string @@ -45,6 +46,7 @@ export interface JobBase { } frequency: string streams_config: string + difference_streams?: string activate?: boolean } export interface JobTask { @@ -52,6 +54,7 @@ export interface JobTask { start_time: string status: string file_path: string + job_type: JobType } export interface TaskLog { level: string @@ -60,12 +63,12 @@ export interface TaskLog { } export type JobCreationSteps = "config" | "source" | "destination" | "streams" -export type JobType = "active" | "inactive" | "saved" | "failed" +export type JobStatus = "active" | "inactive" | "saved" | "failed" export interface JobTableProps { jobs: Job[] loading: boolean - jobType: JobType + jobType: JobStatus onSync: (id: string) => void onEdit: (id: string) => void onPause: (id: string, checked: boolean) => void @@ -90,3 +93,8 @@ export interface JobConnectionProps { remainingJobs?: number jobs: EntityJob[] } + +export enum JobType { + Sync = "sync", + ClearDestination = "clear", +} diff --git a/ui/src/types/modalTypes.ts b/ui/src/types/modalTypes.ts index 7c4a1da8..56d8ac07 100644 --- a/ui/src/types/modalTypes.ts +++ b/ui/src/types/modalTypes.ts @@ -18,6 +18,15 @@ export interface ResetStreamsModalProps { onConfirm: () => void } +export interface StreamDifferenceModalProps { + streamDifference: StreamsDataStructure + onConfirm: () => void +} + +export interface StreamEditDisabledModalProps { + from: "jobSettings" | "jobEdit" +} + export interface IngestionModeChangeModalProps { onConfirm: (ingestionMode: IngestionMode) => void ingestionMode: IngestionMode diff --git a/ui/src/utils/constants.ts b/ui/src/utils/constants.ts index 10db2d3e..7f9d7e1f 100644 --- a/ui/src/utils/constants.ts +++ b/ui/src/utils/constants.ts @@ -91,7 +91,7 @@ export const DESTINATION_LABELS = { APACHE_ICEBERG: "apache iceberg", } -export const JOB_TYPES = { +export const JOB_STATUS = { ACTIVE: "active", INACTIVE: "inactive", SAVED: "saved", diff --git a/ui/src/utils/utils.ts b/ui/src/utils/utils.ts index a59860ab..a36a6012 100644 --- a/ui/src/utils/utils.ts +++ b/ui/src/utils/utils.ts @@ -1,7 +1,12 @@ import { message } from "antd" import parser from "cron-parser" -import { CronParseResult, IngestionMode, SelectedStream } from "../types" +import { + CronParseResult, + JobType, + IngestionMode, + SelectedStream, +} from "../types" import { DAYS_MAP, DESTINATION_INTERNAL_TYPES, @@ -82,6 +87,17 @@ export const getStatusClass = (status: string) => { } } +export const getJobTypeClass = (jobType: JobType) => { + switch (jobType) { + case JobType.Sync: + return "text-[#52C41A] bg-[#F6FFED]" + case JobType.ClearDestination: + return "text-amber-700 bg-amber-50" + default: + return "text-[rgba(0,0,0,88)] bg-transparent" + } +} + export const getConnectorInLowerCase = (connector: string) => { const lowerConnector = connector.toLowerCase() @@ -124,6 +140,17 @@ export const getStatusLabel = (status: string) => { } } +export const getJobTypeLabel = (lastRunType: JobType) => { + switch (lastRunType) { + case JobType.Sync: + return "Sync" + case JobType.ClearDestination: + return "Clear Destination" + default: + return lastRunType + } +} + export const getConnectorLabel = (type: string): string => { switch (type) { case "mongodb":