├── .dockerignore ├── ui ├── shared │ ├── setup-tests.js │ ├── components │ │ ├── Tip.js │ │ ├── Layout.js │ │ ├── FieldError.js │ │ ├── Footer.test.js │ │ ├── index.js │ │ ├── Footer.js │ │ ├── Layout.test.js │ │ ├── FieldError.test.js │ │ ├── Header.test.js │ │ ├── ErrorSummary.js │ │ ├── Header.js │ │ ├── GroupByList.js │ │ └── ErrorSummary.test.js │ ├── fetch.js │ └── fetch.test.js ├── favicon.ico ├── features │ ├── collections │ │ ├── collections-api.js │ │ ├── collections-api.test.js │ │ ├── collections.js │ │ ├── collections-components.js │ │ ├── collections-components.test.js │ │ └── collections.test.js │ ├── variables │ │ ├── variables-api.js │ │ ├── variables.js │ │ ├── variables-components.js │ │ ├── variables-api.test.js │ │ ├── variables-components.test.js │ │ └── variables.test.js │ ├── jobs │ │ ├── jobs-api.js │ │ ├── jobs.js │ │ ├── jobs-api.test.js │ │ └── jobs-components.js │ ├── collection │ │ ├── collection-api.js │ │ ├── collection.js │ │ └── collection-api.test.js │ ├── variable │ │ ├── variable-api.js │ │ ├── variable-api.test.js │ │ ├── variable.js │ │ └── variable-components.js │ ├── history │ │ ├── history-api.js │ │ ├── history-components.js │ │ ├── history.js │ │ └── history-api.test.js │ └── job │ │ └── job-api.js ├── index.test.js ├── index.html └── index.js ├── infrastructure ├── http │ ├── testdata │ │ ├── jobs │ │ │ ├── get │ │ │ │ ├── error-golden.json │ │ │ │ ├── empty-input.json │ │ │ │ ├── error-input.json │ │ │ │ ├── match-etag-golden.json │ │ │ │ ├── match-etag-input.json │ │ │ │ ├── no-match-etag-input.json │ │ │ │ ├── empty-golden.json │ │ │ │ ├── no-match-etag-golden.json │ │ │ │ ├── ok-input.json │ │ │ │ └── ok-golden.json │ │ │ └── post │ │ │ │ ├── conflict-golden.json │ │ │ │ ├── unprocessable-input.json │ │ │ │ ├── ok-golden.json │ │ │ │ ├── validation-error-input.json │ │ │ │ ├── unprocessable-golden.json │ │ │ │ ├── conflict-input.json │ │ │ │ ├── ok-input.json │ │ │ │ └── validation-error-golden.json │ │ ├── collections │ │ │ ├── get │ │ │ │ ├── error-golden.json │ │ │ │ ├── empty-input.json │ │ │ │ ├── error-input.json │ │ │ │ ├── match-etag-golden.json │ │ │ │ ├── match-etag-input.json │ │ │ │ ├── no-match-etag-input.json │ │ │ │ ├── empty-golden.json │ │ │ │ ├── no-match-etag-golden.json │ │ │ │ ├── ok-input.json │ │ │ │ └── ok-golden.json │ │ │ └── post │ │ │ │ ├── conflict-golden.json │ │ │ │ ├── unprocessable-input.json │ │ │ │ ├── ok-golden.json │ │ │ │ ├── validation-error-input.json │ │ │ │ ├── conflict-input.json │ │ │ │ ├── ok-input.json │ │ │ │ ├── validation-error-golden.json │ │ │ │ └── unprocessable-golden.json │ │ ├── jobs-item │ │ │ ├── delete │ │ │ │ ├── ok-golden.json │ │ │ │ ├── match-etag-golden.json │ │ │ │ ├── not-found-golden.json │ │ │ │ ├── etag-not-found-golden.json │ │ │ │ ├── no-match-etag-golden.json │ │ │ │ ├── invalid-id-input.json │ │ │ │ ├── ok-input.json │ │ │ │ ├── not-found-input.json │ │ │ │ ├── etag-not-found-input.json │ │ │ │ ├── invalid-id-golden.json │ │ │ │ ├── match-etag-input.json │ │ │ │ └── no-match-etag-input.json │ │ │ ├── patch │ │ │ │ ├── ok-golden.json │ │ │ │ ├── match-etag-golden.json │ │ │ │ ├── not-found-golden.json │ │ │ │ ├── save-error-golden.json │ │ │ │ ├── no-match-etag-golden.json │ │ │ │ ├── save-conflict-golden.json │ │ │ │ ├── invalid-id-input.json │ │ │ │ ├── unprocessable-input.json │ │ │ │ ├── not-found-input.json │ │ │ │ ├── invalid-id-golden.json │ │ │ │ ├── save-conflict-input.json │ │ │ │ ├── validation-error-golden.json │ │ │ │ ├── unprocessable-golden.json │ │ │ │ ├── no-match-etag-input.json │ │ │ │ ├── ok-input.json │ │ │ │ ├── save-error-input.json │ │ │ │ ├── validation-error-input.json │ │ │ │ └── match-etag-input.json │ │ │ └── get │ │ │ │ ├── match-etag-golden.json │ │ │ │ ├── not-found-golden.json │ │ │ │ ├── invalid-id-input.json │ │ │ │ ├── not-found-input.json │ │ │ │ ├── invalid-id-golden.json │ │ │ │ ├── match-etag-input.json │ │ │ │ ├── ok-input.json │ │ │ │ ├── ok-golden.json │ │ │ │ ├── no-match-etag-golden.json │ │ │ │ └── no-match-etag-input.json │ │ ├── collections-item │ │ │ ├── delete │ │ │ │ ├── ok-golden.json │ │ │ │ ├── match-etag-golden.json │ │ │ │ ├── no-match-etag-golden.json │ │ │ │ ├── not-found-golden.json │ │ │ │ ├── etag-not-found-golden.json │ │ │ │ ├── invalid-id-input.json │ │ │ │ ├── ok-input.json │ │ │ │ ├── not-found-input.json │ │ │ │ ├── etag-not-found-input.json │ │ │ │ ├── invalid-id-golden.json │ │ │ │ ├── match-etag-input.json │ │ │ │ └── no-match-etag-input.json │ │ │ ├── patch │ │ │ │ ├── ok-golden.json │ │ │ │ ├── not-found-golden.json │ │ │ │ ├── match-etag-golden.json │ │ │ │ ├── no-match-etag-golden.json │ │ │ │ ├── save-conflict-golden.json │ │ │ │ ├── save-error-golden.json │ │ │ │ ├── invalid-id-input.json │ │ │ │ ├── unprocessable-input.json │ │ │ │ ├── validation-error-input.json │ │ │ │ ├── not-found-input.json │ │ │ │ ├── ok-input.json │ │ │ │ ├── invalid-id-golden.json │ │ │ │ ├── save-conflict-input.json │ │ │ │ ├── validation-error-golden.json │ │ │ │ ├── save-error-input.json │ │ │ │ ├── unprocessable-golden.json │ │ │ │ ├── no-match-etag-input.json │ │ │ │ └── match-etag-input.json │ │ │ └── get │ │ │ │ ├── match-etag-golden.json │ │ │ │ ├── not-found-golden.json │ │ │ │ ├── invalid-id-input.json │ │ │ │ ├── not-found-input.json │ │ │ │ ├── ok-input.json │ │ │ │ ├── ok-golden.json │ │ │ │ ├── no-match-etag-golden.json │ │ │ │ ├── invalid-id-golden.json │ │ │ │ ├── match-etag-input.json │ │ │ │ └── no-match-etag-input.json │ │ ├── jobs-item-history │ │ │ ├── delete │ │ │ │ ├── ok-golden.json │ │ │ │ ├── delete-error-golden.json │ │ │ │ ├── match-etag-golden.json │ │ │ │ ├── not-found-golden.json │ │ │ │ ├── etag-not-found-golden.json │ │ │ │ ├── no-match-etag-golden.json │ │ │ │ ├── invalid-id-input.json │ │ │ │ ├── ok-input.json │ │ │ │ ├── validation-error-input.json │ │ │ │ ├── not-found-input.json │ │ │ │ ├── delete-error-input.json │ │ │ │ ├── etag-not-found-input.json │ │ │ │ ├── match-etag-input.json │ │ │ │ ├── no-match-etag-input.json │ │ │ │ ├── invalid-id-golden.json │ │ │ │ └── validation-error-golden.json │ │ │ └── get │ │ │ │ ├── match-etag-golden.json │ │ │ │ ├── not-found-golden.json │ │ │ │ ├── match-not-found-golden.json │ │ │ │ ├── status-failed-golden.json │ │ │ │ ├── invalid-id-input.json │ │ │ │ ├── not-found-input.json │ │ │ │ ├── status-failed-input.json │ │ │ │ ├── no-match-etag-golden.json │ │ │ │ ├── match-not-found-input.json │ │ │ │ ├── match-etag-input.json │ │ │ │ ├── no-match-etag-input.json │ │ │ │ ├── invalid-id-golden.json │ │ │ │ ├── ok-input.json │ │ │ │ └── ok-golden.json │ │ ├── jobs-item-status │ │ │ ├── patch │ │ │ │ ├── ok-golden.json │ │ │ │ ├── not-found-golden.json │ │ │ │ ├── run-error-golden.json │ │ │ │ ├── match-etag-golden.json │ │ │ │ ├── no-match-etag-golden.json │ │ │ │ ├── run-running-golden.json │ │ │ │ ├── cancel-not-running-golden.json │ │ │ │ ├── invalid-id-input.json │ │ │ │ ├── unprocessable-input.json │ │ │ │ ├── no-match-etag-input.json │ │ │ │ ├── not-found-input.json │ │ │ │ ├── cancel-not-running-input.json │ │ │ │ ├── cancel-input.json │ │ │ │ ├── run-running-input.json │ │ │ │ ├── run-error-input.json │ │ │ │ ├── invalid-id-golden.json │ │ │ │ ├── cancel-golden.json │ │ │ │ ├── ok-input.json │ │ │ │ ├── unprocessable-golden.json │ │ │ │ └── match-etag-input.json │ │ │ └── get │ │ │ │ ├── match-etag-golden.json │ │ │ │ ├── not-found-golden.json │ │ │ │ ├── invalid-id-input.json │ │ │ │ ├── not-found-input.json │ │ │ │ ├── ok-input.json │ │ │ │ ├── match-etag-input.json │ │ │ │ ├── no-match-etag-input.json │ │ │ │ ├── ok-golden.json │ │ │ │ ├── no-match-etag-golden.json │ │ │ │ └── invalid-id-golden.json │ │ └── health │ │ │ └── get │ │ │ ├── ok-input.json │ │ │ ├── error-input.json │ │ │ ├── ok-golden.json │ │ │ └── error-golden.json │ ├── etag.go │ ├── ui.go │ ├── runner.go │ ├── server.go │ ├── routes.go │ └── runner_test.go ├── postgres │ ├── history.go │ ├── collections.go │ ├── variables.go │ └── subscriber.go └── cron │ └── scheduler.go ├── .gitignore ├── misc ├── docs │ └── img │ │ ├── job.png │ │ ├── jobs.png │ │ ├── collection.png │ │ ├── db-schema.png │ │ ├── variable.png │ │ ├── variables.png │ │ ├── architecture.png │ │ ├── collections.png │ │ ├── job-history.png │ │ └── general-error.png ├── k8s │ ├── README.md │ ├── db-service.yaml │ ├── db-persistentvolumeclaim.yaml │ ├── app-service.yaml │ ├── db-deployment.yaml │ └── app-deployment.yaml ├── docker │ ├── README.md │ ├── docker-compose.yaml │ └── Dockerfile └── db │ ├── patch-ids.sql │ └── samples.sql ├── domain ├── testdata │ └── validation │ │ ├── collection │ │ ├── ok.json │ │ └── invalid.json │ │ └── job │ │ ├── invalid-headers.json │ │ ├── request-null.json │ │ ├── uri-not-http.json │ │ ├── invalid-uri.json │ │ ├── ok.json │ │ └── invalid.json ├── subscriber.go ├── scheduler.go ├── runner.go ├── models_etag.go ├── models_etag_test.go ├── repository.go ├── template.go └── validation_test.go ├── go.mod ├── .babelrc ├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ ├── image.yml │ └── tests.yaml ├── core ├── history.go ├── collections.go ├── scheduler.go ├── service.go ├── variables.go ├── subscription.go ├── runner.go └── jobs.go ├── LICENSE ├── main.go ├── .eslintrc ├── shared └── rule │ └── validation.go ├── webpack.config.js ├── go.sum └── package.json /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | static/ 3 | node_modules/ 4 | *.exe 5 | -------------------------------------------------------------------------------- /ui/shared/setup-tests.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs/get/error-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 503 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/get/error-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 503 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/delete/ok-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/patch/ok-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs/post/conflict-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 409 3 | } -------------------------------------------------------------------------------- /ui/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akornatskyy/scheduler/HEAD/ui/favicon.ico -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/delete/ok-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/patch/ok-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/post/conflict-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 409 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/delete/ok-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/patch/ok-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/delete/match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/delete/not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/get/match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 304 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/get/not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/patch/match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/patch/not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/patch/save-error-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 503 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.exe~ 3 | *.test 4 | *.out 5 | node_modules/ 6 | static/ 7 | coverage/ 8 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/get/match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 304 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/get/not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/patch/not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/get/match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 304 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/get/not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/get/match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 304 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/get/not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/patch/not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/patch/run-error-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 503 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/delete/etag-not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/delete/no-match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 412 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/patch/no-match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 412 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/patch/save-conflict-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 409 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/delete/match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/delete/no-match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 412 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/delete/not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/patch/match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/patch/no-match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 412 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/patch/save-conflict-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 409 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/patch/save-error-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 503 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/delete/delete-error-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 503 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/delete/match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/delete/not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/get/match-not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/get/status-failed-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 503 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/patch/match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/patch/no-match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 412 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/patch/run-running-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /misc/docs/img/job.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akornatskyy/scheduler/HEAD/misc/docs/img/job.png -------------------------------------------------------------------------------- /misc/docs/img/jobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akornatskyy/scheduler/HEAD/misc/docs/img/jobs.png -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/delete/etag-not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/delete/etag-not-found-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 404 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/delete/no-match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 412 3 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/patch/cancel-not-running-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 204 3 | } -------------------------------------------------------------------------------- /misc/docs/img/collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akornatskyy/scheduler/HEAD/misc/docs/img/collection.png -------------------------------------------------------------------------------- /misc/docs/img/db-schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akornatskyy/scheduler/HEAD/misc/docs/img/db-schema.png -------------------------------------------------------------------------------- /misc/docs/img/variable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akornatskyy/scheduler/HEAD/misc/docs/img/variable.png -------------------------------------------------------------------------------- /misc/docs/img/variables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akornatskyy/scheduler/HEAD/misc/docs/img/variables.png -------------------------------------------------------------------------------- /misc/docs/img/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akornatskyy/scheduler/HEAD/misc/docs/img/architecture.png -------------------------------------------------------------------------------- /misc/docs/img/collections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akornatskyy/scheduler/HEAD/misc/docs/img/collections.png -------------------------------------------------------------------------------- /misc/docs/img/job-history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akornatskyy/scheduler/HEAD/misc/docs/img/job-history.png -------------------------------------------------------------------------------- /misc/docs/img/general-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akornatskyy/scheduler/HEAD/misc/docs/img/general-error.png -------------------------------------------------------------------------------- /domain/testdata/validation/collection/ok.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": { 3 | "id": "", 4 | "name": "My App #1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/health/get/ok-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/health" 4 | }, 5 | "mock": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs/get/empty-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs" 4 | }, 5 | "mock": { 6 | "jobs": [] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/get/invalid-id-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs/1234567890123456789012345678901234567" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/health/get/error-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/health" 4 | }, 5 | "mock": { 6 | "err": "ping" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs/get/error-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs" 4 | }, 5 | "mock": { 6 | "err": "unexpected" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs/post/unprocessable-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "POST", 4 | "path": "/jobs" 5 | }, 6 | "mock": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/get/invalid-id-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/collections/1234567890123456789012345678901234567" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/get/empty-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/collections" 4 | }, 5 | "mock": { 6 | "collections": [] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/get/invalid-id-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs/1234567890123456789012345678901234567/history" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/get/invalid-id-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs/1234567890123456789012345678901234567/status" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /ui/features/collections/collections-api.js: -------------------------------------------------------------------------------- 1 | import {go} from '../../shared/fetch'; 2 | 3 | export function listCollections() { 4 | return go('GET', '/collections'); 5 | } 6 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/get/error-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/collections" 4 | }, 5 | "mock": { 6 | "err": "unexpected" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/post/unprocessable-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "POST", 4 | "path": "/collections" 5 | }, 6 | "mock": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/delete/invalid-id-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/jobs/1234567890123456789012345678901234567" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/patch/invalid-id-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "PATCH", 4 | "path": "/jobs/1234567890123456789012345678901234567" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs/get/match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 304, 3 | "headers": { 4 | "Content-Type": [ 5 | "application/json; charset=UTF-8" 6 | ] 7 | } 8 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/get/match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 304, 3 | "headers": { 4 | "Content-Type": [ 5 | "application/json; charset=UTF-8" 6 | ] 7 | } 8 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/delete/invalid-id-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/collections/1234567890123456789012345678901234567" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/patch/invalid-id-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "PATCH", 4 | "path": "/collections/1234567890123456789012345678901234567" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/delete/invalid-id-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/jobs/1234567890123456789012345678901234567/history" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/patch/invalid-id-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "PATCH", 4 | "path": "/jobs/1234567890123456789012345678901234567/status" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/delete/ok-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899" 5 | }, 6 | "mock": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/get/not-found-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899" 4 | }, 5 | "mock": { 6 | "err": "not found" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/patch/unprocessable-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "PATCH", 4 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899" 5 | }, 6 | "mock": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /domain/subscriber.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | type UpdateEventCallback func(*UpdateEvent) error 4 | 5 | type Subscriber interface { 6 | SetCallback(callback UpdateEventCallback) 7 | Start() 8 | Stop() 9 | } 10 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/delete/ok-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/collections/d4be3c55-039a-4480-a85c-820bbbdd4899" 5 | }, 6 | "mock": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /misc/k8s/README.md: -------------------------------------------------------------------------------- 1 | # K8S 2 | 3 | ```sh 4 | kubectl apply -f misc/k8s 5 | 6 | kubectl port-forward service/scheduler-db 5432 7 | # apply sql scripts 8 | kubectl port-forward service/scheduler 8080 9 | ``` 10 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/get/not-found-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/collections/d4be3c55-039a-4480-a85c-820bbbdd4899" 4 | }, 5 | "mock": { 6 | "err": "not found" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/delete/ok-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899/history" 5 | }, 6 | "mock": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/get/not-found-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899/history" 4 | }, 5 | "mock": { 6 | "err": "not found" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/get/not-found-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899/status" 4 | }, 5 | "mock": { 6 | "err": "not found" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/patch/unprocessable-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "PATCH", 4 | "path": "/collections/d4be3c55-039a-4480-a85c-820bbbdd4899" 5 | }, 6 | "mock": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/get/status-failed-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899/history" 4 | }, 5 | "mock": { 6 | "err": "retrieve-job-status" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs/post/ok-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 201, 3 | "headers": { 4 | "Content-Type": [ 5 | "application/json; charset=UTF-8" 6 | ] 7 | }, 8 | "body": "8a332e22-5b6d-4173-a61f-bc0863fb60bb" 9 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/post/ok-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 201, 3 | "headers": { 4 | "Content-Type": [ 5 | "application/json; charset=UTF-8" 6 | ] 7 | }, 8 | "body": "fc62401e-7ad4-4050-995a-56bed972a7a6" 9 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/health/get/ok-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "headers": { 4 | "Content-Type": [ 5 | "application/json; charset=UTF-8" 6 | ] 7 | }, 8 | "body": { 9 | "status": "up" 10 | } 11 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/delete/validation-error-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899/history?before=x" 5 | }, 6 | "mock": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /misc/k8s/db-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: scheduler-db 5 | spec: 6 | selector: 7 | app: scheduler-db 8 | ports: 9 | - name: postgresql 10 | port: 5432 11 | targetPort: 5432 12 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/delete/not-found-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899" 5 | }, 6 | "mock": { 7 | "err": "not found" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /misc/k8s/db-persistentvolumeclaim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: scheduler-db 5 | spec: 6 | accessModes: 7 | - ReadWriteOnce 8 | resources: 9 | requests: 10 | storage: 100Mi 11 | -------------------------------------------------------------------------------- /misc/k8s/app-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: scheduler 5 | spec: 6 | selector: 7 | app: scheduler 8 | type: LoadBalancer 9 | ports: 10 | - name: http 11 | port: 8080 12 | targetPort: 8080 13 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/delete/not-found-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/collections/d4be3c55-039a-4480-a85c-820bbbdd4899" 5 | }, 6 | "mock": { 7 | "err": "not found" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/delete/not-found-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899/history" 5 | }, 6 | "mock": { 7 | "err": "not found" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/patch/unprocessable-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "PATCH", 4 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899/status" 5 | }, 6 | "mock": { 7 | "jobStatus": { 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/health/get/error-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 503, 3 | "headers": { 4 | "Content-Type": [ 5 | "application/json; charset=UTF-8" 6 | ] 7 | }, 8 | "body": { 9 | "message": "ping", 10 | "status": "down" 11 | } 12 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/delete/delete-error-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899/history" 5 | }, 6 | "mock": { 7 | "err": "delete-job-history" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs/get/match-etag-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs", 4 | "headers": { 5 | "If-None-Match": [ 6 | "\"57k0vs3uybfn\"" 7 | ] 8 | } 9 | }, 10 | "mock": { 11 | "jobs": [] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs/get/no-match-etag-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs", 4 | "headers": { 5 | "If-None-Match": [ 6 | "\"no match\"" 7 | ] 8 | } 9 | }, 10 | "mock": { 11 | "jobs": [] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-status/get/ok-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs/dc93f741-ccc4-4d15-9023-950392a74309/status" 4 | }, 5 | "mock": { 6 | "jobStatus": { 7 | "updated": "2019-07-03T10:02:04.436276Z" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /domain/scheduler.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "time" 4 | 5 | type Scheduler interface { 6 | SetRunner(f func(*JobDefinition)) 7 | ListIDs() []string 8 | Add(j *JobDefinition) error 9 | Remove(id string) 10 | NextRun(id string) *time.Time 11 | Start() 12 | Stop() 13 | } 14 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs/post/validation-error-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "POST", 4 | "path": "/jobs", 5 | "headers": { 6 | "Content-Type": ["application/json"] 7 | }, 8 | "body": { 9 | } 10 | }, 11 | "mock": { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ui/shared/components/Tip.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Tip = ({children}) => ( 4 | 5 | 6 | Tip! {children} 7 | 8 | ); 9 | 10 | export default Tip; 11 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/get/match-etag-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/collections", 4 | "headers": { 5 | "If-None-Match": [ 6 | "\"57k0vs3uybfn\"" 7 | ] 8 | } 9 | }, 10 | "mock": { 11 | "collections": [] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/get/no-match-etag-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/collections", 4 | "headers": { 5 | "If-None-Match": [ 6 | "\"no match\"" 7 | ] 8 | } 9 | }, 10 | "mock": { 11 | "collections": [] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/post/validation-error-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "POST", 4 | "path": "/collections", 5 | "headers": { 6 | "Content-Type": ["application/json"] 7 | }, 8 | "body": {} 9 | }, 10 | "mock": { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs/get/empty-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "headers": { 4 | "Content-Type": [ 5 | "application/json; charset=UTF-8" 6 | ], 7 | "Etag": [ 8 | "\"57k0vs3uybfn\"" 9 | ] 10 | }, 11 | "body": { 12 | "items": [] 13 | } 14 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/get/empty-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "headers": { 4 | "Content-Type": [ 5 | "application/json; charset=UTF-8" 6 | ], 7 | "Etag": [ 8 | "\"57k0vs3uybfn\"" 9 | ] 10 | }, 11 | "body": { 12 | "items": [] 13 | } 14 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs/get/no-match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "headers": { 4 | "Content-Type": [ 5 | "application/json; charset=UTF-8" 6 | ], 7 | "Etag": [ 8 | "\"57k0vs3uybfn\"" 9 | ] 10 | }, 11 | "body": { 12 | "items": [] 13 | } 14 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/get/no-match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "headers": { 4 | "Content-Type": [ 5 | "application/json; charset=UTF-8" 6 | ], 7 | "Etag": [ 8 | "\"57k0vs3uybfn\"" 9 | ] 10 | }, 11 | "body": { 12 | "items": [] 13 | } 14 | } -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/get/no-match-etag-golden.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "headers": { 4 | "Content-Type": [ 5 | "application/json; charset=UTF-8" 6 | ], 7 | "Etag": [ 8 | "\"fdqgxvtir8\"" 9 | ] 10 | }, 11 | "body": { 12 | "items": [] 13 | } 14 | } -------------------------------------------------------------------------------- /ui/features/variables/variables-api.js: -------------------------------------------------------------------------------- 1 | import {go} from '../../shared/fetch'; 2 | 3 | export {listCollections} from '../collections/collections-api'; 4 | 5 | export function listVariables(collectionId) { 6 | return go( 7 | 'GET', 8 | collectionId ? `/variables?collectionId=${collectionId}` : '/variables', 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/akornatskyy/scheduler 2 | 3 | go 1.11 4 | 5 | require ( 6 | github.com/NYTimes/gziphandler v1.1.1 7 | github.com/akornatskyy/goext v1.4.5 8 | github.com/google/uuid v1.6.0 // indirect 9 | github.com/julienschmidt/httprouter v1.3.0 10 | github.com/lib/pq v1.10.9 11 | github.com/robfig/cron/v3 v3.0.1 12 | ) 13 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/get/match-not-found-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "path": "/jobs/dc93f741-ccc4-4d15-9023-950392a74309/history", 4 | "headers": { 5 | "If-None-Match": [ 6 | "\"fdqgxvtir8\"" 7 | ] 8 | } 9 | }, 10 | "mock": { 11 | "err": "not found" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /domain/runner.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | type RunError struct { 9 | Code int 10 | Err error 11 | } 12 | 13 | func (r *RunError) Error() string { 14 | return fmt.Sprintf("%d %s", r.Code, r.Err) 15 | } 16 | 17 | type Runner interface { 18 | Run(ctx context.Context, a *Action) error 19 | } 20 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/post/conflict-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "POST", 4 | "path": "/collections", 5 | "headers": { 6 | "Content-Type": ["application/json"] 7 | }, 8 | "body": { 9 | "name": "my-app" 10 | } 11 | }, 12 | "mock": { 13 | "err": "conflict" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item/delete/etag-not-found-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899", 5 | "headers": { 6 | "If-Match": [ 7 | "\"fdqgxvtir8\"" 8 | ] 9 | } 10 | }, 11 | "mock": { 12 | "err": "not found" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "18" 8 | } 9 | } 10 | ], 11 | "@babel/preset-react" 12 | ], 13 | "plugins": [ 14 | "@babel/plugin-transform-class-properties", 15 | "@babel/plugin-transform-optional-chaining" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections-item/delete/etag-not-found-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/collections/d4be3c55-039a-4480-a85c-820bbbdd4899", 5 | "headers": { 6 | "If-Match": [ 7 | "\"fdqgxvtir8\"" 8 | ] 9 | } 10 | }, 11 | "mock": { 12 | "err": "not found" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/jobs-item-history/delete/etag-not-found-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "DELETE", 4 | "path": "/jobs/d4be3c55-039a-4480-a85c-820bbbdd4899/history", 5 | "headers": { 6 | "If-Match": [ 7 | "\"fdqgxvtir8\"" 8 | ] 9 | } 10 | }, 11 | "mock": { 12 | "err": "not found" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /infrastructure/http/testdata/collections/post/ok-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "req": { 3 | "method": "POST", 4 | "path": "/collections", 5 | "headers": { 6 | "Content-Type": ["application/json"] 7 | }, 8 | "body": { 9 | "id": "fc62401e-7ad4-4050-995a-56bed972a7a6", 10 | "name": "my-app" 11 | } 12 | }, 13 | "mock": { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ui/shared/components/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ErrorSummary from './ErrorSummary'; 4 | 5 | const Layout = ({title, errors, children}) => ( 6 | <> 7 |

{title}

8 |
9 |
10 | 11 | {children} 12 |
13 | 14 | ); 15 | 16 | export default Layout; 17 | -------------------------------------------------------------------------------- /ui/features/jobs/jobs-api.js: -------------------------------------------------------------------------------- 1 | import {go} from '../../shared/fetch'; 2 | 3 | export {listCollections} from '../collections/collections-api'; 4 | 5 | export function listJobs(collectionId) { 6 | return go( 7 | 'GET', 8 | collectionId 9 | ? `/jobs?fields=status,errorRate&collectionId=${collectionId}` 10 | : '/jobs?fields=status,errorRate', 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /ui/shared/components/FieldError.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const FieldError = ({message}) => { 4 | if (!message) { 5 | return null; 6 | } 7 | return ( 8 |

9 |